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/plugins/emoji_manage_plugin/plugin.py b/plugins/emoji_manage_plugin/plugin.py index 893d14b7..453afbc2 100644 --- a/plugins/emoji_manage_plugin/plugin.py +++ b/plugins/emoji_manage_plugin/plugin.py @@ -1,26 +1,19 @@ -import random -from typing import List, Tuple, Type, Any +from typing import List, Tuple, Type from src.plugin_system import ( BasePlugin, register_plugin, - BaseAction, BaseCommand, - BaseTool, ComponentInfo, - ActionActivationType, ConfigField, - BaseEventHandler, - EventType, - MaiMessages, - ToolParamType, ReplyContentType, emoji_api, ) from maim_message import Seg -from src.config.config import global_config from src.common.logger import get_logger + logger = get_logger("emoji_manage_plugin") + class AddEmojiCommand(BaseCommand): command_name = "add_emoji" command_description = "添加表情包" @@ -29,7 +22,7 @@ class AddEmojiCommand(BaseCommand): async def execute(self) -> Tuple[bool, str, bool]: # 查找消息中的表情包 # logger.info(f"查找消息中的表情包: {self.message.message_segment}") - + emoji_base64_list = self.find_and_return_emoji_in_message(self.message.message_segment) if not emoji_base64_list: @@ -51,7 +44,7 @@ class AddEmojiCommand(BaseCommand): emotions = result.get("emotions", []) replaced = result.get("replaced", False) - result_msg = f"表情包 {i+1} 注册成功{'(替换旧表情包)' if replaced else '(新增表情包)'}" + result_msg = f"表情包 {i + 1} 注册成功{'(替换旧表情包)' if replaced else '(新增表情包)'}" if description: result_msg += f"\n描述: {description}" if emotions: @@ -61,11 +54,11 @@ class AddEmojiCommand(BaseCommand): else: fail_count += 1 error_msg = result.get("message", "注册失败") - results.append(f"表情包 {i+1} 注册失败: {error_msg}") + results.append(f"表情包 {i + 1} 注册失败: {error_msg}") except Exception as e: fail_count += 1 - results.append(f"表情包 {i+1} 注册时发生错误: {str(e)}") + results.append(f"表情包 {i + 1} 注册时发生错误: {str(e)}") # 构建返回消息 total_count = success_count + fail_count @@ -140,6 +133,7 @@ class AddEmojiCommand(BaseCommand): emoji_base64_list.extend(self.find_and_return_emoji_in_message(seg.data)) return emoji_base64_list + class ListEmojiCommand(BaseCommand): """列表表情包Command - 响应/emoji list命令""" @@ -156,6 +150,7 @@ class ListEmojiCommand(BaseCommand): # 解析命令参数 import re + match = re.match(r"^/emoji list(?:\s+(\d+))?$", self.message.raw_message) max_count = 10 # 默认显示10个 if match and match.group(1): @@ -195,7 +190,7 @@ class ListEmojiCommand(BaseCommand): display_emojis = all_emojis[:max_count] message_lines.append(f"\n📋 显示前 {len(display_emojis)} 个表情包:") - for i, (emoji_base64, description, emotion) in enumerate(display_emojis, 1): + for i, (_, description, emotion) in enumerate(display_emojis, 1): # 截断过长的描述 short_desc = description[:50] + "..." if len(description) > 50 else description message_lines.append(f"{i}. {short_desc} [{emotion}]") @@ -257,7 +252,7 @@ class DeleteEmojiCommand(BaseCommand): count_after = result.get("count_after", 0) emotions = result.get("emotions", []) - result_msg = f"表情包 {i+1} 删除成功" + result_msg = f"表情包 {i + 1} 删除成功" if description: result_msg += f"\n描述: {description}" if emotions: @@ -268,11 +263,11 @@ class DeleteEmojiCommand(BaseCommand): else: fail_count += 1 error_msg = result.get("message", "删除失败") - results.append(f"表情包 {i+1} 删除失败: {error_msg}") + results.append(f"表情包 {i + 1} 删除失败: {error_msg}") except Exception as e: fail_count += 1 - results.append(f"表情包 {i+1} 删除时发生错误: {str(e)}") + results.append(f"表情包 {i + 1} 删除时发生错误: {str(e)}") # 构建返回消息 total_count = success_count + fail_count @@ -401,4 +396,4 @@ class EmojiManagePlugin(BasePlugin): (AddEmojiCommand.get_command_info(), AddEmojiCommand), (ListEmojiCommand.get_command_info(), ListEmojiCommand), (DeleteEmojiCommand.get_command_info(), DeleteEmojiCommand), - ] \ No newline at end of file + ] diff --git a/src/chat/brain_chat/brain_chat.py b/src/chat/brain_chat/brain_chat.py index 5ba077ab..36f78af4 100644 --- a/src/chat/brain_chat/brain_chat.py +++ b/src/chat/brain_chat/brain_chat.py @@ -16,7 +16,6 @@ from src.chat.brain_chat.brain_planner import BrainPlanner from src.chat.planner_actions.action_modifier import ActionModifier from src.chat.planner_actions.action_manager import ActionManager from src.chat.heart_flow.hfc_utils import CycleDetail -from src.chat.heart_flow.hfc_utils import send_typing, stop_typing from src.chat.express.expression_learner import expression_learner_manager from src.person_info.person_info import Person from src.plugin_system.base.component_types import EventType, ActionInfo @@ -96,7 +95,6 @@ class BrainChatting: self.last_read_time = time.time() - 2 self.more_plan = False - async def start(self): """检查是否需要启动主循环,如果未激活则启动。""" @@ -171,10 +169,8 @@ class BrainChatting: if len(recent_messages_list) >= 1: self.last_read_time = time.time() - await self._observe( - recent_messages_list=recent_messages_list - ) - + await self._observe(recent_messages_list=recent_messages_list) + else: # Normal模式:消息数量不足,等待 await asyncio.sleep(0.2) @@ -233,11 +229,11 @@ class BrainChatting: async def _observe( self, # interest_value: float = 0.0, - recent_messages_list: Optional[List["DatabaseMessages"]] = None + recent_messages_list: Optional[List["DatabaseMessages"]] = None, ) -> bool: # sourcery skip: merge-else-if-into-elif, remove-redundant-if if recent_messages_list is None: recent_messages_list = [] - reply_text = "" # 初始化reply_text变量,避免UnboundLocalError + _reply_text = "" # 初始化reply_text变量,避免UnboundLocalError async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): await self.expression_learner.trigger_learning_for_chat() @@ -334,7 +330,7 @@ class BrainChatting: "taken_time": time.time(), } ) - reply_text = reply_text_from_reply + _reply_text = reply_text_from_reply else: # 没有回复信息,构建纯动作的loop_info loop_info = { @@ -347,7 +343,7 @@ class BrainChatting: "taken_time": time.time(), }, } - reply_text = action_reply_text + _reply_text = action_reply_text self.end_cycle(loop_info, cycle_timers) self.print_cycle_info(cycle_timers) @@ -484,7 +480,6 @@ class BrainChatting: """执行单个动作的通用函数""" try: with Timer(f"动作{action_planner_info.action_type}", cycle_timers): - if action_planner_info.action_type == "no_reply": # 直接处理no_action逻辑,不再通过动作系统 reason = action_planner_info.reasoning or "选择不回复" @@ -517,7 +512,9 @@ class BrainChatting: if not success or not llm_response or not llm_response.reply_set: if action_planner_info.action_message: - logger.info(f"对 {action_planner_info.action_message.processed_plain_text} 的回复生成失败") + logger.info( + f"对 {action_planner_info.action_message.processed_plain_text} 的回复生成失败" + ) else: logger.info("回复生成失败") return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None} diff --git a/src/chat/brain_chat/brain_planner.py b/src/chat/brain_chat/brain_planner.py index 30627a5b..5c4f969c 100644 --- a/src/chat/brain_chat/brain_planner.py +++ b/src/chat/brain_chat/brain_planner.py @@ -307,7 +307,9 @@ class BrainPlanner: if chat_target_info: # 构建聊天上下文描述 - chat_context_description = f"你正在和 {chat_target_info.person_name or chat_target_info.user_nickname or '对方'} 聊天中" + chat_context_description = ( + f"你正在和 {chat_target_info.person_name or chat_target_info.user_nickname or '对方'} 聊天中" + ) # 构建动作选项块 action_options_block = await self._build_action_options_block(current_available_actions) diff --git a/src/chat/express/expression_learner.py b/src/chat/express/expression_learner.py index 79fa433e..bc484c93 100644 --- a/src/chat/express/expression_learner.py +++ b/src/chat/express/expression_learner.py @@ -10,11 +10,14 @@ from src.common.logger import get_logger from src.common.database.database_model import Expression from src.llm_models.utils_model import LLMRequest from src.config.config import model_config, global_config -from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive, build_anonymous_messages, build_bare_messages +from src.chat.utils.chat_message_builder import ( + get_raw_msg_by_timestamp_with_chat_inclusive, + build_anonymous_messages, + build_bare_messages, +) from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.message_receive.chat_stream import get_chat_manager from json_repair import repair_json -from src.chat.utils.utils import get_embedding MAX_EXPRESSION_COUNT = 300 @@ -99,7 +102,9 @@ class ExpressionLearner: self.last_learning_time: float = time.time() # 学习参数 - _, self.enable_learning, self.learning_intensity = global_config.expression.get_expression_config_for_chat(self.chat_id) + _, self.enable_learning, self.learning_intensity = global_config.expression.get_expression_config_for_chat( + self.chat_id + ) self.min_messages_for_learning = 15 / self.learning_intensity # 触发学习所需的最少消息数 self.min_learning_interval = 150 / self.learning_intensity @@ -237,17 +242,42 @@ class ExpressionLearner: return [] learnt_expressions = res learnt_expressions_str = "" - for _chat_id, situation, style, context, context_words, full_context, full_context_embedding in learnt_expressions: + for ( + _chat_id, + situation, + style, + _context, + _context_words, + _full_context, + _full_context_embedding, + ) in learnt_expressions: learnt_expressions_str += f"{situation}->{style}\n" - + logger.info(f"在 {self.chat_name} 学习到表达风格:\n{learnt_expressions_str}") # 按chat_id分组 chat_dict: Dict[str, List[Dict[str, Any]]] = {} - for chat_id, situation, style, context, context_words, full_context, full_context_embedding in learnt_expressions: + for ( + chat_id, + situation, + style, + context, + context_words, + full_context, + full_context_embedding, + ) in learnt_expressions: if chat_id not in chat_dict: chat_dict[chat_id] = [] - chat_dict[chat_id].append({"situation": situation, "style": style, "context": context, "context_words": context_words, "full_context": full_context, "full_context_embedding": full_context_embedding}) + chat_dict[chat_id].append( + { + "situation": situation, + "style": style, + "context": context, + "context_words": context_words, + "full_context": full_context, + "full_context_embedding": full_context_embedding, + } + ) current_time = time.time() @@ -300,11 +330,13 @@ class ExpressionLearner: expr.delete_instance() return learnt_expressions - async def match_expression_context(self, expression_pairs: List[Tuple[str, str]], random_msg_match_str: str) -> List[Tuple[str, str, str]]: + async def match_expression_context( + self, expression_pairs: List[Tuple[str, str]], random_msg_match_str: str + ) -> List[Tuple[str, str, str]]: # 为expression_pairs逐个条目赋予编号,并构建成字符串 numbered_pairs = [] for i, (situation, style) in enumerate(expression_pairs, 1): - numbered_pairs.append(f"{i}. 当\"{situation}\"时,使用\"{style}\"") + numbered_pairs.append(f'{i}. 当"{situation}"时,使用"{style}"') expression_pairs_str = "\n".join(numbered_pairs) @@ -319,20 +351,20 @@ class ExpressionLearner: print(f"match_expression_context_prompt: {prompt}") print(f"random_msg_match_str: {response}") - + # 解析JSON响应 match_responses = [] try: response = response.strip() # 检查是否已经是标准JSON数组格式 - if response.startswith('[') and response.endswith(']'): + if response.startswith("[") and response.endswith("]"): match_responses = json.loads(response) else: # 尝试直接解析多个JSON对象 try: # 如果是多个JSON对象用逗号分隔,包装成数组 - if response.startswith('{') and not response.startswith('['): - response = '[' + response + ']' + if response.startswith("{") and not response.startswith("["): + response = "[" + response + "]" match_responses = json.loads(response) else: # 使用repair_json处理响应 @@ -394,7 +426,9 @@ class ExpressionLearner: return matched_expressions - async def learn_expression(self, num: int = 10) -> Optional[List[Tuple[str, str, str, List[str], str, List[float]]]]: + async def learn_expression( + self, num: int = 10 + ) -> Optional[List[Tuple[str, str, str, List[str], str, List[float]]]]: """从指定聊天流学习表达方式 Args: @@ -416,18 +450,17 @@ class ExpressionLearner: if not random_msg or random_msg == []: return None # 转化成str - chat_id: str = random_msg[0].chat_id + _chat_id: str = random_msg[0].chat_id # random_msg_str: str = build_readable_messages(random_msg, timestamp_mode="normal") random_msg_str: str = await build_anonymous_messages(random_msg) random_msg_match_str: str = await build_bare_messages(random_msg) - prompt: str = await global_prompt_manager.format_prompt( prompt, 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: @@ -440,24 +473,31 @@ class ExpressionLearner: expressions: List[Tuple[str, str]] = self.parse_expression_response(response) - matched_expressions: List[Tuple[str, str, str]] = await self.match_expression_context(expressions, random_msg_match_str) + matched_expressions: List[Tuple[str, str, str]] = await self.match_expression_context( + expressions, random_msg_match_str + ) + + split_matched_expressions: List[Tuple[str, str, str, List[str]]] = self.split_expression_context( + matched_expressions + ) - split_matched_expressions: List[Tuple[str, str, str, List[str]]] = self.split_expression_context(matched_expressions) - split_matched_expressions_w_emb = [] full_context_embedding: List[float] = await self.get_full_context_embedding(random_msg_match_str) - - for situation, style, context, context_words in split_matched_expressions: - split_matched_expressions_w_emb.append((self.chat_id, situation, style, context, context_words, random_msg_match_str,full_context_embedding)) + for situation, style, context, context_words in split_matched_expressions: + split_matched_expressions_w_emb.append( + (self.chat_id, situation, style, context, context_words, random_msg_match_str, full_context_embedding) + ) return split_matched_expressions_w_emb - + async def get_full_context_embedding(self, context: str) -> List[float]: embedding, _ = await self.embedding_model.get_embedding(context) return embedding - - def split_expression_context(self, matched_expressions: List[Tuple[str, str, str]]) -> List[Tuple[str, str, str, List[str]]]: + + def split_expression_context( + self, matched_expressions: List[Tuple[str, str, str]] + ) -> List[Tuple[str, str, str, List[str]]]: """ 对matched_expressions中的context部分进行jieba分词 diff --git a/src/chat/express/expression_selector.py b/src/chat/express/expression_selector.py index 557ffd11..dbb40f6a 100644 --- a/src/chat/express/expression_selector.py +++ b/src/chat/express/expression_selector.py @@ -114,10 +114,10 @@ class ExpressionSelector: def get_related_chat_ids(self, chat_id: str) -> List[str]: """根据expression_groups配置,获取与当前chat_id相关的所有chat_id(包括自身)""" groups = global_config.expression.expression_groups - + # 检查是否存在全局共享组(包含"*"的组) global_group_exists = any("*" in group for group in groups) - + if global_group_exists: # 如果存在全局共享组,则返回所有可用的chat_id all_chat_ids = set() @@ -126,7 +126,7 @@ class ExpressionSelector: if chat_id_candidate := self._parse_stream_config_to_chat_id(stream_config_str): all_chat_ids.add(chat_id_candidate) return list(all_chat_ids) if all_chat_ids else [chat_id] - + # 否则使用现有的组逻辑 for group in groups: group_chat_ids = [] diff --git a/src/chat/frequency_control/frequency_control.py b/src/chat/frequency_control/frequency_control.py index 1d9b1fbb..60056c21 100644 --- a/src/chat/frequency_control/frequency_control.py +++ b/src/chat/frequency_control/frequency_control.py @@ -43,4 +43,4 @@ class FrequencyControlManager: # 创建全局实例 -frequency_control_manager = FrequencyControlManager() \ No newline at end of file +frequency_control_manager = FrequencyControlManager() diff --git a/src/chat/heart_flow/heartFC_chat.py b/src/chat/heart_flow/heartFC_chat.py index 88909b2b..105657d6 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): """检查是否需要启动主循环,如果未激活则启动。""" @@ -206,7 +208,11 @@ class HeartFChatting: # *控制频率用 if mentioned_message: await self._observe(recent_messages_list=recent_messages_list, force_reply_message=mentioned_message) - elif random.random() < global_config.chat.talk_value * frequency_control_manager.get_or_create_frequency_control(self.stream_id).get_talk_frequency_adjust(): + elif ( + random.random() + < global_config.chat.talk_value + * frequency_control_manager.get_or_create_frequency_control(self.stream_id).get_talk_frequency_adjust() + ): await self._observe(recent_messages_list=recent_messages_list) else: # 没有提到,继续保持沉默,等待5秒防止频繁触发 @@ -276,14 +282,17 @@ class HeartFChatting: recent_messages_list = [] reply_text = "" # 初始化reply_text变量,避免UnboundLocalError - start_time = time.time() - + if s4u_config.enable_s4u: await send_typing() 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}次思考") @@ -350,7 +359,7 @@ class HeartFChatting: available_actions=available_actions, ) ) - + logger.info( f"{self.log_prefix} 决定执行{len(action_to_use_info)}个动作: {' '.join([a.action_type for a in action_to_use_info])}" ) @@ -412,7 +421,7 @@ class HeartFChatting: }, } reply_text = action_reply_text - + self.end_cycle(loop_info, cycle_timers) self.print_cycle_info(cycle_timers) @@ -423,11 +432,6 @@ class HeartFChatting: else: await asyncio.sleep(0.1) - - - - - """S4U内容,暂时保留""" if s4u_config.enable_s4u: await stop_typing() diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index 54c92b35..fdc13e2f 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -1,10 +1,8 @@ -import asyncio import re import traceback from typing import Tuple, TYPE_CHECKING -from src.config.config import global_config from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.storage import MessageStorage from src.chat.heart_flow.heartflow import heartflow @@ -74,7 +72,7 @@ class HeartFCMessageReceiver: await self.storage.store_message(message, chat) - heartflow_chat: HeartFChatting = await heartflow.get_or_create_heartflow_chat(chat.stream_id) # type: ignore + _heartflow_chat: HeartFChatting = await heartflow.get_or_create_heartflow_chat(chat.stream_id) # type: ignore # 3. 日志记录 mes_name = chat.group_info.group_name if chat.group_info else "私聊" @@ -102,7 +100,7 @@ class HeartFCMessageReceiver: replace_bot_name=True, ) # if not processed_plain_text: - # print(message) + # print(message) logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}") # type: ignore 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/message_receive/bot.py b/src/chat/message_receive/bot.py index d963779b..89e9bb5a 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -8,7 +8,7 @@ from maim_message import UserInfo, Seg, GroupInfo from src.common.logger import get_logger from src.config.config import global_config from src.mood.mood_manager import mood_manager # 导入情绪管理器 -from src.chat.message_receive.chat_stream import get_chat_manager, ChatStream +from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.message import MessageRecv, MessageRecvS4U from src.chat.message_receive.storage import MessageStorage from src.chat.heart_flow.heartflow_message_processor import HeartFCMessageReceiver diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 46967075..10bb53dc 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}, @@ -343,7 +343,6 @@ class ActionPlanner: interest=interest, plan_style=global_config.personality.plan_style, ) - return prompt, message_id_list except Exception as e: @@ -421,6 +420,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 +433,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 @@ -502,9 +507,7 @@ class ActionPlanner: action.action_data = action.action_data or {} action.action_data["loop_start_time"] = loop_start_time - logger.debug( - f"{self.log_prefix}规划器选择了{len(actions)}个动作: {' '.join([a.action_type for a in actions])}" - ) + logger.debug(f"{self.log_prefix}规划器选择了{len(actions)}个动作: {' '.join([a.action_type for a in actions])}") return actions diff --git a/src/chat/replyer/group_generator.py b/src/chat/replyer/group_generator.py index 87bfc670..75b0998c 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 @@ -27,7 +28,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.person_info.person_info import Person, is_person_known +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 @@ -42,6 +43,7 @@ init_rewrite_prompt() logger = get_logger("replyer") + class DefaultReplyer: def __init__( self, @@ -215,7 +217,7 @@ class DefaultReplyer: traceback.print_exc() return False, llm_response -#移动到 relation插件中构建 + # 移动到 relation插件中构建 # async def build_relation_info(self, chat_content: str, sender: str, person_list: List[Person]): # if not global_config.relationship.enable_relationship: # return "" @@ -277,9 +279,7 @@ class DefaultReplyer: expression_habits_block = "" expression_habits_title = "" if style_habits_str.strip(): - expression_habits_title = ( - "在回复时,你可以参考以下的语言习惯,不要生硬使用:" - ) + expression_habits_title = "在回复时,你可以参考以下的语言习惯,不要生硬使用:" expression_habits_block += f"{style_habits_str}\n" return f"{expression_habits_title}\n{expression_habits_block}", selected_ids @@ -315,6 +315,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: """构建工具信息块 @@ -498,7 +509,6 @@ class DefaultReplyer: -------------------------------- """ - # 构建背景对话 prompt all_dialogue_prompt = "" if message_list_before_now: @@ -524,7 +534,6 @@ class DefaultReplyer: time_block: str, chat_target_1: str, chat_target_2: str, - identity_block: str, sender: str, target: str, @@ -701,6 +710,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 +724,7 @@ class DefaultReplyer: "expression_habits": "选取表达方式", "relation_info": "感受关系", # "memory_block": "回忆", + "memory_block": "记忆", "tool_info": "使用工具", "prompt_info": "获取知识", "actions_info": "动作信息", @@ -742,6 +753,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"] @@ -759,13 +771,9 @@ class DefaultReplyer: if sender: if is_group_chat: - reply_target_block = ( - f"现在{sender}说的:{target}。引起了你的注意" - ) + reply_target_block = f"现在{sender}说的:{target}。引起了你的注意" else: # private chat - reply_target_block = ( - f"现在{sender}说的:{target}。引起了你的注意" - ) + reply_target_block = f"现在{sender}说的:{target}。引起了你的注意" else: reply_target_block = "" @@ -779,6 +787,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 +807,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 +956,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") @@ -1044,6 +1054,3 @@ def weighted_sample_no_replacement(items, weights, k) -> list: pool.pop(idx) break return selected - - - 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/lpmm_prompt.py b/src/chat/replyer/prompt/lpmm_prompt.py index d5d02664..afce7f5e 100644 --- a/src/chat/replyer/prompt/lpmm_prompt.py +++ b/src/chat/replyer/prompt/lpmm_prompt.py @@ -1,9 +1,7 @@ - from src.chat.utils.prompt_builder import Prompt # from src.chat.memory_system.memory_activator import MemoryActivator - def init_lpmm_prompt(): Prompt( """ @@ -20,5 +18,3 @@ If you need to use the search tool, please directly call the function "lpmm_sear """, name="lpmm_get_knowledge_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/chat/replyer/prompt/rewrite_prompt.py b/src/chat/replyer/prompt/rewrite_prompt.py index 3118ae88..b96d8cd4 100644 --- a/src/chat/replyer/prompt/rewrite_prompt.py +++ b/src/chat/replyer/prompt/rewrite_prompt.py @@ -1,9 +1,7 @@ - from src.chat.utils.prompt_builder import Prompt # from src.chat.memory_system.memory_activator import MemoryActivator - def init_rewrite_prompt(): Prompt("你正在qq群里聊天,下面是群里正在聊的内容:", "chat_target_group1") Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") @@ -31,4 +29,4 @@ def init_rewrite_prompt(): 现在,你说: """, "default_expressor_prompt", - ) \ No newline at end of file + ) diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 5488dc9f..b02a9164 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -859,7 +859,6 @@ async def build_anonymous_messages(messages: List[DatabaseMessages]) -> str: # 处理图片ID content = process_pic_ids(content) - anon_name = get_anon_name(platform, user_id) # print(f"anon_name:{anon_name}") @@ -945,11 +944,12 @@ async def build_bare_messages(messages: List[DatabaseMessages]) -> str: # 获取纯文本内容 content = msg.processed_plain_text or "" - # 处理图片ID pic_pattern = r"\[picid:[^\]]+\]" + def replace_pic_id(match): return "[图片]" + content = re.sub(pic_pattern, replace_pic_id, content) # 处理用户引用格式,移除回复和@标记 diff --git a/src/common/data_models/llm_data_model.py b/src/common/data_models/llm_data_model.py index e8d57b41..2d2ee0c3 100644 --- a/src/common/data_models/llm_data_model.py +++ b/src/common/data_models/llm_data_model.py @@ -16,4 +16,4 @@ class LLMGenerationDataModel(BaseDataModel): tool_calls: Optional[List["ToolCall"]] = None prompt: Optional[str] = None selected_expressions: Optional[List[int]] = None - reply_set: Optional["ReplySetModel"] = None \ No newline at end of file + reply_set: Optional["ReplySetModel"] = None diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 4c8e6ed3..1cdd5016 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: @@ -732,11 +748,14 @@ def check_field_constraints(): logger.exception(f"检查字段约束时出错: {e}") return inconsistencies + + def fix_image_id(): """ 修复表情包的 image_id 字段 """ import uuid + try: with db: for img in Images.select(): @@ -747,6 +766,7 @@ def fix_image_id(): except Exception as e: logger.exception(f"修复 image_id 时出错: {e}") + # 模块加载时调用初始化函数 initialize_database(sync_constraints=True) -fix_image_id() \ No newline at end of file +fix_image_id() 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/config/official_configs.py b/src/config/official_configs.py index b61a234a..87bb4d97 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -46,13 +46,13 @@ class PersonalityConfig(ConfigBase): interest: str = "" """兴趣""" - + plan_style: str = "" """说话规则,行为风格""" - + visual_style: str = "" """图片提示词""" - + private_plan_style: str = "" """私聊说话规则,行为风格""" @@ -86,7 +86,7 @@ class ChatConfig(ConfigBase): planner_smooth: float = 3 """规划器平滑,增大数值会减小planner负荷,略微降低反应速度,推荐2-5,0为关闭,必须大于等于0""" - + talk_value: float = 1 """思考频率""" @@ -302,6 +302,7 @@ class EmojiConfig(ConfigBase): filtration_prompt: str = "符合公序良俗" """表情包过滤要求""" + @dataclass class KeywordRuleConfig(ConfigBase): """关键词规则配置类""" diff --git a/src/llm_models/exceptions.py b/src/llm_models/exceptions.py index bf1c88de..65e49f74 100644 --- a/src/llm_models/exceptions.py +++ b/src/llm_models/exceptions.py @@ -85,4 +85,4 @@ class ModelAttemptFailed(Exception): self.original_exception = original_exception def __str__(self): - return self.message \ No newline at end of file + return self.message diff --git a/src/llm_models/model_client/gemini_client.py b/src/llm_models/model_client/gemini_client.py index 2a95f765..be47f9be 100644 --- a/src/llm_models/model_client/gemini_client.py +++ b/src/llm_models/model_client/gemini_client.py @@ -192,7 +192,7 @@ def _process_delta( elif getattr(p, "text", None): # 正常输出写入 buffer fc_delta_buffer.write(p.text) - + if delta.function_calls: # 为什么不用hasattr呢,是因为这个属性一定有,即使是个空的 for call in delta.function_calls: try: @@ -396,10 +396,7 @@ def _default_normal_response_parser( " 可能会对回复内容造成影响,建议修改模型 max_tokens 配置!" ) else: - logger.warning( - "⚠ Gemini 响应因达到 max_tokens 限制被截断,\n" - " 请修改模型 max_tokens 配置!" - ) + logger.warning("⚠ Gemini 响应因达到 max_tokens 限制被截断,\n 请修改模型 max_tokens 配置!") return api_response, _usage_record except Exception as e: @@ -456,7 +453,7 @@ class GeminiClient(BaseClient): logger.warning( f"无效的 thinking_budget 值 {extra_params['thinking_budget']},将使用模型自动预算模式 {tb}" ) - + # 优先尝试精确匹配 if model_id in THINKING_BUDGET_LIMITS: limits = THINKING_BUDGET_LIMITS[model_id] @@ -541,7 +538,7 @@ class GeminiClient(BaseClient): tools = _convert_tool_options(tool_options) if tool_options else None # 解析并裁剪 thinking_budget tb = self.clamp_thinking_budget(extra_params, model_info.model_identifier) - + # 将response_format转换为Gemini API所需的格式 generation_config_dict = { "max_output_tokens": max_tokens, diff --git a/src/llm_models/model_client/openai_client.py b/src/llm_models/model_client/openai_client.py index 92632222..148ec8cb 100644 --- a/src/llm_models/model_client/openai_client.py +++ b/src/llm_models/model_client/openai_client.py @@ -487,7 +487,7 @@ class OpenaiClient(BaseClient): req_task.cancel() raise ReqAbortException("请求被外部信号中断") await asyncio.sleep(0.1) # 等待0.5秒后再次检查任务&中断信号量状态 - + # logger. logger.debug(f"OpenAI API响应(非流式): {req_task.result()}") @@ -511,7 +511,7 @@ class OpenaiClient(BaseClient): ) # logger.debug(f"OpenAI API响应: {resp}") - + return resp async def get_embedding( diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py index 78631d6b..4d7865d9 100644 --- a/src/llm_models/utils_model.py +++ b/src/llm_models/utils_model.py @@ -149,7 +149,7 @@ class LLMRequest: logger.debug(f"LLM请求总耗时: {time.time() - start_time}") logger.debug(f"LLM生成内容: {response}") - + content = response.content reasoning_content = response.reasoning_content or "" tool_calls = response.tool_calls diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index d0832d18..3978d738 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -44,7 +44,7 @@ def init_prompt(): """, "get_mood_prompt", ) - + Prompt( """ {chat_talking_prompt} @@ -103,9 +103,7 @@ class ChatMood: if random.random() > update_probability: return - logger.debug( - f"{self.log_prefix} 更新情绪状态,更新概率: {update_probability:.2f}" - ) + logger.debug(f"{self.log_prefix} 更新情绪状态,更新概率: {update_probability:.2f}") message_time: float = message.message_info.time # type: ignore message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( @@ -154,12 +152,12 @@ class ChatMood: self.mood_state = response self.last_change_time = message_time - + async def get_mood(self) -> str: self.regression_count = 0 current_time = time.time() - + logger.info(f"{self.log_prefix} 获取情绪状态") message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( chat_id=self.chat_id, @@ -207,7 +205,7 @@ class ChatMood: self.mood_state = response self.last_change_time = current_time - + return response async def regress_mood(self): diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index f4aeecc7..7793da31 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -17,7 +17,9 @@ from src.config.config import global_config, model_config logger = get_logger("person_info") -relation_selection_model = LLMRequest(model_set=model_config.model_task_config.utils_small, request_type="relation_selection") +relation_selection_model = LLMRequest( + model_set=model_config.model_task_config.utils_small, request_type="relation_selection" +) def get_person_id(platform: str, user_id: Union[int, str]) -> str: @@ -91,9 +93,10 @@ def extract_categories_from_response(response: str) -> list[str]: """从response中提取所有<>包裹的内容""" if not isinstance(response, str): return [] - + import re - pattern = r'<([^<>]+)>' + + pattern = r"<([^<>]+)>" matches = re.findall(pattern, response) return matches @@ -420,7 +423,7 @@ class Person: except Exception as e: logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}") - async def build_relationship(self,chat_content:str = "",info_type = ""): + async def build_relationship(self, chat_content: str = "", info_type=""): if not self.is_known: return "" # 构建points文本 @@ -433,7 +436,7 @@ class Person: points_text = "" category_list = self.get_all_category() - + if chat_content: prompt = f"""当前聊天内容: {chat_content} @@ -449,11 +452,13 @@ class Person: # print(prompt) # print(response) category_list = extract_categories_from_response(response) - if "none" not in category_list: + if "none" not in category_list: for category in category_list: random_memory = self.get_random_memory_by_category(category, 2) if random_memory: - random_memory_str = "\n".join([get_memory_content_from_memory(memory) for memory in random_memory]) + random_memory_str = "\n".join( + [get_memory_content_from_memory(memory) for memory in random_memory] + ) points_text = f"有关 {category} 的内容:{random_memory_str}" break elif info_type: @@ -469,15 +474,16 @@ class Person: # print(prompt) # print(response) category_list = extract_categories_from_response(response) - if "none" not in category_list: + if "none" not in category_list: for category in category_list: random_memory = self.get_random_memory_by_category(category, 3) if random_memory: - random_memory_str = "\n".join([get_memory_content_from_memory(memory) for memory in random_memory]) + random_memory_str = "\n".join( + [get_memory_content_from_memory(memory) for memory in random_memory] + ) points_text = f"有关 {category} 的内容:{random_memory_str}" break else: - for category in category_list: random_memory = self.get_random_memory_by_category(category, 1)[0] if random_memory: diff --git a/src/plugin_system/apis/emoji_api.py b/src/plugin_system/apis/emoji_api.py index 6d142a9e..e1661887 100644 --- a/src/plugin_system/apis/emoji_api.py +++ b/src/plugin_system/apis/emoji_api.py @@ -12,7 +12,6 @@ import random import base64 import os import uuid -import time from typing import Optional, Tuple, List, Dict, Any from src.common.logger import get_logger @@ -358,7 +357,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": None, "emotions": None, "replaced": None, - "hash": None + "hash": None, } # 3. 确保emoji目录存在 @@ -368,19 +367,21 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D if not filename: # 基于时间戳、微秒和短base64生成唯一文件名 import time + timestamp = int(time.time()) microseconds = int(time.time() * 1000000) % 1000000 # 添加微秒级精度 # 生成12位随机标识符,使用base64编码(增加随机性) import random - random_bytes = random.getrandbits(72).to_bytes(9, 'big') # 72位 = 9字节 = 12位base64 - short_id = base64.b64encode(random_bytes).decode('ascii')[:12].rstrip('=') + + random_bytes = random.getrandbits(72).to_bytes(9, "big") # 72位 = 9字节 = 12位base64 + short_id = base64.b64encode(random_bytes).decode("ascii")[:12].rstrip("=") # 确保base64编码适合文件名(替换/和-) - short_id = short_id.replace('/', '_').replace('+', '-') + short_id = short_id.replace("/", "_").replace("+", "-") filename = f"emoji_{timestamp}_{microseconds}_{short_id}" # 确保文件名有扩展名 - if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): + if not filename.lower().endswith((".jpg", ".jpeg", ".png", ".gif")): filename = f"{filename}.png" # 默认使用png格式 # 检查文件名是否已存在,如果存在则重新生成短标识符 @@ -390,14 +391,15 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D while os.path.exists(temp_file_path) and attempts < max_attempts: # 重新生成短标识符 import random - random_bytes = random.getrandbits(48).to_bytes(6, 'big') - short_id = base64.b64encode(random_bytes).decode('ascii')[:8].rstrip('=') - short_id = short_id.replace('/', '_').replace('+', '-') + + random_bytes = random.getrandbits(48).to_bytes(6, "big") + short_id = base64.b64encode(random_bytes).decode("ascii")[:8].rstrip("=") + short_id = short_id.replace("/", "_").replace("+", "-") # 分离文件名和扩展名,重新生成文件名 name_part, ext = os.path.splitext(filename) # 去掉原来的标识符,添加新的 - base_name = name_part.rsplit('_', 1)[0] # 移除最后一个_后的部分 + base_name = name_part.rsplit("_", 1)[0] # 移除最后一个_后的部分 filename = f"{base_name}_{short_id}{ext}" temp_file_path = os.path.join(EMOJI_DIR, filename) attempts += 1 @@ -406,7 +408,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D if os.path.exists(temp_file_path): uuid_short = str(uuid.uuid4())[:8] name_part, ext = os.path.splitext(filename) - base_name = name_part.rsplit('_', 1)[0] + base_name = name_part.rsplit("_", 1)[0] filename = f"{base_name}_{uuid_short}{ext}" temp_file_path = os.path.join(EMOJI_DIR, filename) @@ -428,7 +430,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": None, "emotions": None, "replaced": None, - "hash": None + "hash": None, } # 5. 保存base64图片到emoji目录 @@ -443,7 +445,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": None, "emotions": None, "replaced": None, - "hash": None + "hash": None, } logger.debug(f"[EmojiAPI] 图片已保存到临时文件: {temp_file_path}") @@ -456,7 +458,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": None, "emotions": None, "replaced": None, - "hash": None + "hash": None, } # 6. 调用注册方法 @@ -483,8 +485,8 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D # 通过文件名查找新注册的表情包(注意:文件名在注册后可能已经改变) for emoji_obj in reversed(emoji_manager.emoji_objects): if not emoji_obj.is_deleted and ( - emoji_obj.filename == filename or # 直接匹配 - (hasattr(emoji_obj, 'full_path') and filename in emoji_obj.full_path) # 路径包含匹配 + emoji_obj.filename == filename # 直接匹配 + or (hasattr(emoji_obj, "full_path") and filename in emoji_obj.full_path) # 路径包含匹配 ): new_emoji_info = emoji_obj break @@ -501,7 +503,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": description, "emotions": emotions, "replaced": replaced, - "hash": emoji_hash + "hash": emoji_hash, } else: return { @@ -510,7 +512,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": None, "emotions": None, "replaced": None, - "hash": None + "hash": None, } except Exception as e: @@ -521,7 +523,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D "description": None, "emotions": None, "replaced": None, - "hash": None + "hash": None, } @@ -585,16 +587,16 @@ async def delete_emoji(emoji_hash: str) -> Dict[str, Any]: "count_before": count_before, "count_after": count_after, "description": description, - "emotions": emotions + "emotions": emotions, } else: return { "success": False, - "message": f"表情包删除失败,可能因为哈希值不存在或删除过程出错", + "message": "表情包删除失败,可能因为哈希值不存在或删除过程出错", "count_before": count_before, "count_after": count_after, "description": None, - "emotions": None + "emotions": None, } except Exception as e: @@ -605,7 +607,7 @@ async def delete_emoji(emoji_hash: str) -> Dict[str, Any]: "count_before": None, "count_after": None, "description": None, - "emotions": None + "emotions": None, } @@ -659,7 +661,7 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals "message": f"未找到匹配描述 '{description}' 的表情包", "deleted_count": 0, "deleted_hashes": [], - "matched_count": 0 + "matched_count": 0, } # 删除匹配的表情包 @@ -681,7 +683,7 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals "message": f"成功删除 {deleted_count} 个表情包 (匹配到 {matched_count} 个)", "deleted_count": deleted_count, "deleted_hashes": deleted_hashes, - "matched_count": matched_count + "matched_count": matched_count, } else: return { @@ -689,7 +691,7 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals "message": f"匹配到 {matched_count} 个表情包,但删除全部失败", "deleted_count": 0, "deleted_hashes": [], - "matched_count": matched_count + "matched_count": matched_count, } except Exception as e: @@ -699,5 +701,5 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals "message": f"删除过程中发生错误: {str(e)}", "deleted_count": 0, "deleted_hashes": [], - "matched_count": 0 + "matched_count": 0, } diff --git a/src/plugin_system/apis/frequency_api.py b/src/plugin_system/apis/frequency_api.py index 51d10a09..68c1fa5a 100644 --- a/src/plugin_system/apis/frequency_api.py +++ b/src/plugin_system/apis/frequency_api.py @@ -3,13 +3,14 @@ from src.chat.frequency_control.frequency_control import frequency_control_manag logger = get_logger("frequency_api") + def get_current_talk_frequency(chat_id: str) -> float: return frequency_control_manager.get_or_create_frequency_control(chat_id).get_talk_frequency_adjust() + def set_talk_frequency_adjust(chat_id: str, talk_frequency_adjust: float) -> None: - frequency_control_manager.get_or_create_frequency_control( - chat_id - ).set_talk_frequency_adjust(talk_frequency_adjust) + frequency_control_manager.get_or_create_frequency_control(chat_id).set_talk_frequency_adjust(talk_frequency_adjust) + def get_talk_frequency_adjust(chat_id: str) -> float: return frequency_control_manager.get_or_create_frequency_control(chat_id).get_talk_frequency_adjust() diff --git a/src/plugin_system/apis/mood_api.py b/src/plugin_system/apis/mood_api.py index 10094021..d5300e9f 100644 --- a/src/plugin_system/apis/mood_api.py +++ b/src/plugin_system/apis/mood_api.py @@ -1,9 +1,6 @@ import asyncio -import traceback -import time -from typing import Optional, Union, Dict, List, TYPE_CHECKING, Tuple +from typing import Optional -from src.chat.message_receive import message from src.common.logger import get_logger from src.mood.mood_manager import mood_manager @@ -12,5 +9,5 @@ logger = get_logger("mood_api") async def get_mood_by_chat_id(chat_id: str) -> Optional[float]: chat_mood = mood_manager.get_mood_by_chat_id(chat_id) - mood = asyncio.create_task(chat_mood.get_mood()) - return mood \ No newline at end of file + mood = asyncio.create_task(chat_mood.get_mood()) + return mood diff --git a/src/plugin_system/apis/send_api.py b/src/plugin_system/apis/send_api.py index e73504a4..fd2b723f 100644 --- a/src/plugin_system/apis/send_api.py +++ b/src/plugin_system/apis/send_api.py @@ -363,7 +363,7 @@ async def custom_reply_set_to_stream( ) -> bool: """ 向指定流发送混合型消息集 - + Args: reply_set: ReplySetModel 对象,包含多个 ReplyContent stream_id: 聊天流ID @@ -451,7 +451,9 @@ def _parse_content_to_seg(reply_content: "ReplyContent") -> Tuple[Seg, bool]: single_node_content.append(sub_seg) message_segment = Seg(type="seglist", data=single_node_content) forward_message_list.append( - MessageBase(message_segment=message_segment, message_info=BaseMessageInfo(user_info=user_info)).to_dict() + MessageBase( + message_segment=message_segment, message_info=BaseMessageInfo(user_info=user_info) + ).to_dict() ) return Seg(type="forward", data=forward_message_list), False # type: ignore else: diff --git a/src/plugin_system/core/tool_use.py b/src/plugin_system/core/tool_use.py index 56768a33..5c8b0033 100644 --- a/src/plugin_system/core/tool_use.py +++ b/src/plugin_system/core/tool_use.py @@ -91,7 +91,7 @@ class ToolExecutor: # 缓存未命中,执行工具调用 # 获取可用工具 tools = self._get_tool_definitions() - + # print(f"tools: {tools}") # 获取当前时间 diff --git a/src/plugins/built_in/emoji_plugin/emoji.py b/src/plugins/built_in/emoji_plugin/emoji.py index 63a9cc51..91f3ff0e 100644 --- a/src/plugins/built_in/emoji_plugin/emoji.py +++ b/src/plugins/built_in/emoji_plugin/emoji.py @@ -48,7 +48,7 @@ class EmojiAction(BaseAction): # 1. 获取发送表情的原因 # reason = self.action_data.get("reason", "表达当前情绪") reason = self.reasoning - + # 2. 随机获取20个表情包 sampled_emojis = await emoji_api.get_random(30) if not sampled_emojis: 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 diff --git a/src/plugins/built_in/relation/plugin.py b/src/plugins/built_in/relation/plugin.py index 500dae39..577eb94c 100644 --- a/src/plugins/built_in/relation/plugin.py +++ b/src/plugins/built_in/relation/plugin.py @@ -14,7 +14,6 @@ from src.plugins.built_in.relation.relation import BuildRelationAction logger = get_logger("relation_actions") - class GetPersonInfoTool(BaseTool): """获取用户信息""" @@ -24,7 +23,7 @@ class GetPersonInfoTool(BaseTool): ("person_name", ToolParamType.STRING, "需要获取信息的人的名称", True, None), ("info_type", ToolParamType.STRING, "需要获取信息的类型", True, None), ] - + available_for_llm = True async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]: @@ -44,7 +43,7 @@ class GetPersonInfoTool(BaseTool): return {"content": f"用户 {person_name} 不存在"} if not person.is_known: return {"content": f"不认识用户 {person_name}"} - + relation_str = await person.build_relationship(info_type=info_type) return {"content": relation_str}