diff --git a/.gitignore b/.gitignore index 373171a7..6e60126e 100644 --- a/.gitignore +++ b/.gitignore @@ -323,6 +323,8 @@ run_pet.bat !/plugins/hello_world_plugin !/plugins/emoji_manage_plugin !/plugins/take_picture_plugin +!/plugins/deep_think +!/plugins/__init__.py config.toml diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/deep_think/_manifest.json b/plugins/deep_think/_manifest.json new file mode 100644 index 00000000..036bab0c --- /dev/null +++ b/plugins/deep_think/_manifest.json @@ -0,0 +1,34 @@ +{ + "manifest_version": 1, + "name": "Deep Think插件 (Deep Think 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": ["deep", "think", "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": "deep_think", + "description": "发送深度思考" + } + ] + } +} diff --git a/plugins/deep_think/plugin.py b/plugins/deep_think/plugin.py new file mode 100644 index 00000000..8fa75c9d --- /dev/null +++ b/plugins/deep_think/plugin.py @@ -0,0 +1,102 @@ +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 + +logger = get_logger("relation_actions") + + + +class DeepThinkTool(BaseTool): + """获取用户信息""" + + name = "deep_think" + description = "深度思考,对某个问题进行全面且深入的思考,当面临复杂环境或重要问题时,使用此获得更好的解决方案" + parameters = [ + ("question", ToolParamType.STRING, "需要思考的问题,越具体越好", True, None), + ] + + available_for_llm = True + + async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]: + """执行比较两个数的大小 + + Args: + function_args: 工具参数 + + Returns: + dict: 工具执行结果 + """ + question: str = function_args.get("question") # type: ignore + + print(f"question: {question}") + + prompt = f""" +请你思考以下问题,以简洁的一段话回答: +{question} + """ + + models = llm_api.get_available_models() + chat_model_config = models.get("replyer") # 使用字典访问方式 + + success, thinking_result, _, _ = await llm_api.generate_with_model( + prompt, model_config=chat_model_config, request_type="deep_think" + ) + + print(f"thinking_result: {thinking_result}") + + thinking_result =f"思考结果:{thinking_result}\n**注意** 因为你进行了深度思考,最后的回复内容可以回复的长一些,更加详细一些,不用太简洁。\n" + + return {"content": thinking_result} + + +@register_plugin +class DeepThinkPlugin(BasePlugin): + """关系动作插件 + + 系统内置插件,提供基础的聊天交互功能: + - Reply: 回复动作 + - NoReply: 不回复动作 + - Emoji: 表情动作 + + 注意:插件基本信息优先从_manifest.json文件中读取 + """ + + # 插件基本信息 + plugin_name: str = "deep_think" # 内部标识符 + 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="2.0.0", description="配置文件版本"), + } + } + + def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: + """返回插件包含的组件列表""" + + # --- 根据配置注册组件 --- + components = [] + components.append((DeepThinkTool.get_tool_info(), DeepThinkTool)) + + return components diff --git a/src/chat/express/expression_learner.py b/src/chat/express/expression_learner.py index 79fa433e..4b3d75db 100644 --- a/src/chat/express/expression_learner.py +++ b/src/chat/express/expression_learner.py @@ -427,7 +427,7 @@ class ExpressionLearner: chat_str=random_msg_str, ) - print(f"random_msg_str:{random_msg_str}") + # print(f"random_msg_str:{random_msg_str}") logger.info(f"学习{type_str}的prompt: {prompt}") try: diff --git a/src/chat/heart_flow/heartFC_chat.py b/src/chat/heart_flow/heartFC_chat.py index 88909b2b..3ae22013 100644 --- a/src/chat/heart_flow/heartFC_chat.py +++ b/src/chat/heart_flow/heartFC_chat.py @@ -25,6 +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.chat.utils.chat_message_builder import ( build_readable_messages_with_id, get_raw_msg_before_timestamp_with_chat, @@ -102,6 +103,7 @@ class HeartFChatting: self.talk_threshold = global_config.chat.talk_value self.no_reply_until_call = False + async def start(self): """检查是否需要启动主循环,如果未激活则启动。""" @@ -284,6 +286,10 @@ class HeartFChatting: async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): await self.expression_learner.trigger_learning_for_chat() + + await global_memory_chest.build_running_content(chat_id=self.stream_id) + + cycle_timers, thinking_id = self.start_cycle() logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考") diff --git a/src/chat/memory_system/Memory_chest.py b/src/chat/memory_system/Memory_chest.py new file mode 100644 index 00000000..6c595f9b --- /dev/null +++ b/src/chat/memory_system/Memory_chest.py @@ -0,0 +1,321 @@ + +from src.llm_models.utils_model import LLMRequest +from src.config.config import model_config +from src.common.database.database_model import MemoryChest as MemoryChestModel +from src.common.logger import get_logger +from src.config.config import global_config +from src.plugin_system.apis.message_api import build_readable_messages +import time +from src.plugin_system.apis.message_api import get_raw_msg_by_timestamp_with_chat + +logger = get_logger("memory_chest") + +class MemoryChest: + def __init__(self): + + self.LLMRequest = LLMRequest( + model_set=model_config.model_task_config.utils_small, + request_type="memory_chest", + ) + + self.memory_build_threshold = 20 + self.memory_size_limit = 300 + + self.running_content_list = {} # {chat_id: {"content": running_content, "last_update_time": timestamp}} + self.fetched_memory_list = [] # [(chat_id, (question, answer, timestamp)), ...] + + async def build_running_content(self, chat_id: str = None) -> str: + """ + 构建记忆仓库的运行内容 + + Args: + message_str: 消息内容 + chat_id: 聊天ID,用于提取对应的运行内容 + + Returns: + str: 构建后的运行内容 + """ + # 检查是否需要更新:上次更新时间和现在时间的消息数量大于30 + if chat_id not in self.running_content_list: + self.running_content_list[chat_id] = { + "content": "", + "last_update_time": time.time() + } + + should_update = True + if chat_id and chat_id in self.running_content_list: + last_update_time = self.running_content_list[chat_id]["last_update_time"] + current_time = time.time() + # 使用message_api获取消息数量 + message_list = get_raw_msg_by_timestamp_with_chat( + timestamp_start=last_update_time, + timestamp_end=current_time, + chat_id=chat_id, + limit=global_config.chat.max_context_size * 2, + ) + + new_messages_count = len(message_list) + should_update = new_messages_count > self.memory_build_threshold + logger.info(f"chat_id {chat_id} 自上次更新后有 {new_messages_count} 条新消息,{'需要' if should_update else '不需要'}更新") + + + if should_update: + # 如果有chat_id,先提取对应的running_content + message_str = build_readable_messages( + message_list, + replace_bot_name=True, + timestamp_mode="relative", + read_mark=0.0, + show_actions=True, + ) + + + current_running_content = "" + if chat_id and chat_id in self.running_content_list: + current_running_content = self.running_content_list[chat_id]["content"] + + prompt = f""" +以下是你的记忆内容: +{current_running_content} + +请将下面的新聊天记录内的有用的信息,添加到你的记忆中 +请主要关注概念和知识,而不是聊天的琐事 +记忆为一段纯文本,逻辑清晰,指出事件,概念的含义,并说明关系 +请输出添加后的记忆内容,不要输出其他内容: +{message_str} +""" + + if global_config.debug.show_prompt: + logger.info(f"记忆仓库构建运行内容 prompt: {prompt}") + else: + logger.debug(f"记忆仓库构建运行内容 prompt: {prompt}") + + running_content, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(prompt) + + print(f"记忆仓库构建运行内容: {running_content}") + + # 如果有chat_id,更新对应的running_content + if chat_id and running_content: + self.running_content_list[chat_id] = { + "content": running_content, + "last_update_time": time.time() + } + + # 检查running_content长度是否大于500 + if len(running_content) > self.memory_size_limit: + await self._save_to_database_and_clear(chat_id, running_content) + + + + return running_content + + + + + def get_all_titles(self) -> list[str]: + """ + 获取记忆仓库中的所有标题 + + Returns: + list: 包含所有标题的列表 + """ + try: + # 查询所有记忆记录的标题 + titles = [] + for memory in MemoryChestModel.select(): + if memory.title: + titles.append(memory.title) + return titles + except Exception as e: + print(f"获取记忆标题时出错: {e}") + return [] + + async def get_answer_by_question(self, chat_id: str = "", question: str = "") -> str: + """ + 根据问题获取答案 + """ + title = await self.select_title_by_question(question) + + if not title: + return "" + + for memory in MemoryChestModel.select(): + if memory.title == title: + content = memory.content + + prompt = f""" +{content} + +请根据问题:{question} +在上方内容中,提取相关信息的原文并输出,请务必提取上面原文,不要输出其他内容: +""" + + if global_config.debug.show_prompt: + logger.info(f"记忆仓库获取答案 prompt: {prompt}") + else: + logger.debug(f"记忆仓库获取答案 prompt: {prompt}") + + answer, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(prompt) + + + logger.info(f"记忆仓库获取答案: {answer}") + + # 将问题和答案存到fetched_memory_list + if chat_id and answer: + self.fetched_memory_list.append((chat_id, (question, answer, time.time()))) + + # 清理fetched_memory_list + self._cleanup_fetched_memory_list() + + return answer + + def get_chat_memories_as_string(self, chat_id: str) -> str: + """ + 获取某个chat_id的所有记忆,并构建成字符串 + + Args: + chat_id: 聊天ID + + Returns: + str: 格式化的记忆字符串,格式:问题:xxx,答案:xxxxx\n问题:xxx,答案:xxxxx\n... + """ + try: + memories = [] + + # 从fetched_memory_list中获取该chat_id的所有记忆 + for cid, (question, answer, timestamp) in self.fetched_memory_list: + if cid == chat_id: + memories.append(f"问题:{question},答案:{answer}") + + # 按时间戳排序(最新的在后面) + memories.sort() + + # 用换行符连接所有记忆 + result = "\n".join(memories) + + logger.info(f"chat_id {chat_id} 共有 {len(memories)} 条记忆") + return result + + except Exception as e: + logger.error(f"获取chat_id {chat_id} 的记忆时出错: {e}") + return "" + + + async def select_title_by_question(self, question: str) -> str: + """ + 根据消息内容选择最匹配的标题 + + Args: + question: 问题 + + Returns: + str: 选择的标题 + """ + # 获取所有标题并构建格式化字符串 + titles = self.get_all_titles() + formatted_titles = "" + for title in titles: + formatted_titles += f"{title}\n" + + prompt = f""" +所有主题: +{formatted_titles} + +请根据以下问题,选择一个能够回答问题的主题: +问题:{question} +请你输出主题,不要输出其他内容,完整输出主题名: +""" + + if global_config.debug.show_prompt: + logger.info(f"记忆仓库选择标题 prompt: {prompt}") + else: + logger.debug(f"记忆仓库选择标题 prompt: {prompt}") + + + title, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(prompt) + + # 根据 title 获取 titles 里的对应项 + titles = self.get_all_titles() + selected_title = None + + # 查找完全匹配的标题 + for t in titles: + if t == title: + selected_title = t + break + + + logger.info(f"记忆仓库选择标题: {selected_title}") + + return selected_title + + def _cleanup_fetched_memory_list(self): + """ + 清理fetched_memory_list,移除超过10分钟的记忆和超过10条的最旧记忆 + """ + try: + current_time = time.time() + ten_minutes_ago = current_time - 600 # 10分钟 = 600秒 + + # 移除超过10分钟的记忆 + self.fetched_memory_list = [ + (chat_id, (question, answer, timestamp)) + for chat_id, (question, answer, timestamp) in self.fetched_memory_list + if timestamp > ten_minutes_ago + ] + + # 如果记忆条数超过10条,移除最旧的5条 + if len(self.fetched_memory_list) > 10: + # 按时间戳排序,移除最旧的5条 + self.fetched_memory_list.sort(key=lambda x: x[1][2]) # 按timestamp排序 + self.fetched_memory_list = self.fetched_memory_list[5:] # 保留最新的5条 + + logger.debug(f"fetched_memory_list清理后,当前有 {len(self.fetched_memory_list)} 条记忆") + + except Exception as e: + logger.error(f"清理fetched_memory_list时出错: {e}") + + async def _save_to_database_and_clear(self, chat_id: str, content: str): + """ + 生成标题,保存到数据库,并清空对应chat_id的running_content + + Args: + chat_id: 聊天ID + content: 要保存的内容 + """ + try: + # 生成标题 + title_prompt = f""" +请为以下内容生成一个描述全面的标题,要求描述内容的主要概念和事件: +{content} + +请只输出标题,不要输出其他内容: +""" + + if global_config.debug.show_prompt: + logger.info(f"记忆仓库生成标题 prompt: {title_prompt}") + else: + logger.debug(f"记忆仓库生成标题 prompt: {title_prompt}") + + title, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(title_prompt) + + if title: + # 保存到数据库 + MemoryChestModel.create( + title=title.strip(), + content=content + ) + logger.info(f"已保存记忆仓库内容,标题: {title.strip()}, chat_id: {chat_id}") + + # 清空对应chat_id的running_content + if chat_id in self.running_content_list: + del self.running_content_list[chat_id] + logger.info(f"已清空chat_id {chat_id} 的running_content") + else: + logger.warning(f"生成标题失败,chat_id: {chat_id}") + + except Exception as e: + logger.error(f"保存记忆仓库内容时出错: {e}") + + +global_memory_chest = MemoryChest() \ No newline at end of file diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 46967075..c5537b04 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -109,7 +109,7 @@ no_reply_until_call """ {action_name} 动作描述:{action_description} -使用条件: +使用条件{parallel_text}: {action_require} {{ "action": "{action_name}",{action_parameters}, @@ -421,6 +421,11 @@ class ActionPlanner: for require_item in action_info.action_require: require_text += f"- {require_item}\n" require_text = require_text.rstrip("\n") + + if not action_info.parallel_action: + parallel_text = "(当选择这个动作时,请不要选择其他动作)" + else: + parallel_text = "" # 获取动作提示模板并填充 using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt") @@ -429,6 +434,7 @@ class ActionPlanner: action_description=action_info.description, action_parameters=param_text, action_require=require_text, + parallel_text=parallel_text, ) action_options_block += using_action_prompt diff --git a/src/chat/replyer/group_generator.py b/src/chat/replyer/group_generator.py index 87bfc670..4a45b741 100644 --- a/src/chat/replyer/group_generator.py +++ b/src/chat/replyer/group_generator.py @@ -6,6 +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.mais4u.mai_think import mai_thinking_manager from src.common.logger import get_logger from src.common.data_models.database_data_model import DatabaseMessages @@ -315,6 +316,17 @@ class DefaultReplyer: # memory_str += f"- {instant_memory}\n" # return memory_str + + async def build_memory_block(self) -> str: + """构建记忆块 + """ + # if not global_config.memory.enable_memory: + # return "" + + if global_memory_chest.get_chat_memories_as_string(self.chat_stream.stream_id): + return f"你有以下记忆:\n{global_memory_chest.get_chat_memories_as_string(self.chat_stream.stream_id)}" + else: + return "" async def build_tool_info(self, chat_history: str, sender: str, target: str, enable_tool: bool = True) -> str: """构建工具信息块 @@ -701,6 +713,7 @@ class DefaultReplyer: # self.build_relation_info(chat_talking_prompt_short, sender, person_list_short), "relation_info" # ), # self._time_and_run_task(self.build_memory_block(message_list_before_short, target), "memory_block"), + self._time_and_run_task(self.build_memory_block(), "memory_block"), self._time_and_run_task( self.build_tool_info(chat_talking_prompt_short, sender, target, enable_tool=enable_tool), "tool_info" ), @@ -714,6 +727,7 @@ class DefaultReplyer: "expression_habits": "选取表达方式", "relation_info": "感受关系", # "memory_block": "回忆", + "memory_block": "记忆", "tool_info": "使用工具", "prompt_info": "获取知识", "actions_info": "动作信息", @@ -742,6 +756,7 @@ class DefaultReplyer: selected_expressions: List[int] # relation_info: str = results_dict["relation_info"] # memory_block: str = results_dict["memory_block"] + memory_block: str = results_dict["memory_block"] tool_info: str = results_dict["tool_info"] prompt_info: str = results_dict["prompt_info"] # 直接使用格式化后的结果 actions_info: str = results_dict["actions_info"] @@ -779,6 +794,7 @@ class DefaultReplyer: "replyer_self_prompt", expression_habits_block=expression_habits_block, tool_info_block=tool_info, + memory_block=memory_block, knowledge_prompt=prompt_info, # memory_block=memory_block, # relation_info_block=relation_info, @@ -798,6 +814,7 @@ class DefaultReplyer: "replyer_prompt", expression_habits_block=expression_habits_block, tool_info_block=tool_info, + memory_block=memory_block, knowledge_prompt=prompt_info, # memory_block=memory_block, # relation_info_block=relation_info, @@ -946,7 +963,7 @@ class DefaultReplyer: async def llm_generate_content(self, prompt: str): with Timer("LLM生成", {}): # 内部计时器,可选保留 # 直接使用已初始化的模型实例 - # logger.info(f"\n{prompt}\n") + logger.info(f"\n{prompt}\n") if global_config.debug.show_prompt: logger.info(f"\n{prompt}\n") diff --git a/src/chat/replyer/private_generator.py b/src/chat/replyer/private_generator.py index c134b64e..85afbc9f 100644 --- a/src/chat/replyer/private_generator.py +++ b/src/chat/replyer/private_generator.py @@ -6,6 +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.mais4u.mai_think import mai_thinking_manager from src.common.logger import get_logger from src.common.data_models.database_data_model import DatabaseMessages @@ -312,6 +313,15 @@ class PrivateReplyer: # return memory_str + + async def build_memory_block(self) -> str: + """构建记忆块 + """ + if global_memory_chest.get_chat_memories_as_string(self.chat_stream.stream_id): + return f"你有以下记忆:\n{global_memory_chest.get_chat_memories_as_string(self.chat_stream.stream_id)}" + else: + return "" + async def build_tool_info(self, chat_history: str, sender: str, target: str, enable_tool: bool = True) -> str: """构建工具信息块 @@ -582,6 +592,7 @@ class PrivateReplyer: self._time_and_run_task( self.build_relation_info(chat_talking_prompt_short, sender), "relation_info" ), + self._time_and_run_task(self.build_memory_block(), "memory_block"), # self._time_and_run_task(self.build_memory_block(message_list_before_short, target), "memory_block"), self._time_and_run_task( self.build_tool_info(chat_talking_prompt_short, sender, target, enable_tool=enable_tool), "tool_info" @@ -595,7 +606,7 @@ class PrivateReplyer: task_name_mapping = { "expression_habits": "选取表达方式", "relation_info": "感受关系", - # "memory_block": "回忆", + "memory_block": "回忆", "tool_info": "使用工具", "prompt_info": "获取知识", "actions_info": "动作信息", @@ -623,7 +634,7 @@ class PrivateReplyer: expression_habits_block: str selected_expressions: List[int] relation_info: str = results_dict["relation_info"] - # memory_block: str = results_dict["memory_block"] + memory_block: str = results_dict["memory_block"] tool_info: str = results_dict["tool_info"] prompt_info: str = results_dict["prompt_info"] # 直接使用格式化后的结果 actions_info: str = results_dict["actions_info"] @@ -649,7 +660,7 @@ class PrivateReplyer: expression_habits_block=expression_habits_block, tool_info_block=tool_info, knowledge_prompt=prompt_info, - # memory_block=memory_block, + memory_block=memory_block, relation_info_block=relation_info, extra_info_block=extra_info_block, identity=personality_prompt, @@ -670,7 +681,7 @@ class PrivateReplyer: expression_habits_block=expression_habits_block, tool_info_block=tool_info, knowledge_prompt=prompt_info, - # memory_block=memory_block, + memory_block=memory_block, relation_info_block=relation_info, extra_info_block=extra_info_block, identity=personality_prompt, diff --git a/src/chat/replyer/prompt/replyer_prompt.py b/src/chat/replyer/prompt/replyer_prompt.py index dd1a434e..5410b6b6 100644 --- a/src/chat/replyer/prompt/replyer_prompt.py +++ b/src/chat/replyer/prompt/replyer_prompt.py @@ -13,7 +13,7 @@ def init_replyer_prompt(): Prompt( """{knowledge_prompt}{tool_info_block}{extra_info_block} -{expression_habits_block} +{expression_habits_block}{memory_block} 你正在qq群里聊天,下面是群里正在聊的内容: {time_block} @@ -34,7 +34,7 @@ def init_replyer_prompt(): Prompt( """{knowledge_prompt}{tool_info_block}{extra_info_block} -{expression_habits_block} +{expression_habits_block}{memory_block} 你正在qq群里聊天,下面是群里正在聊的内容: {time_block} @@ -55,7 +55,7 @@ def init_replyer_prompt(): Prompt( """{knowledge_prompt}{tool_info_block}{extra_info_block} -{expression_habits_block} +{expression_habits_block}{memory_block} 你正在和{sender_name}聊天,这是你们之前聊的内容: {time_block} @@ -74,7 +74,7 @@ def init_replyer_prompt(): Prompt( """{knowledge_prompt}{tool_info_block}{extra_info_block} -{expression_habits_block} +{expression_habits_block}{memory_block} 你正在和{sender_name}聊天,这是你们之前聊的内容: {time_block} diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 4c8e6ed3..006aea14 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -317,6 +317,19 @@ class Expression(BaseModel): class Meta: table_name = "expression" +class MemoryChest(BaseModel): + """ + 用于存储记忆仓库的模型 + """ + + title = TextField() # 标题 + content = TextField() # 内容 + + class Meta: + table_name = "memory_chest" + + + class GraphNodes(BaseModel): """ @@ -369,6 +382,7 @@ def create_tables(): GraphNodes, # 添加图节点表 GraphEdges, # 添加图边表 ActionRecords, # 添加 ActionRecords 到初始化列表 + MemoryChest, ] ) @@ -396,6 +410,7 @@ def initialize_database(sync_constraints=False): GraphNodes, GraphEdges, ActionRecords, # 添加 ActionRecords 到初始化列表 + MemoryChest, ] try: @@ -493,6 +508,7 @@ def sync_field_constraints(): GraphNodes, GraphEdges, ActionRecords, + MemoryChest, ] try: diff --git a/src/config/config.py b/src/config/config.py index bb34fe3f..465b9158 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -53,7 +53,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ -MMC_VERSION = "0.10.4-snapshot.1" +MMC_VERSION = "0.11.0-snapshot.1" def get_key_comment(toml_table, key): diff --git a/src/plugins/built_in/memory/build_memory.py b/src/plugins/built_in/memory/build_memory.py index e53b57fe..7b6f8971 100644 --- a/src/plugins/built_in/memory/build_memory.py +++ b/src/plugins/built_in/memory/build_memory.py @@ -3,9 +3,13 @@ from typing import Tuple from src.common.logger import get_logger 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.chat.utils.utils import cut_key_words +from src.chat.memory_system.Memory_chest import global_memory_chest +from src.plugin_system.base.base_tool import BaseTool +from typing import Any logger = get_logger("memory") @@ -66,73 +70,153 @@ def init_prompt(): ) -class BuildMemoryAction(BaseAction): - """关系动作 - 构建关系""" +# class BuildMemoryAction(BaseAction): +# """关系动作 - 构建关系""" +# activation_type = ActionActivationType.LLM_JUDGE +# parallel_action = True + +# # 动作基本信息 +# action_name = "build_memory" +# action_description = ( +# "了解对于某个概念或者某件事的记忆,并存储下来,在之后的聊天中,你可以根据这条记忆来获取相关信息" +# ) + +# # 动作参数定义 +# action_parameters = { +# "concept_name": "需要了解或记忆的概念或事件的名称", +# "concept_description": "需要了解或记忆的概念或事件的描述,需要具体且明确", +# } + +# # 动作使用场景 +# action_require = [ +# "了解对于某个概念或者某件事的记忆,并存储下来,在之后的聊天中,你可以根据这条记忆来获取相关信息", +# "有你不了解的概念", +# "有人要求你记住某个概念或者事件", +# "你对某件事或概念有新的理解,或产生了兴趣", +# ] + +# # 关联类型 +# associated_types = ["text"] + +# async def execute(self) -> Tuple[bool, str]: +# """执行关系动作""" + +# try: +# # 1. 获取构建关系的原因 +# concept_description = self.action_data.get("concept_description", "") +# logger.info(f"{self.log_prefix} 添加记忆原因: {self.reasoning}") +# concept_name = self.action_data.get("concept_name", "") +# # 2. 获取目标用户信息 + +# # 对 concept_name 进行jieba分词 +# concept_name_tokens = cut_key_words(concept_name) +# # logger.info(f"{self.log_prefix} 对 concept_name 进行分词结果: {concept_name_tokens}") + +# filtered_concept_name_tokens = [ +# token +# for token in concept_name_tokens +# if all(keyword not in token for keyword in global_config.memory.memory_ban_words) +# ] + +# if not filtered_concept_name_tokens: +# logger.warning(f"{self.log_prefix} 过滤后的概念名称列表为空,跳过添加记忆") +# return False, "过滤后的概念名称列表为空,跳过添加记忆" + +# similar_topics_dict = ( +# hippocampus_manager.get_hippocampus().parahippocampal_gyrus.get_similar_topics_from_keywords( +# filtered_concept_name_tokens +# ) +# ) +# await hippocampus_manager.get_hippocampus().parahippocampal_gyrus.add_memory_with_similar( +# concept_description, similar_topics_dict +# ) + +# return True, f"成功添加记忆: {concept_name}" + +# except Exception as e: +# logger.error(f"{self.log_prefix} 构建记忆时出错: {e}") +# return False, f"构建记忆时出错: {e}" + +class GetMemoryTool(BaseTool): + """获取用户信息""" + + name = "get_memory" + description = "在记忆中搜索,获取某个问题的答案" + parameters = [ + ("question", ToolParamType.STRING, "需要获取答案的问题", True, None) + ] + + available_for_llm = True + + async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]: + """执行比较两个数的大小 + + Args: + function_args: 工具参数 + + Returns: + dict: 工具执行结果 + """ + question: str = function_args.get("question") # type: ignore + + answer = await global_memory_chest.get_answer_by_question(question=question) + if not answer: + return {"content": f"没有找到相关记忆"} + + return {"content": f"问题:{question},答案:{answer}"} + + + +class GetMemoryAction(BaseAction): + """关系动作 - 获取记忆""" + activation_type = ActionActivationType.LLM_JUDGE parallel_action = True - - # 动作基本信息 - action_name = "build_memory" + + # 动作基本信息 + action_name = "get_memory" action_description = ( - "了解对于某个概念或者某件事的记忆,并存储下来,在之后的聊天中,你可以根据这条记忆来获取相关信息" + "在记忆中搜寻某个问题的答案" ) # 动作参数定义 action_parameters = { - "concept_name": "需要了解或记忆的概念或事件的名称", - "concept_description": "需要了解或记忆的概念或事件的描述,需要具体且明确", + "question": "需要搜寻或回答的问题", } # 动作使用场景 action_require = [ - "了解对于某个概念或者某件事的记忆,并存储下来,在之后的聊天中,你可以根据这条记忆来获取相关信息", + "在记忆中搜寻某个问题的答案", "有你不了解的概念", - "有人要求你记住某个概念或者事件", - "你对某件事或概念有新的理解,或产生了兴趣", + "有人提问关于过去的事情" + "你需要根据记忆回答某个问题", ] - + # 关联类型 associated_types = ["text"] - + async def execute(self) -> Tuple[bool, str]: """执行关系动作""" - - try: - # 1. 获取构建关系的原因 - concept_description = self.action_data.get("concept_description", "") - logger.info(f"{self.log_prefix} 添加记忆原因: {self.reasoning}") - concept_name = self.action_data.get("concept_name", "") - # 2. 获取目标用户信息 - - # 对 concept_name 进行jieba分词 - concept_name_tokens = cut_key_words(concept_name) - # logger.info(f"{self.log_prefix} 对 concept_name 进行分词结果: {concept_name_tokens}") - - filtered_concept_name_tokens = [ - token - for token in concept_name_tokens - if all(keyword not in token for keyword in global_config.memory.memory_ban_words) - ] - - if not filtered_concept_name_tokens: - logger.warning(f"{self.log_prefix} 过滤后的概念名称列表为空,跳过添加记忆") - return False, "过滤后的概念名称列表为空,跳过添加记忆" - - similar_topics_dict = ( - hippocampus_manager.get_hippocampus().parahippocampal_gyrus.get_similar_topics_from_keywords( - filtered_concept_name_tokens - ) + + question = self.action_data.get("question", "") + answer = await global_memory_chest.get_answer_by_question(self.chat_id, question) + if not answer: + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"你回忆了有关问题:{question}的记忆,但是没有找到相关记忆", + action_done=True, ) - await hippocampus_manager.get_hippocampus().parahippocampal_gyrus.add_memory_with_similar( - concept_description, similar_topics_dict - ) - - return True, f"成功添加记忆: {concept_name}" - - except Exception as e: - logger.error(f"{self.log_prefix} 构建记忆时出错: {e}") - return False, f"构建记忆时出错: {e}" + + return False, f"没有找到相关记忆" + + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"你回忆了有关问题:{question}的记忆,答案是:{answer}", + action_done=True, + ) + + return True, f"成功获取记忆: {answer}" # 还缺一个关系的太多遗忘和对应的提取 diff --git a/src/plugins/built_in/memory/plugin.py b/src/plugins/built_in/memory/plugin.py index 25f95448..ae71bf07 100644 --- a/src/plugins/built_in/memory/plugin.py +++ b/src/plugins/built_in/memory/plugin.py @@ -1,25 +1,23 @@ from typing import List, Tuple, Type # 导入新插件系统 -from src.plugin_system import BasePlugin, ComponentInfo +from src.plugin_system import BasePlugin, ComponentInfo, register_plugin from src.plugin_system.base.config_types import ConfigField # 导入依赖的系统组件 from src.common.logger import get_logger -from src.plugins.built_in.memory.build_memory import BuildMemoryAction +from src.plugins.built_in.memory.build_memory import GetMemoryAction, GetMemoryTool -logger = get_logger("relation_actions") +logger = get_logger("memory_build") -# @register_plugin +@register_plugin class MemoryBuildPlugin(BasePlugin): - """关系动作插件 + """记忆构建插件 系统内置插件,提供基础的聊天交互功能: - - Reply: 回复动作 - - NoReply: 不回复动作 - - Emoji: 表情动作 + - GetMemory: 获取记忆 注意:插件基本信息优先从_manifest.json文件中读取 """ @@ -43,9 +41,6 @@ class MemoryBuildPlugin(BasePlugin): "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"), }, - "components": { - "memory_max_memory_num": ConfigField(type=int, default=10, description="记忆最大数量"), - }, } def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: @@ -53,6 +48,7 @@ class MemoryBuildPlugin(BasePlugin): # --- 根据配置注册组件 --- components = [] - components.append((BuildMemoryAction.get_action_info(), BuildMemoryAction)) + components.append((GetMemoryAction.get_action_info(), GetMemoryAction)) + components.append((GetMemoryTool.get_tool_info(), GetMemoryTool)) return components