diff --git a/.gitignore b/.gitignore index 6e60126e..b9b06ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -324,6 +324,7 @@ run_pet.bat !/plugins/emoji_manage_plugin !/plugins/take_picture_plugin !/plugins/deep_think +!/plugins/MaiFrequencyControl !/plugins/__init__.py config.toml diff --git a/src/chat/heart_flow/heartFC_chat.py b/src/chat/heart_flow/heartFC_chat.py index 105657d6..5cb115ea 100644 --- a/src/chat/heart_flow/heartFC_chat.py +++ b/src/chat/heart_flow/heartFC_chat.py @@ -25,7 +25,7 @@ from src.plugin_system.core import events_manager from src.plugin_system.apis import generator_api, send_api, message_api, database_api from src.mais4u.mai_think import mai_thinking_manager from src.mais4u.s4u_config import s4u_config -from src.chat.memory_system.Memory_chest import global_memory_chest +from src.memory_system.Memory_chest import global_memory_chest from src.chat.utils.chat_message_builder import ( build_readable_messages_with_id, get_raw_msg_before_timestamp_with_chat, diff --git a/src/chat/replyer/group_generator.py b/src/chat/replyer/group_generator.py index 97bba8ae..39eac7af 100644 --- a/src/chat/replyer/group_generator.py +++ b/src/chat/replyer/group_generator.py @@ -6,7 +6,7 @@ import re from typing import List, Optional, Dict, Any, Tuple from datetime import datetime -from src.chat.memory_system.Memory_chest import global_memory_chest +from src.memory_system.Memory_chest import global_memory_chest from src.mais4u.mai_think import mai_thinking_manager from src.common.logger import get_logger from src.common.data_models.database_data_model import DatabaseMessages @@ -27,7 +27,7 @@ from src.chat.utils.chat_message_builder import ( ) from src.chat.express.expression_selector import expression_selector -# from src.chat.memory_system.memory_activator import MemoryActivator +# from src.memory_system.memory_activator import MemoryActivator from src.person_info.person_info import Person from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api diff --git a/src/chat/replyer/private_generator.py b/src/chat/replyer/private_generator.py index 85afbc9f..d53a1e4c 100644 --- a/src/chat/replyer/private_generator.py +++ b/src/chat/replyer/private_generator.py @@ -6,7 +6,7 @@ import re from typing import List, Optional, Dict, Any, Tuple from datetime import datetime -from src.chat.memory_system.Memory_chest import global_memory_chest +from src.memory_system.Memory_chest import global_memory_chest from src.mais4u.mai_think import mai_thinking_manager from src.common.logger import get_logger from src.common.data_models.database_data_model import DatabaseMessages @@ -27,7 +27,7 @@ from src.chat.utils.chat_message_builder import ( ) from src.chat.express.expression_selector import expression_selector -# from src.chat.memory_system.memory_activator import MemoryActivator +# from src.memory_system.memory_activator import MemoryActivator from src.person_info.person_info import Person, is_person_known from src.plugin_system.base.component_types import ActionInfo, EventType diff --git a/src/chat/replyer/prompt/lpmm_prompt.py b/src/chat/replyer/prompt/lpmm_prompt.py index afce7f5e..43eb41f2 100644 --- a/src/chat/replyer/prompt/lpmm_prompt.py +++ b/src/chat/replyer/prompt/lpmm_prompt.py @@ -1,5 +1,5 @@ from src.chat.utils.prompt_builder import Prompt -# from src.chat.memory_system.memory_activator import MemoryActivator +# from src.memory_system.memory_activator import MemoryActivator def init_lpmm_prompt(): diff --git a/src/chat/replyer/prompt/rewrite_prompt.py b/src/chat/replyer/prompt/rewrite_prompt.py index b96d8cd4..bc91800e 100644 --- a/src/chat/replyer/prompt/rewrite_prompt.py +++ b/src/chat/replyer/prompt/rewrite_prompt.py @@ -1,5 +1,5 @@ from src.chat.utils.prompt_builder import Prompt -# from src.chat.memory_system.memory_activator import MemoryActivator +# from src.memory_system.memory_activator import MemoryActivator def init_rewrite_prompt(): diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 30ed2152..d3704890 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -326,6 +326,19 @@ class MemoryChest(BaseModel): class Meta: table_name = "memory_chest" + +class MemoryConflict(BaseModel): + """ + 用于存储记忆整合过程中冲突内容的模型 + """ + + conflict_content = TextField() # 冲突内容 + answer = TextField(null=True) # 回答内容 + create_time = FloatField() # 创建时间 + update_time = FloatField() # 更新时间 + + class Meta: + table_name = "memory_conflicts" @@ -382,6 +395,7 @@ def create_tables(): GraphEdges, # 添加图边表 ActionRecords, # 添加 ActionRecords 到初始化列表 MemoryChest, + MemoryConflict, # 添加记忆冲突表 ] ) @@ -410,6 +424,7 @@ def initialize_database(sync_constraints=False): GraphEdges, ActionRecords, # 添加 ActionRecords 到初始化列表 MemoryChest, + MemoryConflict, ] try: @@ -508,6 +523,7 @@ def sync_field_constraints(): GraphEdges, ActionRecords, MemoryChest, + MemoryConflict, ] try: @@ -692,6 +708,8 @@ def check_field_constraints(): GraphNodes, GraphEdges, ActionRecords, + MemoryChest, + MemoryConflict, ] inconsistencies = {} diff --git a/src/common/logger.py b/src/common/logger.py index c8bfa59e..8d49d53f 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -422,6 +422,7 @@ MODULE_COLORS = { # s4u "context_web_api": "\033[38;5;240m", # 深灰色 "S4U_chat": "\033[92m", # 深灰色 + "conflict_tracker": "\033[38;5;82m", # 柔和的粉色,不显眼但保持粉色系 } # 定义模块别名映射 - 将真实的logger名称映射到显示的别名 diff --git a/src/curiousity/questions.py b/src/curiousity/questions.py new file mode 100644 index 00000000..1a06b57a --- /dev/null +++ b/src/curiousity/questions.py @@ -0,0 +1,245 @@ +import time +import asyncio +from rich.traceback import install +from src.common.logger import get_logger +from src.common.database.database_model import MemoryConflict +from src.chat.utils.chat_message_builder import ( + get_raw_msg_by_timestamp_with_chat, + build_readable_messages, +) +from src.llm_models.utils_model import LLMRequest +from src.config.config import model_config, global_config + +logger = get_logger("conflict_tracker") + +logger = get_logger("conflict_tracker") + +install(extra_lines=3) + +class QuestionTracker: + """ + 用于跟踪一个问题在后续聊天中的解答情况 + """ + + def __init__(self, question: str, chat_id: str) -> None: + self.question = question + self.chat_id = chat_id + now = time.time() + self.start_time = now + self.last_read_time = now + self.active = True + # 将 LLM 实例作为类属性,使用 utils 模型 + self.llm_request = LLMRequest(model_set=model_config.model_task_config.utils, request_type="conflict.judge") + + def stop(self) -> None: + self.active = False + + async def judge_answer(self, conversation_text: str) -> tuple[bool, str]: + """ + 使用小模型判定问题是否已得到解答。 + 返回 (已解答, 答案) + """ + prompt = ( + "你是一个严谨的判定器。下面给出聊天记录以及一个问题。\n" + "任务:判断在这段聊天中,该问题是否已经得到明确解答。或从聊天内容中可以整理出答案\n" + "如果已解答,请只输出:YES: <简短答案>\n" + "如果没有,请只输出:NO\n\n" + f"问题:{self.question}\n" + "聊天记录如下:\n" + f"{conversation_text}" + ) + + + + if global_config.debug.show_prompt: + logger.info(f"判定提示词: {prompt}") + else: + logger.debug("已发送判定提示词") + + result_text, _ = await self.llm_request.generate_response_async(prompt, temperature=0.2) + if not result_text: + return False, "" + + logger.info(f"判定提示词: {prompt},问题: {self.question},result: {result_text}") + + text = result_text.strip() + if text.upper().startswith("YES:"): + answer = text[4:].strip() + return True, answer + if text.upper().startswith("YES"): + # 兼容仅输出 YES 或 YES + answer = text[3:].strip().lstrip(":").strip() + return True, answer + return False, "" + +class ConflictTracker: + """ + 记忆整合冲突追踪器 + + 用于记录和存储记忆整合过程中的冲突内容 + """ + + async def record_conflict(self, conflict_content: str, start_following: bool = False,chat_id: str = "") -> bool: + """ + 记录冲突内容 + + Args:k + conflict_content: 冲突内容 + + Returns: + bool: 是否成功记录 + """ + try: + if not conflict_content or conflict_content.strip() == "": + return False + + # 若需要跟随后续消息以判断是否得到解答,则进入跟踪流程 + if start_following and chat_id: + tracker = QuestionTracker(conflict_content.strip(), chat_id) + # 后台启动跟踪任务,避免阻塞 + asyncio.create_task(self._follow_and_record(tracker, conflict_content.strip())) + return True + + # 默认:直接记录,不进行跟踪 + MemoryConflict.create( + conflict_content=conflict_content, + create_time=time.time(), + update_time=time.time(), + answer="", + ) + + logger.info(f"记录冲突内容: {len(conflict_content)} 字符") + return True + + except Exception as e: + logger.error(f"记录冲突内容时出错: {e}") + return False + + async def _follow_and_record(self, tracker: QuestionTracker, original_question: str) -> None: + """ + 后台任务:跟踪问题是否被解答,并写入数据库。 + """ + try: + max_duration = 30 * 60 # 30 分钟 + max_messages = 100 # 最多 100 条消息 + poll_interval = 2.0 # 秒 + + while tracker.active: + now_ts = time.time() + # 终止条件:时长达到上限 + if now_ts - tracker.start_time >= max_duration: + logger.info("问题跟踪达到30分钟上限,判定为未解答") + break + + # 统计最近一段是否有新消息(不过滤机器人,过滤命令) + recent_msgs = get_raw_msg_by_timestamp_with_chat( + chat_id=tracker.chat_id, + timestamp_start=tracker.last_read_time, + timestamp_end=now_ts, + limit=0, + limit_mode="latest", + filter_bot=False, + filter_command=True, + ) + + if len(recent_msgs) > 0: + tracker.last_read_time = now_ts + + # 统计从开始到现在的总消息数(用于触发100条上限) + all_msgs = get_raw_msg_by_timestamp_with_chat( + chat_id=tracker.chat_id, + timestamp_start=tracker.start_time, + timestamp_end=now_ts, + limit=0, + limit_mode="latest", + filter_bot=False, + filter_command=True, + ) + + # 构建可读聊天文本 + chat_text = build_readable_messages( + all_msgs, + replace_bot_name=True, + timestamp_mode="relative", + read_mark=0.0, + truncate=False, + show_actions=False, + show_pic=False, + remove_emoji_stickers=True, + ) + + # 让小模型判断是否有答案 + answered, answer_text = await tracker.judge_answer(chat_text) + if answered: + logger.info("问题已得到解答,结束跟踪并写入答案") + tracker.stop() + MemoryConflict.create( + conflict_content=tracker.question, + create_time=tracker.start_time, + update_time=time.time(), + answer=answer_text or "", + ) + return + + if len(all_msgs) >= max_messages: + logger.info("问题跟踪达到100条消息上限,判定为未解答") + break + + # 无新消息时稍作等待 + await asyncio.sleep(poll_interval) + + # 未获取到答案,仅存储问题 + MemoryConflict.create( + conflict_content=original_question, + create_time=time.time(), + update_time=time.time(), + answer="", + ) + logger.info(f"记录冲突内容(未解答): {len(original_question)} 字符") + except Exception as e: + logger.error(f"后台问题跟踪任务异常: {e}") + + async def record_memory_merge_conflict(self, part2_content: str) -> bool: + """ + 记录记忆整合过程中的冲突内容(part2) + + Args: + part2_content: 冲突内容(part2) + + Returns: + bool: 是否成功记录 + """ + if not part2_content or part2_content.strip() == "": + return False + + return await self.record_conflict(part2_content) + + async def get_all_conflicts(self) -> list: + """ + 获取所有冲突记录 + + Returns: + list: 冲突记录列表 + """ + try: + conflicts = list(MemoryConflict.select()) + return conflicts + except Exception as e: + logger.error(f"获取冲突记录时出错: {e}") + return [] + + async def get_conflict_count(self) -> int: + """ + 获取冲突记录数量 + + Returns: + int: 记录数量 + """ + try: + return MemoryConflict.select().count() + except Exception as e: + logger.error(f"获取冲突记录数量时出错: {e}") + return 0 + +# 全局冲突追踪器实例 +global_conflict_tracker = ConflictTracker() \ No newline at end of file diff --git a/src/main.py b/src/main.py index c91f23fc..f3202727 100644 --- a/src/main.py +++ b/src/main.py @@ -13,9 +13,9 @@ from src.common.logger import get_logger from src.common.server import get_global_server, Server from src.mood.mood_manager import mood_manager from src.chat.knowledge import lpmm_start_up -from src.chat.memory_system.Hippocampus import hippocampus_manager -from src.chat.memory_system.hippocampus_to_memory_chest_task import HippocampusToMemoryChestTask -from src.chat.memory_system.memory_management_task import MemoryManagementTask +from src.memory_system.Hippocampus import hippocampus_manager +from src.memory_system.hippocampus_to_memory_chest_task import HippocampusToMemoryChestTask +from src.memory_system.memory_management_task import MemoryManagementTask from rich.traceback import install from src.migrate_helper.migrate import check_and_run_migrations # from src.api.main import start_api_server diff --git a/src/mais4u/mais4u_chat/s4u_msg_processor.py b/src/mais4u/mais4u_chat/s4u_msg_processor.py index 4263194b..c70b7e48 100644 --- a/src/mais4u/mais4u_chat/s4u_msg_processor.py +++ b/src/mais4u/mais4u_chat/s4u_msg_processor.py @@ -2,7 +2,7 @@ import asyncio import math from typing import Tuple -from src.chat.memory_system.Hippocampus import hippocampus_manager +from src.memory_system.Hippocampus import hippocampus_manager from src.chat.message_receive.message import MessageRecv, MessageRecvS4U from maim_message.message_base import GroupInfo from src.chat.message_receive.storage import MessageStorage diff --git a/src/mais4u/mais4u_chat/s4u_prompt.py b/src/mais4u/mais4u_chat/s4u_prompt.py index 15e4d729..a7a00506 100644 --- a/src/mais4u/mais4u_chat/s4u_prompt.py +++ b/src/mais4u/mais4u_chat/s4u_prompt.py @@ -4,7 +4,7 @@ from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat import time from src.chat.utils.utils import get_recent_group_speaker -from src.chat.memory_system.Hippocampus import hippocampus_manager +from src.memory_system.Hippocampus import hippocampus_manager import random from datetime import datetime import asyncio diff --git a/src/chat/memory_system/Hippocampus.py b/src/memory_system/Hippocampus.py similarity index 100% rename from src/chat/memory_system/Hippocampus.py rename to src/memory_system/Hippocampus.py diff --git a/src/chat/memory_system/Memory_chest.py b/src/memory_system/Memory_chest.py similarity index 79% rename from src/chat/memory_system/Memory_chest.py rename to src/memory_system/Memory_chest.py index 57f22d54..bfdabbe0 100644 --- a/src/chat/memory_system/Memory_chest.py +++ b/src/memory_system/Memory_chest.py @@ -99,19 +99,34 @@ class MemoryChest: current_running_content = self.running_content_list[chat_id]["content"] prompt = f""" -以下是你的记忆内容: +以下是你的记忆内容和新的聊天记录,请你将他们整合和修改: +记忆内容: + {current_running_content} + -请将下面的新聊天记录内的有用的信息,添加到你的记忆中 -请主要关注概念和知识,而不是聊天的琐事 -重要!!你要关注的概念和知识必须是较为不常见的信息,或者时效性较强的信息!! -不要!!关注常见的只是,或者已经过时的信息!! +<聊天记录> +{message_str} + +聊天记录中可能包含有效信息,也可能信息密度很低,请你根据聊天记录中的信息,修改中的内容与中的内容 +-------------------------------- +请将上面的新聊天记录内的有用的信息进行整合到现有的记忆中 +请主要关注概念和知识或者时效性较强的信息!!,而不是聊天的琐事 1.不要关注诸如某个用户做了什么,说了什么,不要关注某个用户的行为,而是关注其中的概念性信息 2.概念要求精确,不啰嗦,像科普读物或教育课本那样 3.如果有图片,请只关注图片和文本结合的知识和概念性内容 -记忆为一段纯文本,逻辑清晰,指出概念的含义,并说明关系 -请输出添加后的记忆内容,不要输出其他内容: -{message_str} +4.记忆为一段纯文本,逻辑清晰,指出概念的含义,并说明关系 + +记忆内容的格式,你必须仿照下面的格式,但不一定全部使用: +[概念] 是 [概念的含义(简短描述,不超过十个字)] +[概念] 不是 [对概念的负面含义(简短描述,不超过十个字)] +[概念1] 与 [概念2] 是 [概念1和概念2的关联(简短描述,不超过二十个字)] +[概念1] 包含 [概念2] 和 [概念3] +[概念1] 属于 [概念2] +......(不要包含中括号) + +请仿照上述格式输出,每个知识点一句话。输出成一段平文本 +现在请你输出,不要输出其他内容,注意一定要直白,白话,口语化不要浮夸,修辞。: """ if global_config.debug.show_prompt: @@ -120,7 +135,7 @@ class MemoryChest: logger.debug(f"记忆仓库构建运行内容 prompt: {prompt}") running_content, (reasoning_content, model_name, tool_calls) = await self.LLMRequest_build.generate_response_async(prompt) - + print(f"记忆仓库构建运行内容: {running_content}") # 如果有chat_id,更新对应的running_content @@ -136,7 +151,7 @@ class MemoryChest: "create_time": create_time } - # 检查running_content长度是否大于500 + # 检查running_content长度是否大于限制 if len(running_content) > self.memory_size_limit: await self._save_to_database_and_clear(chat_id, running_content) @@ -147,8 +162,7 @@ class MemoryChest: f"内容大小 {len(running_content)} 达到限制的 {int(self.memory_size_limit * 0.3)} 字符,强制保存") await self._save_to_database_and_clear(chat_id, running_content) - - + return running_content @@ -478,6 +492,54 @@ class MemoryChest: logger.error(f"根据标题查找记忆时出错: {e}") return [] + def _parse_merged_parts(self, merged_response: str) -> tuple[str, str]: + """ + 解析合并记忆的part1和part2内容 + + Args: + merged_response: LLM返回的合并记忆响应 + + Returns: + tuple[str, str]: (part1_content, part2_content) + """ + try: + # 使用正则表达式提取part1和part2内容 + import re + + # 提取part1内容 + part1_pattern = r'(.*?)' + part1_match = re.search(part1_pattern, merged_response, re.DOTALL) + part1_content = part1_match.group(1).strip() if part1_match else "" + + # 提取part2内容 + part2_pattern = r'(.*?)' + part2_match = re.search(part2_pattern, merged_response, re.DOTALL) + part2_content = part2_match.group(1).strip() if part2_match else "" + + # 检查是否包含none或None(不区分大小写) + def is_none_content(content: str) -> bool: + if not content: + return True + # 检查是否只包含"none"或"None"(不区分大小写) + return re.match(r'^\s*none\s*$', content, re.IGNORECASE) is not None + + # 如果包含none,则设置为空字符串 + if is_none_content(part1_content): + part1_content = "" + logger.info("part1内容为none,设置为空") + + if is_none_content(part2_content): + part2_content = "" + logger.info("part2内容为none,设置为空") + + logger.info(f"解析合并记忆结果: part1={len(part1_content)}字符, part2={len(part2_content)}字符") + + return part1_content, part2_content + + except Exception as e: + logger.error(f"解析合并记忆part1/part2时出错: {e}") + return "", "" + def _parse_merge_target_json(self, json_text: str) -> list[str]: """ 解析choose_merge_target生成的JSON响应 @@ -542,19 +604,35 @@ class MemoryChest: content += f"{memory}\n" prompt = f""" -以下是多段记忆内容,请将它们合并成一段记忆: +以下是多段记忆内容,请将它们进行整合和修改: {content} - -请将下面的多段记忆内容,合并成一段记忆 +-------------------------------- +请将上面的多段记忆内容,合并成两部分内容,第一部分是可以整合,不冲突的概念和知识,第二部分是相互有冲突的概念和知识 请主要关注概念和知识,而不是聊天的琐事 重要!!你要关注的概念和知识必须是较为不常见的信息,或者时效性较强的信息!! 不要!!关注常见的只是,或者已经过时的信息!! 1.不要关注诸如某个用户做了什么,说了什么,不要关注某个用户的行为,而是关注其中的概念性信息 2.概念要求精确,不啰嗦,像科普读物或教育课本那样 3.如果有图片,请只关注图片和文本结合的知识和概念性内容 -4.如果记忆中有冲突的地方,可以进行整合。如果无法整合,需要在此处标注存在冲突的不同信息 -记忆为一段纯文本,逻辑清晰,指出概念的含义,并说明关系 -请输出合并的记忆内容,不要输出其他内容: +4.记忆为一段纯文本,逻辑清晰,指出概念的含义,并说明关系 +**第一部分** +1.如果两个概念在描述同一件事情,且相互之间逻辑不冲突(请你严格判断),且相互之间没有矛盾,请将它们整合成一个概念,并输出到第一部分 +2.如果某个概念在时间上更新了另一个概念,请用新概念更新就概念来整合,并输出到第一部分 +3.如果没有可整合的概念,请你输出none +**第二部分** +1.如果记忆中有无法整合的地方,例如概念不一致,有逻辑上的冲突,请你输出到第二部分 +2.如果两个概念在描述同一件事情,但相互之间逻辑冲突,请将它们输出到第二部分 +3.如果没有无法整合的概念,请你输出none + +**输出格式要求** +请你按以下格式输出: + +第一部分内容,整合后的概念,如果第一部分为none,请输出none + + +第二部分内容,无法整合,冲突的概念,如果第二部分为none,请输出none + +不要输出其他内容,现在请你输出,不要输出其他内容,注意一定要直白,白话,口语化不要浮夸,修辞。: """ if global_config.debug.show_prompt: @@ -564,18 +642,33 @@ class MemoryChest: merged_memory, (reasoning_content, model_name, tool_calls) = await self.LLMRequest_build.generate_response_async(prompt) - # 生成合并后的标题 - merged_title = await self._generate_title_for_merged_memory(merged_memory) - - # 保存合并后的记忆到数据库 - MemoryChestModel.create( - title=merged_title, - content=merged_memory - ) - - logger.info(f"合并记忆已保存: {merged_title}") - - return merged_title, merged_memory + # 解析part1和part2 + part1_content, part2_content = self._parse_merged_parts(merged_memory) + + # 处理part2:独立记录冲突内容(无论part1是否为空) + if part2_content and part2_content.strip() != "none": + logger.info(f"合并记忆part2记录冲突内容: {len(part2_content)} 字符") + # 导入冲突追踪器 + from src.curiousity.questions import global_conflict_tracker + # 记录冲突到数据库 + await global_conflict_tracker.record_memory_merge_conflict(part2_content) + + # 处理part1:生成标题并保存 + if part1_content and part1_content.strip() != "none": + merged_title = await self._generate_title_for_merged_memory(part1_content) + + # 保存part1到数据库 + MemoryChestModel.create( + title=merged_title, + content=part1_content + ) + + logger.info(f"合并记忆part1已保存: {merged_title}") + + return merged_title, part1_content + else: + logger.warning("合并记忆part1为空,跳过保存") + return "", "" except Exception as e: logger.error(f"合并记忆时出错: {e}") return "", "" diff --git a/src/chat/memory_system/hippocampus_to_memory_chest_task.py b/src/memory_system/hippocampus_to_memory_chest_task.py similarity index 99% rename from src/chat/memory_system/hippocampus_to_memory_chest_task.py rename to src/memory_system/hippocampus_to_memory_chest_task.py index 6349a423..ea94f6e6 100644 --- a/src/chat/memory_system/hippocampus_to_memory_chest_task.py +++ b/src/memory_system/hippocampus_to_memory_chest_task.py @@ -5,7 +5,7 @@ import re from typing import List from src.manager.async_task_manager import AsyncTask -from src.chat.memory_system.Hippocampus import hippocampus_manager +from src.memory_system.Hippocampus import hippocampus_manager from src.common.logger import get_logger logger = get_logger("hippocampus_to_memory_chest") diff --git a/src/chat/memory_system/memory_management_task.py b/src/memory_system/memory_management_task.py similarity index 99% rename from src/chat/memory_system/memory_management_task.py rename to src/memory_system/memory_management_task.py index 52ec330e..2b47d51f 100644 --- a/src/chat/memory_system/memory_management_task.py +++ b/src/memory_system/memory_management_task.py @@ -4,7 +4,7 @@ import random from typing import List from src.manager.async_task_manager import AsyncTask -from src.chat.memory_system.Memory_chest import global_memory_chest +from src.memory_system.Memory_chest import global_memory_chest from src.common.logger import get_logger from src.common.database.database_model import MemoryChest as MemoryChestModel from src.config.config import global_config diff --git a/src/chat/memory_system/memory_utils.py b/src/memory_system/memory_utils.py similarity index 100% rename from src/chat/memory_system/memory_utils.py rename to src/memory_system/memory_utils.py diff --git a/src/plugins/built_in/MaiCurious/_manifest.json b/src/plugins/built_in/MaiCurious/_manifest.json new file mode 100644 index 00000000..121c9454 --- /dev/null +++ b/src/plugins/built_in/MaiCurious/_manifest.json @@ -0,0 +1,34 @@ +{ + "manifest_version": 1, + "name": "MaiCurious插件 (MaiCurious Actions)", + "version": "1.0.0", + "description": "可以好奇", + "author": { + "name": "SengokuCola", + "url": "https://github.com/MaiM-with-u" + }, + "license": "GPL-v3.0-or-later", + + "host_application": { + "min_version": "0.11.0" + }, + "homepage_url": "https://github.com/MaiM-with-u/maibot", + "repository_url": "https://github.com/MaiM-with-u/maibot", + "keywords": ["curious", "action", "built-in"], + "categories": ["Deep Think"], + + "default_locale": "zh-CN", + "locales_path": "_locales", + + "plugin_info": { + "is_built_in": true, + "plugin_type": "action_provider", + "components": [ + { + "type": "action", + "name": "maicurious", + "description": "发送好奇" + } + ] + } +} diff --git a/src/plugins/built_in/MaiCurious/plugin.py b/src/plugins/built_in/MaiCurious/plugin.py new file mode 100644 index 00000000..f93b6c83 --- /dev/null +++ b/src/plugins/built_in/MaiCurious/plugin.py @@ -0,0 +1,116 @@ +from typing import List, Tuple, Type, Any + +# 导入新插件系统 +from src.plugin_system import BasePlugin, register_plugin, ComponentInfo +from src.plugin_system.base.config_types import ConfigField +from src.person_info.person_info import Person +from src.plugin_system.base.base_tool import BaseTool, ToolParamType + +# 导入依赖的系统组件 +from src.common.logger import get_logger + +from src.plugins.built_in.relation.relation import BuildRelationAction +from src.plugin_system.apis import llm_api +from src.plugin_system.base.base_action import BaseAction +from src.plugin_system.base.component_types import ActionActivationType +from src.plugin_system.apis import config_api +from src.plugin_system.apis import frequency_api +from src.plugin_system.apis import generator_api +from src.curiousity.questions import global_conflict_tracker + +logger = get_logger("relation_actions") + + + +class CuriousAction(BaseAction): + """频率调节动作 - 调整聊天发言频率""" + + activation_type = ActionActivationType.LLM_JUDGE + parallel_action = False + + # 动作基本信息 + action_name = "question" + + action_description = "对现有的存在疑问的信息生成一个问题" + + # 动作参数定义 + action_parameters = { + "question": "存在疑问的信息,提出一个问题", + } + + # 动作使用场景 + bot_name = config_api.get_global_config("bot.nickname") + + + action_require = [ + f"当聊天记录中的信息存在逻辑上的矛盾时使用", + f"当有人反对或否定你提出的信息时使用", + f"或当你对现有的信息存在疑问时使用", + ] + + # 关联类型 + associated_types = ["text"] + + async def execute(self) -> Tuple[bool, str]: + """执行频率调节动作""" + try: + question = self.action_data.get("question", "") + + # 存储问题到冲突追踪器 + if question: + await global_conflict_tracker.record_conflict(conflict_content=question, start_following=True,chat_id=self.chat_id) + logger.info(f"已存储问题到冲突追踪器: {len(question)} 字符") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"你产生了一个问题,原因:{question},尝试向其他人提问或回忆吧", + action_done=True, + ) + return True, "问题已记录" + except Exception as e: + error_msg = f"问题生成失败: {str(e)}" + logger.error(f"{self.log_prefix} {error_msg}", exc_info=True) + await self.send_text("问题生成失败") + return False, error_msg + + +@register_plugin +class CuriousPlugin(BasePlugin): + """关系动作插件 + + 系统内置插件,提供基础的聊天交互功能: + - Reply: 回复动作 + - NoReply: 不回复动作 + - Emoji: 表情动作 + + 注意:插件基本信息优先从_manifest.json文件中读取 + """ + + # 插件基本信息 + plugin_name: str = "maicurious" # 内部标识符 + enable_plugin: bool = True + dependencies: list[str] = [] # 插件依赖列表 + python_dependencies: list[str] = [] # Python包依赖列表 + config_file_name: str = "config.toml" + + # 配置节描述 + config_section_descriptions = { + "plugin": "插件启用配置", + "components": "核心组件启用配置", + } + + # 配置Schema定义 + config_schema: dict = { + "plugin": { + "enabled": ConfigField(type=bool, default=False, description="是否启用插件"), + "config_version": ConfigField(type=str, default="3.0.0", description="配置文件版本"), + } + } + + def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: + """返回插件包含的组件列表""" + + # --- 根据配置注册组件 --- + components = [] + components.append((CuriousAction.get_action_info(), CuriousAction)) + + return components diff --git a/src/plugins/built_in/memory/build_memory.py b/src/plugins/built_in/memory/build_memory.py index 9158fcbc..de25023c 100644 --- a/src/plugins/built_in/memory/build_memory.py +++ b/src/plugins/built_in/memory/build_memory.py @@ -5,9 +5,9 @@ from src.config.config import global_config from src.chat.utils.prompt_builder import Prompt from src.llm_models.payload_content.tool_option import ToolParamType from src.plugin_system import BaseAction, ActionActivationType -from src.chat.memory_system.Hippocampus import hippocampus_manager +from src.memory_system.Hippocampus import hippocampus_manager from src.chat.utils.utils import cut_key_words -from src.chat.memory_system.Memory_chest import global_memory_chest +from src.memory_system.Memory_chest import global_memory_chest from src.plugin_system.base.base_tool import BaseTool from typing import Any