From 788de7045010805131a8d0cead6bf2b5addca91c Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 11 Mar 2026 14:49:37 +0800 Subject: [PATCH] =?UTF-8?q?remove=EF=BC=9A=E7=A7=BB=E9=99=A4=E5=81=9A?= =?UTF-8?q?=E6=A2=A6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prompts/dream_react_head.prompt | 45 -- prompts/dream_summary.prompt | 13 - src/config/config.py | 4 - src/config/official_configs.py | 73 --- src/dream/dream_agent.py | 552 ------------------ src/dream/dream_generator.py | 210 ------- src/dream/tools/__init__.py | 6 - src/dream/tools/create_chat_history_tool.py | 65 --- src/dream/tools/delete_chat_history_tool.py | 25 - src/dream/tools/delete_jargon_tool.py | 25 - src/dream/tools/finish_maintenance_tool.py | 16 - .../tools/get_chat_history_detail_tool.py | 43 -- src/dream/tools/search_chat_history_tool.py | 214 ------- src/dream/tools/search_jargon_tool.py | 102 ---- src/dream/tools/update_chat_history_tool.py | 48 -- src/dream/tools/update_jargon_tool.py | 51 -- src/main.py | 2 - src/services/__init__.py | 2 +- 18 files changed, 1 insertion(+), 1495 deletions(-) delete mode 100644 prompts/dream_react_head.prompt delete mode 100644 prompts/dream_summary.prompt delete mode 100644 src/dream/dream_agent.py delete mode 100644 src/dream/dream_generator.py delete mode 100644 src/dream/tools/__init__.py delete mode 100644 src/dream/tools/create_chat_history_tool.py delete mode 100644 src/dream/tools/delete_chat_history_tool.py delete mode 100644 src/dream/tools/delete_jargon_tool.py delete mode 100644 src/dream/tools/finish_maintenance_tool.py delete mode 100644 src/dream/tools/get_chat_history_detail_tool.py delete mode 100644 src/dream/tools/search_chat_history_tool.py delete mode 100644 src/dream/tools/search_jargon_tool.py delete mode 100644 src/dream/tools/update_chat_history_tool.py delete mode 100644 src/dream/tools/update_jargon_tool.py diff --git a/prompts/dream_react_head.prompt b/prompts/dream_react_head.prompt deleted file mode 100644 index 64952254..00000000 --- a/prompts/dream_react_head.prompt +++ /dev/null @@ -1,45 +0,0 @@ -你的名字是{bot_name},你现在处于"梦境维护模式(dream agent)"。 -你可以自由地在 ChatHistory 库中探索、整理、创建和删改记录,以帮助自己在未来更好地回忆和理解对话历史。 - -本轮要维护的聊天ID:{chat_id} -本轮随机选中的起始记忆 ID:{start_memory_id} -请优先以这条起始记忆为切入点,先理解它的内容与上下文,再决定如何在其附近进行创建新概括、重写或删除等整理操作;如果起始记忆为空,则由你自行选择合适的切入点。 - -你可以使用的工具包括: -**ChatHistory 维护工具:** -- search_chat_history:根据关键词或参与人搜索该 chat_id 下的历史记忆概括列表 -- get_chat_history_detail:查看某条概括的详细内容 -- create_chat_history:根据整理后的理解创建一条新的 ChatHistory 概括记录(主题、概括、关键词、关键信息等) -- update_chat_history:在不改变事实的前提下重写或精炼主题、概括、关键词、关键信息 -- delete_chat_history:删除明显冗余、噪声、错误或无意义的记录,或者非常有时效性的信息,或者无太多有用信息的日常互动。 -你也可以先用 create_chat_history 创建一条新的综合概括,再对旧的冗余记录执行多次 delete_chat_history 来完成“合并”效果。 - -**Jargon(黑话)维护工具(只读,禁止修改):** -- search_jargon:根据一个或多个关键词搜索Jargon 记录,通常是含义不明确的词条或者特殊的缩写 - -**通用工具:** -- finish_maintenance:当你认为当前维护工作已经完成,没有更多需要整理的内容时,调用此工具来结束本次运行 - -**工作目标**: -- 发现冗余、重复或高度相似的记录,并进行合并或删除; -- 发现主题/概括过于含糊、啰嗦或缺少关键信息的记录,进行重写和精简; -- summary要尽可能保持有用的信息; -- 尽量保持信息的真实与可用性,不要凭空捏造事实。 - -**合并准则** -- 你可以新建一个记录,然后删除旧记录来实现合并。 -- 如果两个或多个记录的主题相似,内容是对主题不同方面的信息或讨论,且信息量较少,则可以合并为一条记录。 -- 如果两个记录冲突,可以根据逻辑保留一个或者进行整合,也可以采取更新的记录,删除旧的记录 - -**轮次信息**: -- 本次维护最多执行 {max_iterations} 轮 -- 每轮开始时,系统会告知你当前是第几轮,还剩多少轮 -- 如果提前完成维护工作,可以调用 finish_maintenance 工具主动结束 - -**每一轮的执行方式(必须遵守):** -- 第一步:先用一小段中文自然语言,写出你的「思考」和本轮计划(例如要查什么、准备怎么合并/修改); -- 第二步:在这段思考之后,再通过工具调用来执行你的计划(可以调用 0~N 个工具); -- 第三步:收到工具结果后,在下一轮继续先写出新的思考,再视情况继续调用工具。 - -请不要在没有先写出思考的情况下直接调用工具。 -只输出你的思考内容或工具调用结果,由系统负责真正执行工具调用。 \ No newline at end of file diff --git a/prompts/dream_summary.prompt b/prompts/dream_summary.prompt deleted file mode 100644 index 7e4750fe..00000000 --- a/prompts/dream_summary.prompt +++ /dev/null @@ -1,13 +0,0 @@ -你刚刚完成了一次对聊天记录的记忆整理工作。以下是整理过程的摘要: -整理过程: -{conversation_text} - -请将这次整理涉及的相关信息改写为一个富有诗意和想象力的"梦境",请你仅使用具体的记忆的内容,而不是整理过程编写。 -要求: -1. 使用第一人称视角 -2. 叙述直白,不要复杂修辞,口语化 -3. 长度控制在200-800字 -4. 用中文输出 -梦境风格: -{dream_styles} -请直接输出梦境内容,不要添加其他说明: \ No newline at end of file diff --git a/src/config/config.py b/src/config/config.py index a30fa778..6797b0a0 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -29,7 +29,6 @@ from .official_configs import ( VoiceConfig, MemoryConfig, DebugConfig, - DreamConfig, WebUIConfig, DatabaseConfig, ) @@ -86,9 +85,6 @@ class Config(ConfigBase): message_receive: MessageReceiveConfig = Field(default_factory=MessageReceiveConfig) """消息接收配置类""" - dream: DreamConfig = Field(default_factory=DreamConfig) - """做梦配置类""" - tool: ToolConfig = Field(default_factory=ToolConfig) """工具配置类""" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index f5f3afb5..22967893 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -1440,79 +1440,6 @@ class LPMMKnowledgeConfig(ConfigBase): """是否启用PPR,低配机器可关闭""" -class DreamConfig(ConfigBase): - """Dream配置类""" - - __ui_label__ = "做梦" - __ui_icon__ = "moon" - - interval_minutes: int = Field( - default=30, - ge=1, - json_schema_extra={ - "x-widget": "input", - "x-icon": "clock", - }, - ) - """做梦时间间隔(分钟),默认30分钟""" - - max_iterations: int = Field( - default=20, - ge=1, - json_schema_extra={ - "x-widget": "input", - "x-icon": "hash", - }, - ) - """做梦最大轮次,默认20轮""" - - first_delay_seconds: int = Field( - default=1800, - ge=0, - json_schema_extra={ - "x-widget": "input", - "x-icon": "timer", - }, - ) - """程序启动后首次做梦前的延迟时间(秒),默认1800秒""" - - dream_send: str = Field( - default="", - json_schema_extra={ - "x-widget": "input", - "x-icon": "send", - }, - ) - """做梦结果推送目标,格式为 "platform:user_id,为空则不发送""" - - dream_time_ranges: list[str] = Field( - default_factory=lambda: ["23:00-10:00"], - json_schema_extra={ - "x-widget": "custom", - "x-icon": "moon", - }, - ) - """_wrap_做梦时间段配置列表""" - - dream_visible: bool = Field( - default=False, - json_schema_extra={ - "x-widget": "switch", - "x-icon": "eye", - }, - ) - """做梦结果发送后是否存储到上下文""" - - def model_post_init(self, context: Optional[dict] = None) -> None: - if self.interval_minutes < 1: - raise ValueError(f"interval_minutes 必须至少为1,当前值: {self.interval_minutes}") - if self.max_iterations < 1: - raise ValueError(f"max_iterations 必须至少为1,当前值: {self.max_iterations}") - if self.first_delay_seconds < 0: - raise ValueError(f"first_delay_seconds 不能为负数,当前值: {self.first_delay_seconds}") - return super().model_post_init(context) - - class WebUIConfig(ConfigBase): """WebUI配置类""" diff --git a/src/dream/dream_agent.py b/src/dream/dream_agent.py deleted file mode 100644 index 97b4b5d9..00000000 --- a/src/dream/dream_agent.py +++ /dev/null @@ -1,552 +0,0 @@ -import asyncio -import random -import time -from typing import Any, Dict, List, Optional, Tuple - -from sqlalchemy import func as fn - -from src.common.logger import get_logger -from src.config.config import global_config, model_config -from src.common.database.database_model import ChatHistory -from src.prompt.prompt_manager import prompt_manager -from src.llm_models.payload_content.message import MessageBuilder, RoleType, Message -from src.services import llm_service as llm_api -from src.dream.dream_generator import generate_dream_summary - -# dream 工具工厂函数 -from src.dream.tools.search_chat_history_tool import make_search_chat_history -from src.dream.tools.get_chat_history_detail_tool import make_get_chat_history_detail -from src.dream.tools.delete_chat_history_tool import make_delete_chat_history -from src.dream.tools.create_chat_history_tool import make_create_chat_history -from src.dream.tools.update_chat_history_tool import make_update_chat_history -from src.dream.tools.finish_maintenance_tool import make_finish_maintenance -from src.dream.tools.search_jargon_tool import make_search_jargon -from src.dream.tools.delete_jargon_tool import make_delete_jargon -from src.dream.tools.update_jargon_tool import make_update_jargon - -logger = get_logger("dream_agent") - - -class DreamTool: - """dream 模块内部使用的简易工具封装""" - - def __init__(self, name: str, description: str, parameters: List[Tuple], execute_func): - self.name = name - self.description = description - self.parameters = parameters - self.execute_func = execute_func - - def get_tool_definition(self) -> Dict[str, Any]: - return { - "name": self.name, - "description": self.description, - "parameters": self.parameters, - } - - async def execute(self, **kwargs) -> str: - return await self.execute_func(**kwargs) - - -class DreamToolRegistry: - def __init__(self) -> None: - self.tools: Dict[str, DreamTool] = {} - - def register_tool(self, tool: DreamTool) -> None: - """ - 注册或更新 dream 工具。 - 注意:dream agent 每个 chat_id 会重新初始化工具,这里允许覆盖已有同名工具。 - """ - self.tools[tool.name] = tool - logger.info(f"注册/更新 dream 工具: {tool.name}") - - def get_tool(self, name: str) -> Optional[DreamTool]: - return self.tools.get(name) - - def get_tool_definitions(self) -> List[Dict[str, Any]]: - return [tool.get_tool_definition() for tool in self.tools.values()] - - -_dream_tool_registry = DreamToolRegistry() - - -def get_dream_tool_registry() -> DreamToolRegistry: - return _dream_tool_registry - - -def init_dream_tools(chat_id: str) -> None: - """注册 dream agent 可用的 ChatHistory / Jargon 相关工具(限定在当前 chat_id 作用域内)""" - from src.llm_models.payload_content.tool_option import ToolParamType - - # 通过工厂函数生成绑定当前 chat_id 的工具实现 - search_chat_history = make_search_chat_history(chat_id) - get_chat_history_detail = make_get_chat_history_detail(chat_id) - delete_chat_history = make_delete_chat_history(chat_id) - create_chat_history = make_create_chat_history(chat_id) - update_chat_history = make_update_chat_history(chat_id) - finish_maintenance = make_finish_maintenance(chat_id) - - search_jargon = make_search_jargon(chat_id) - _delete_jargon = make_delete_jargon(chat_id) - _update_jargon = make_update_jargon(chat_id) - - _dream_tool_registry.register_tool( - DreamTool( - "search_chat_history", - "根据关键词或参与人查询当前 chat_id 下的 ChatHistory 概览,便于快速定位相关记忆。", - [ - ( - "keyword", - ToolParamType.STRING, - "关键词(可选,支持多个关键词,可用空格、逗号等分隔)。", - False, - None, - ), - ("participant", ToolParamType.STRING, "参与人昵称(可选)。", False, None), - ], - search_chat_history, - ) - ) - - _dream_tool_registry.register_tool( - DreamTool( - "get_chat_history_detail", - "根据 memory_id 获取单条 ChatHistory 的详细内容,包含主题、概括、关键词、关键信息等字段(不包含原文)。", - [ - ("memory_id", ToolParamType.INTEGER, "ChatHistory 主键 ID。", True, None), - ], - get_chat_history_detail, - ) - ) - - _dream_tool_registry.register_tool( - DreamTool( - "delete_chat_history", - "根据 memory_id 删除一条 ChatHistory 记录(请谨慎使用)。", - [ - ("memory_id", ToolParamType.INTEGER, "需要删除的 ChatHistory 主键 ID。", True, None), - ], - delete_chat_history, - ) - ) - - _dream_tool_registry.register_tool( - DreamTool( - "update_chat_history", - "按字段更新 ChatHistory 记录,可用于清理、重写或补充信息。", - [ - ("memory_id", ToolParamType.INTEGER, "需要更新的 ChatHistory 主键 ID。", True, None), - ("theme", ToolParamType.STRING, "新的主题标题,如果不需要修改可不填。", False, None), - ("summary", ToolParamType.STRING, "新的概括内容,如果不需要修改可不填。", False, None), - ("keywords", ToolParamType.STRING, "新的关键词 JSON 字符串,如 ['关键词1','关键词2']。", False, None), - ], - update_chat_history, - ) - ) - - _dream_tool_registry.register_tool( - DreamTool( - "create_chat_history", - "根据整理后的理解创建一条新的 ChatHistory 概括记录(主题、概括、关键词等)。", - [ - ("theme", ToolParamType.STRING, "新的主题标题(必填)。", True, None), - ("summary", ToolParamType.STRING, "新的概括内容(必填)。", True, None), - ( - "keywords", - ToolParamType.STRING, - "新的关键词 JSON 字符串,如 ['关键词1','关键词2'](必填)。", - True, - None, - ), - ("original_text", ToolParamType.STRING, "对话原文内容(必填)。", True, None), - ( - "participants", - ToolParamType.STRING, - "参与人的 JSON 字符串,如 ['用户1','用户2'](必填)。", - True, - None, - ), - ("start_time", ToolParamType.STRING, "起始时间戳(秒,Unix 时间,必填)。", True, None), - ("end_time", ToolParamType.STRING, "结束时间戳(秒,Unix 时间,必填)。", True, None), - ], - create_chat_history, - ) - ) - - _dream_tool_registry.register_tool( - DreamTool( - "finish_maintenance", - "结束本次 dream 维护任务。当你认为当前 chat_id 下的维护工作已经完成,没有更多需要整理、合并或修改的内容时,调用此工具来主动结束本次运行。", - [ - ( - "reason", - ToolParamType.STRING, - "结束维护的原因说明(可选),例如 '已完成所有记录的整理' 或 '当前记录质量良好,无需进一步维护'。", - False, - None, - ), - ], - finish_maintenance, - ) - ) - - # ==================== Jargon 维护工具 ==================== - # 注册 Jargon 工具 - _dream_tool_registry.register_tool( - DreamTool( - "search_jargon", - "根据一个或多个关键词搜索当前 chat_id 相关的 Jargon 记录概览(只包含 is_jargon=True,含全局 Jargon),便于快速理解黑话库。", - [ - ("keyword", ToolParamType.STRING, "按一个或多个关键词搜索内容/含义/推断结果(必填)。", True, None), - ], - search_jargon, - ) - ) - - -async def run_dream_agent_once( - chat_id: str, - max_iterations: Optional[int] = None, - start_memory_id: Optional[int] = None, -) -> None: - """ - 运行一次 dream agent,对指定 chat_id 的 ChatHistory 进行最多 max_iterations 轮的整理。 - 如果 max_iterations 为 None,则使用配置文件中的默认值。 - """ - if max_iterations is None: - max_iterations = global_config.dream.max_iterations - - start_ts = time.time() - logger.info(f"[dream] 开始对 chat_id={chat_id} 进行 dream 维护,最多迭代 {max_iterations} 轮") - - # 初始化工具(作用域限定在当前 chat_id) - init_dream_tools(chat_id) - - tool_registry = get_dream_tool_registry() - tool_defs = tool_registry.get_tool_definitions() - - head_prompt_template = prompt_manager.get_prompt("dream_react_head") - head_prompt_template.add_context("bot_name", global_config.bot.nickname) - head_prompt_template.add_context("time_now", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) - head_prompt_template.add_context("chat_id", chat_id) - head_prompt_template.add_context( - "start_memory_id", - str(start_memory_id) if start_memory_id is not None else "无(本轮由你自由选择切入点)", - ) - head_prompt_template.add_context("max_iterations", str(max_iterations)) - - head_prompt = await prompt_manager.render_prompt(head_prompt_template) - - conversation_messages: List[Message] = [] - - # 如果提供了起始记忆 ID,则在对话正式开始前,先把这条记忆的详细信息放入上下文, - # 避免 LLM 还需要额外调用一次 get_chat_history_detail 才能看到起始记忆内容。 - if start_memory_id is not None: - try: - record = ChatHistory.get_or_none(ChatHistory.id == start_memory_id) - if record: - start_time_str = ( - time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.start_time)) - if record.start_time - else "未知" - ) - end_time_str = ( - time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.end_time)) if record.end_time else "未知" - ) - detail_text = ( - f"ID={record.id}\n" - f"chat_id={record.chat_id}\n" - f"时间范围={start_time_str} 至 {end_time_str}\n" - f"主题={record.theme or '无'}\n" - f"关键词={record.keywords or '无'}\n" - f"参与者={record.participants or '无'}\n" - f"概括={record.summary or '无'}" - ) - - logger.debug( - f"[dream] 预加载起始记忆详情 memory_id={start_memory_id}," - f"预览: {detail_text[:200].replace(chr(10), ' ')}" - ) - - start_detail_builder = MessageBuilder() - start_detail_builder.set_role(RoleType.User) - start_detail_builder.add_text_content( - "【起始记忆详情】以下是本轮随机/指定的起始记忆的详细信息,供你在整理时优先参考:\n\n" + detail_text - ) - conversation_messages.append(start_detail_builder.build()) - else: - logger.warning( - f"[dream] 提供的 start_memory_id={start_memory_id} 未找到对应 ChatHistory 记录," - "将不预加载起始记忆详情。" - ) - except Exception as e: - logger.error(f"[dream] 预加载起始记忆详情失败 start_memory_id={start_memory_id}: {e}") - - # 注意:message_factory 必须是同步函数,返回消息列表(不能是 async/coroutine) - def message_factory( - _client, - *, - _head_prompt: str = head_prompt, - _conversation_messages: List[Message] = conversation_messages, - ) -> List[Message]: - messages: List[Message] = [] - system_builder = MessageBuilder() - system_builder.set_role(RoleType.System) - system_builder.add_text_content(_head_prompt) - messages.append(system_builder.build()) - messages.extend(_conversation_messages) - return messages - - for iteration in range(1, max_iterations + 1): - # 在每轮开始时,添加轮次信息到对话中 - remaining_rounds = max_iterations - iteration + 1 - round_info_builder = MessageBuilder() - round_info_builder.set_role(RoleType.User) - round_info_builder.add_text_content( - f"【轮次信息】当前是第 {iteration}/{max_iterations} 轮,还剩 {remaining_rounds} 轮。" - ) - conversation_messages.append(round_info_builder.build()) - - # 调用 LLM 让其决定是否要使用工具 - ( - success, - response, - reasoning_content, - model_name, - tool_calls, - ) = await llm_api.generate_with_model_with_tools_by_message_factory( - message_factory, - model_config=model_config.model_task_config.tool_use, - tool_options=tool_defs, - request_type="dream.react", - ) - - if not success: - logger.error(f"[dream] 第 {iteration} 轮 LLM 调用失败: {response}") - break - - # 先输出「思考」内容,再输出工具调用信息(思考文本较长,仅在 debug 下输出) - thought_log = reasoning_content or (response[:300] if response else "") - if thought_log: - logger.debug(f"[dream] 第 {iteration} 轮思考内容: {thought_log}") - - logger.info( - f"[dream] 第 {iteration} 轮响应,模型={model_name},工具调用数={len(tool_calls) if tool_calls else 0}" - ) - - assistant_msg: Optional[Message] = None - if tool_calls: - builder = MessageBuilder() - builder.set_role(RoleType.Assistant) - if response and response.strip(): - builder.add_text_content(response) - builder.set_tool_calls(tool_calls) - assistant_msg = builder.build() - elif response and response.strip(): - builder = MessageBuilder() - builder.set_role(RoleType.Assistant) - builder.add_text_content(response) - assistant_msg = builder.build() - - if assistant_msg: - conversation_messages.append(assistant_msg) - - # 如果本轮没有工具调用,仅作为思考记录,继续下一轮 - if not tool_calls: - logger.debug(f"[dream] 第 {iteration} 轮未调用任何工具,仅记录思考。") - continue - - # 执行所有工具调用 - tasks = [] - finish_maintenance_called = False - for tc in tool_calls: - tool = tool_registry.get_tool(tc.func_name) - if not tool: - logger.warning(f"[dream] 未知工具:{tc.func_name}") - continue - - # 检测是否调用了 finish_maintenance 工具 - if tc.func_name == "finish_maintenance": - finish_maintenance_called = True - - params = tc.args or {} - - async def _run_single(t: DreamTool, p: Dict[str, Any], call_id: str, it: int): - try: - result = await t.execute(**p) - logger.debug(f"[dream] 第 {it} 轮 工具 {t.name} 执行完成") - return call_id, result - except Exception as e: - logger.error(f"[dream] 工具 {t.name} 执行失败: {e}") - return call_id, f"工具 {t.name} 执行失败: {e}" - - tasks.append(_run_single(tool, params, tc.call_id, iteration)) - - if not tasks: - continue - - tool_results = await asyncio.gather(*tasks, return_exceptions=False) - - # 将工具结果作为 Tool 消息追加 - for call_id, obs in tool_results: - tool_builder = MessageBuilder() - tool_builder.set_role(RoleType.Tool) - tool_builder.add_text_content(str(obs)) - tool_builder.add_tool_call(call_id) - conversation_messages.append(tool_builder.build()) - - # 如果调用了 finish_maintenance 工具,提前结束本次运行 - if finish_maintenance_called: - logger.info(f"[dream] 第 {iteration} 轮检测到 finish_maintenance 工具调用,提前结束本次维护。") - break - - cost = time.time() - start_ts - logger.info(f"[dream] 对 chat_id={chat_id} 的 dream 维护结束,共迭代 {iteration} 轮,耗时 {cost:.1f} 秒") - - # 生成梦境总结 - await generate_dream_summary(chat_id, conversation_messages, iteration, cost) - - -def _pick_random_chat_id() -> Optional[str]: - """从 ChatHistory 中随机选择一个 chat_id,用于 dream agent 本次维护 - - 规则: - - 只在 chat_id 所属的 ChatHistory 记录数 >= 10 时才会参与随机选择; - - 记录数不足 10 的 chat_id 将被跳过,不会触发做梦 react。 - """ - try: - # 统计每个 chat_id 的记录数,只保留记录数 >= 10 的 chat_id - rows = ( - ChatHistory.select(ChatHistory.chat_id, fn.COUNT(ChatHistory.id).alias("cnt")) - .group_by(ChatHistory.chat_id) - .having(fn.COUNT(ChatHistory.id) >= 10) - .order_by(ChatHistory.chat_id) - .limit(200) - ) - eligible_ids = [r.chat_id for r in rows] - if not eligible_ids: - logger.warning("[dream] ChatHistory 中暂无满足条件(记录数 >= 10)的 chat_id,本轮 dream 任务跳过。") - return None - chosen = random.choice(eligible_ids) - logger.info(f"[dream] 从 {len(eligible_ids)} 个满足条件的 chat_id 中随机选择:{chosen}") - return chosen - except Exception as e: - logger.error(f"[dream] 随机选择 chat_id 失败: {e}") - return None - - -def _pick_random_memory_for_chat(chat_id: str) -> Optional[int]: - """ - 在给定 chat_id 下随机选择一条 ChatHistory 记录,作为本轮整理的起始记忆。 - """ - try: - rows = ( - ChatHistory.select(ChatHistory.id) - .where(ChatHistory.chat_id == chat_id) - .order_by(ChatHistory.start_time.asc()) - .limit(200) - ) - ids = [r.id for r in rows] - if not ids: - logger.warning(f"[dream] chat_id={chat_id} 下暂无 ChatHistory 记录,无法选择起始记忆。") - return None - return random.choice(ids) - except Exception as e: - logger.error(f"[dream] 在 chat_id={chat_id} 下随机选择起始记忆失败: {e}") - return None - - -async def run_dream_cycle_once() -> None: - """ - 单次 dream 周期: - - 随机选择一个 chat_id - - 在该 chat_id 下随机选择一条 ChatHistory 作为起始记忆 - - 以这条起始记忆为切入点,对该 chat_id 运行一次 dream agent(最多 15 轮) - """ - chat_id = _pick_random_chat_id() - if not chat_id: - return - - start_memory_id = _pick_random_memory_for_chat(chat_id) - await run_dream_agent_once( - chat_id=chat_id, - max_iterations=None, # 使用配置文件中的默认值 - start_memory_id=start_memory_id, - ) - - -async def start_dream_scheduler( - first_delay_seconds: Optional[int] = None, - interval_seconds: Optional[int] = None, - stop_event: Optional[asyncio.Event] = None, -) -> None: - """ - dream 调度器: - - 程序启动后先等待 first_delay_seconds(如果为 None,则使用配置文件中的值,默认 60s) - - 然后每隔 interval_seconds(如果为 None,则使用配置文件中的值,默认 30 分钟)运行一次 dream agent 周期 - - 如果提供 stop_event,则在 stop_event 被 set() 后优雅退出循环 - """ - if first_delay_seconds is None: - first_delay_seconds = global_config.dream.first_delay_seconds - - if interval_seconds is None: - interval_seconds = global_config.dream.interval_minutes * 60 - - logger.info( - f"[dream] dream 调度器启动:首次延迟 {first_delay_seconds}s,之后每隔 {interval_seconds}s ({interval_seconds // 60} 分钟) 运行一次 dream agent" - ) - - try: - await asyncio.sleep(first_delay_seconds) - while True: - if stop_event is not None and stop_event.is_set(): - logger.info("[dream] 收到停止事件,结束 dream 调度器循环。") - break - - start_ts = time.time() - # 检查当前时间是否在允许做梦的时间段内 - if not TempMethodsDream.is_in_dream_time(): - logger.debug("[dream] 当前时间不在允许做梦的时间段内,跳过本次执行") - else: - try: - await run_dream_cycle_once() - except Exception as e: - logger.error(f"[dream] 单次 dream 周期执行异常: {e}") - - elapsed = time.time() - start_ts - # 保证两次执行之间至少间隔 interval_seconds - to_sleep = max(0.0, interval_seconds - elapsed) - await asyncio.sleep(to_sleep) - except asyncio.CancelledError: - logger.info("[dream] dream 调度器任务被取消,准备退出。") - raise - - -class TempMethodsDream: - @staticmethod - def is_in_dream_time() -> bool: - if not global_config.dream.dream_time_ranges: - return True - now_min = time.localtime() - now_total_min = now_min.tm_hour * 60 + now_min.tm_min - for time_range in global_config.dream.dream_time_ranges: - if parsed := TempMethodsDream._parse_range(time_range): - start_min, end_min = parsed - if TempMethodsDream._in_range(now_total_min, start_min, end_min): - return True - return False - - @staticmethod - def _in_range(now_min, start_min, end_min) -> bool: - if start_min <= end_min: - return start_min <= now_min <= end_min - return now_min >= start_min or now_min <= end_min - - @staticmethod - def _parse_range(range_str: str) -> Optional[Tuple[int, int]]: - try: - start_str, end_str = [s.strip() for s in range_str.split("-")] - sh, sm = [int(x) for x in start_str.split(":")] - eh, em = [int(x) for x in end_str.split(":")] - return sh * 60 + sm, eh * 60 + em - except Exception: - return None diff --git a/src/dream/dream_generator.py b/src/dream/dream_generator.py deleted file mode 100644 index ff709a5f..00000000 --- a/src/dream/dream_generator.py +++ /dev/null @@ -1,210 +0,0 @@ -from typing import List, Optional -import random -import json - -from src.common.logger import get_logger -from src.config.config import global_config, model_config -from src.llm_models.payload_content.message import RoleType, Message -from src.prompt.prompt_manager import prompt_manager -from src.llm_models.utils_model import LLMRequest -from src.common.utils.utils_session import SessionUtils -from src.services import send_service as send_api - -logger = get_logger("dream_generator") - -# 初始化 utils 模型用于生成梦境总结 -_dream_summary_model: Optional[LLMRequest] = None - -# 梦境风格列表(21种) -DREAM_STYLES = [ - "保持诗意和想象力,自由编写", - "诗意朦胧,如薄雾笼罩的清晨", - "奇幻冒险,充满未知与探索", - "温暖怀旧,带着时光的痕迹", - "神秘悬疑,暗藏深意", - "浪漫唯美,如诗如画", - "科幻未来,科技与想象交织", - "自然清新,如山林间的微风", - "深沉哲思,引人深思", - "轻松幽默,充满趣味", - "悲伤忧郁,带着淡淡哀愁", - "激昂热烈,充满活力", - "宁静平和,如湖面般平静", - "荒诞离奇,打破常规", - "细腻温柔,如春风拂面", - "壮阔宏大,气势磅礴", - "简约纯粹,返璞归真", - "复杂多变,层次丰富", - "梦幻迷离,虚实难辨", - "现实写意,贴近生活", - "抽象概念,超越具象", -] - - -def get_random_dream_styles(count: int = 2) -> List[str]: - """从梦境风格列表中随机选择指定数量的风格""" - return random.sample(DREAM_STYLES, min(count, len(DREAM_STYLES))) - - -async def generate_dream_summary( - chat_id: str, - conversation_messages: List[Message], - total_iterations: int, - time_cost: float, -) -> None: - """生成梦境总结,输出到日志,并根据配置可选地推送给指定用户""" - try: - # 第一步:建立工具调用结果映射 (call_id -> result) - tool_results_map: dict[str, str] = {} - for msg in conversation_messages: - if msg.role == RoleType.Tool and msg.tool_call_id: - content = "" - if msg.content: - if isinstance(msg.content, list) and msg.content: - content = msg.content[0].text if hasattr(msg.content[0], "text") else str(msg.content[0]) - else: - content = str(msg.content) - tool_results_map[msg.tool_call_id] = content - - # 第二步:详细记录所有工具调用操作和结果到日志 - tool_call_count = 0 - logger.info(f"[dream][工具调用详情] 开始记录 chat_id={chat_id} 的所有工具调用操作:") - - for msg in conversation_messages: - if msg.role == RoleType.Assistant and msg.tool_calls: - tool_call_count += 1 - # 提取思考内容 - thought_content = "" - if msg.content: - if isinstance(msg.content, list) and msg.content: - thought_content = ( - msg.content[0].text if hasattr(msg.content[0], "text") else str(msg.content[0]) - ) - else: - thought_content = str(msg.content) - - logger.info(f"[dream][工具调用详情] === 第 {tool_call_count} 组工具调用 ===") - if thought_content: - logger.info( - f"[dream][工具调用详情] 思考内容:{thought_content[:500]}{'...' if len(thought_content) > 500 else ''}" - ) - - # 记录每个工具调用的详细信息 - for idx, tool_call in enumerate(msg.tool_calls, 1): - tool_name = tool_call.func_name - tool_args = tool_call.args or {} - tool_call_id = tool_call.call_id - tool_result = tool_results_map.get(tool_call_id, "未找到执行结果") - - # 格式化参数 - try: - args_str = json.dumps(tool_args, ensure_ascii=False, indent=2) if tool_args else "无参数" - except Exception: - args_str = str(tool_args) - - logger.info(f"[dream][工具调用详情] --- 工具 {idx}: {tool_name} ---") - logger.info(f"[dream][工具调用详情] 调用参数:\n{args_str}") - logger.info(f"[dream][工具调用详情] 执行结果:\n{tool_result}") - logger.info(f"[dream][工具调用详情] {'-' * 60}") - - logger.info(f"[dream][工具调用详情] 共记录了 {tool_call_count} 组工具调用操作") - - # 第三步:构建对话历史摘要(用于生成梦境) - conversation_summary = [] - for msg in conversation_messages: - role = msg.role.value if hasattr(msg.role, "value") else str(msg.role) - content = "" - if msg.content: - content = msg.content[0].text if isinstance(msg.content, list) and msg.content else str(msg.content) - - if role == "user" and "轮次信息" in content: - # 跳过轮次信息消息 - continue - - if role == "assistant": - # 只保留思考内容,简化工具调用信息 - if content: - # 截取前500字符,避免过长 - content_preview = content[:500] + ("..." if len(content) > 500 else "") - conversation_summary.append(f"[{role}] {content_preview}") - elif role == "tool": - # 工具结果,只保留关键信息 - if content: - # 截取前300字符 - content_preview = content[:300] + ("..." if len(content) > 300 else "") - conversation_summary.append(f"[工具执行] {content_preview}") - - conversation_text = "\n".join(conversation_summary[-20:]) # 只保留最后20条消息 - - # 随机选择2个梦境风格 - selected_styles = get_random_dream_styles(2) - dream_styles_text = "\n".join([f"{i + 1}. {style}" for i, style in enumerate(selected_styles)]) - - # 使用 Prompt 管理器格式化梦境生成 prompt - dream_prompt_template = prompt_manager.get_prompt("dream_summary") - dream_prompt_template.add_context("chat_id", chat_id) - dream_prompt_template.add_context("total_iterations", str(total_iterations)) - dream_prompt_template.add_context("time_cost", str(time_cost)) - dream_prompt_template.add_context("conversation_text", conversation_text) - dream_prompt_template.add_context("dream_styles", dream_styles_text) - dream_prompt = await prompt_manager.render_prompt(dream_prompt_template) - - # 调用 utils 模型生成梦境 - summary_model = LLMRequest( - model_set=model_config.model_task_config.replyer, - request_type="dream.summary", - ) - dream_content, (reasoning, model_name, _) = await summary_model.generate_response_async( - dream_prompt, - temperature=0.8, - ) - - if dream_content: - logger.info(f"[dream][梦境总结] 对 chat_id={chat_id} 的整理过程梦境:\n{dream_content}") - - # 第五步:根据配置决定是否将梦境发送给指定用户 - try: - dream_send_raw = getattr(global_config.dream, "dream_send", "") or "" - dream_send = dream_send_raw.strip() - if dream_send: - parts = dream_send.split(":") - if len(parts) != 2: - logger.warning( - f"[dream][梦境总结] dream_send 配置格式不正确,应为 'platform:user_id',当前值: {dream_send_raw!r}" - ) - else: - platform, user_id = parts[0].strip(), parts[1].strip() - if not platform or not user_id: - logger.warning(f"[dream][梦境总结] dream_send 平台或用户ID为空,当前值: {dream_send_raw!r}") - else: - # 默认为私聊会话 - stream_id = SessionUtils.calculate_session_id( - platform=platform, - user_id=str(user_id), - ) - if not stream_id: - logger.error( - f"[dream][梦境总结] 无法根据 dream_send 找到有效的聊天流," - f"platform={platform!r}, user_id={user_id!r}" - ) - else: - dream_visible = global_config.dream.dream_visible - ok = await send_api.text_to_stream( - dream_content, - stream_id=stream_id, - typing=False, - storage_message=dream_visible, - ) - if ok: - logger.info( - f"[dream][梦境总结] 已将梦境结果发送给配置的目标用户: {platform}:{user_id}" - ) - else: - logger.error(f"[dream][梦境总结] 向 {platform}:{user_id} 发送梦境结果失败") - except Exception as send_exc: - logger.error(f"[dream][梦境总结] 发送梦境结果到配置用户时出错: {send_exc}", exc_info=True) - else: - logger.warning("[dream][梦境总结] 未能生成梦境总结") - - except Exception as e: - logger.error(f"[dream][梦境总结] 生成梦境总结失败: {e}", exc_info=True) diff --git a/src/dream/tools/__init__.py b/src/dream/tools/__init__.py deleted file mode 100644 index cd784b02..00000000 --- a/src/dream/tools/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -dream agent 工具实现模块。 - -每个工具的具体实现放在独立文件中,通过 make_xxx(chat_id) 工厂函数 -生成绑定到特定 chat_id 的协程函数,由 dream_agent.init_dream_tools 统一注册。 -""" diff --git a/src/dream/tools/create_chat_history_tool.py b/src/dream/tools/create_chat_history_tool.py deleted file mode 100644 index 30e1b0e4..00000000 --- a/src/dream/tools/create_chat_history_tool.py +++ /dev/null @@ -1,65 +0,0 @@ -import time - -from src.common.logger import get_logger -from src.common.database.database_model import ChatHistory - -logger = get_logger("dream_agent") - - -def make_create_chat_history(chat_id: str): - async def create_chat_history( - theme: str, - summary: str, - keywords: str, - original_text: str, - participants: str, - start_time: float, - end_time: float, - ) -> str: - """创建一条新的 ChatHistory 概括记录(用于整理/合并后的新记忆)""" - try: - logger.info( - f"[dream][tool] 调用 create_chat_history(" - f"theme={bool(theme)}, summary={bool(summary)}, " - f"keywords={bool(keywords)}, original_text={bool(original_text)}, " - f"participants={bool(participants)}, " - f"start_time={start_time}, end_time={end_time}) (chat_id={chat_id})" - ) - - now_ts = time.time() - - # 将传入的 start_time/end_time(如果有)解析为时间戳;否则回退为当前时间 - def _parse_ts(value, default): - if value is None: - return default - try: - return float(value) - except (TypeError, ValueError): - return default - - start_ts = _parse_ts(start_time, now_ts) - end_ts = _parse_ts(end_time, now_ts) - - record = ChatHistory.create( - chat_id=chat_id, - theme=theme, - summary=summary, - keywords=keywords, - original_text=original_text, - participants=participants, - # 对于由 dream 整理产生的新概括,时间范围优先使用工具提供的时间,否则使用当前时间占位 - start_time=start_ts, - end_time=end_ts, - ) - - msg = ( - f"已创建新的 ChatHistory 记录,ID={record.id}," - f"theme={record.theme or '无'},summary={'有' if record.summary else '无'}。" - ) - logger.info(f"[dream][tool] create_chat_history 完成: {msg}") - return msg - except Exception as e: - logger.error(f"create_chat_history 失败: {e}") - return f"create_chat_history 执行失败: {e}" - - return create_chat_history diff --git a/src/dream/tools/delete_chat_history_tool.py b/src/dream/tools/delete_chat_history_tool.py deleted file mode 100644 index 18c32f27..00000000 --- a/src/dream/tools/delete_chat_history_tool.py +++ /dev/null @@ -1,25 +0,0 @@ -from src.common.logger import get_logger -from src.common.database.database_model import ChatHistory - -logger = get_logger("dream_agent") - - -def make_delete_chat_history(chat_id: str): # chat_id 目前未直接使用,预留以备扩展 - async def delete_chat_history(memory_id: int) -> str: - """删除一条 chat_history 记录""" - try: - logger.info(f"[dream][tool] 调用 delete_chat_history(memory_id={memory_id})") - record = ChatHistory.get_or_none(ChatHistory.id == memory_id) - if not record: - msg = f"未找到 ID={memory_id} 的 ChatHistory 记录,无法删除。" - logger.info(f"[dream][tool] delete_chat_history 未找到记录: {msg}") - return msg - rows = ChatHistory.delete().where(ChatHistory.id == memory_id).execute() - msg = f"已删除 ID={memory_id} 的 ChatHistory 记录,受影响行数={rows}。" - logger.info(f"[dream][tool] delete_chat_history 完成: {msg}") - return msg - except Exception as e: - logger.error(f"delete_chat_history 失败: {e}") - return f"delete_chat_history 执行失败: {e}" - - return delete_chat_history diff --git a/src/dream/tools/delete_jargon_tool.py b/src/dream/tools/delete_jargon_tool.py deleted file mode 100644 index 8edd3245..00000000 --- a/src/dream/tools/delete_jargon_tool.py +++ /dev/null @@ -1,25 +0,0 @@ -from src.common.logger import get_logger -from src.common.database.database_model import Jargon - -logger = get_logger("dream_agent") - - -def make_delete_jargon(chat_id: str): # chat_id 目前未直接使用,预留以备扩展 - async def delete_jargon(jargon_id: int) -> str: - """删除一条 Jargon 记录""" - try: - logger.info(f"[dream][tool] 调用 delete_jargon(jargon_id={jargon_id})") - record = Jargon.get_or_none(Jargon.id == jargon_id) - if not record: - msg = f"未找到 ID={jargon_id} 的 Jargon 记录,无法删除。" - logger.info(f"[dream][tool] delete_jargon 未找到记录: {msg}") - return msg - rows = Jargon.delete().where(Jargon.id == jargon_id).execute() - msg = f"已删除 ID={jargon_id} 的 Jargon 记录(内容:{record.content}),受影响行数={rows}。" - logger.info(f"[dream][tool] delete_jargon 完成: {msg}") - return msg - except Exception as e: - logger.error(f"delete_jargon 失败: {e}") - return f"delete_jargon 执行失败: {e}" - - return delete_jargon diff --git a/src/dream/tools/finish_maintenance_tool.py b/src/dream/tools/finish_maintenance_tool.py deleted file mode 100644 index 403b6c6e..00000000 --- a/src/dream/tools/finish_maintenance_tool.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Optional - -from src.common.logger import get_logger - -logger = get_logger("dream_agent") - - -def make_finish_maintenance(chat_id: str): # chat_id 目前未直接使用,预留以备扩展 - async def finish_maintenance(reason: Optional[str] = None) -> str: - """结束本次 dream 维护任务。当你认为当前 chat_id 下的维护工作已经完成,没有更多需要整理的内容时,调用此工具来结束本次运行。""" - reason_text = f",原因:{reason}" if reason else "" - msg = f"DREAM_MAINTENANCE_COMPLETE{reason_text}" - logger.info(f"[dream][tool] 调用 finish_maintenance,结束本次维护{reason_text}") - return msg - - return finish_maintenance diff --git a/src/dream/tools/get_chat_history_detail_tool.py b/src/dream/tools/get_chat_history_detail_tool.py deleted file mode 100644 index 81d63284..00000000 --- a/src/dream/tools/get_chat_history_detail_tool.py +++ /dev/null @@ -1,43 +0,0 @@ -import time - -from src.common.logger import get_logger -from src.common.database.database_model import ChatHistory - -logger = get_logger("dream_agent") - - -def make_get_chat_history_detail(chat_id: str): # chat_id 目前未直接使用,预留以备扩展 - async def get_chat_history_detail(memory_id: int) -> str: - """获取单条 chat_history 的完整内容""" - try: - logger.info(f"[dream][tool] 调用 get_chat_history_detail(memory_id={memory_id})") - record = ChatHistory.get_or_none(ChatHistory.id == memory_id) - if not record: - msg = f"未找到 ID={memory_id} 的 ChatHistory 记录。" - logger.info(f"[dream][tool] get_chat_history_detail 未找到记录: {msg}") - return msg - - # 将时间戳转换为可读时间格式 - start_time_str = ( - time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.start_time)) if record.start_time else "未知" - ) - end_time_str = ( - time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.end_time)) if record.end_time else "未知" - ) - - result = ( - f"ID={record.id}\n" - # f"chat_id={record.chat_id}\n" - f"时间范围={start_time_str} 至 {end_time_str}\n" - f"主题={record.theme or '无'}\n" - f"关键词={record.keywords or '无'}\n" - f"参与者={record.participants or '无'}\n" - f"概括={record.summary or '无'}" - ) - logger.debug(f"[dream][tool] get_chat_history_detail 成功,预览: {result[:200].replace(chr(10), ' ')}") - return result - except Exception as e: - logger.error(f"get_chat_history_detail 失败: {e}") - return f"get_chat_history_detail 执行失败: {e}" - - return get_chat_history_detail diff --git a/src/dream/tools/search_chat_history_tool.py b/src/dream/tools/search_chat_history_tool.py deleted file mode 100644 index 5d216f00..00000000 --- a/src/dream/tools/search_chat_history_tool.py +++ /dev/null @@ -1,214 +0,0 @@ -import json -from typing import List, Optional - -from src.common.logger import get_logger -from src.common.database.database_model import ChatHistory -from src.chat.utils.utils import parse_keywords_string - -logger = get_logger("dream_agent") - - -def make_search_chat_history(chat_id: str): - async def search_chat_history( - keyword: Optional[str] = None, - participant: Optional[str] = None, - ) -> str: - """根据关键词或参与人查询记忆,返回匹配的记忆id、记忆标题theme和关键词keywords(dream 维护专用版本)""" - try: - # 检查参数 - if not keyword and not participant: - return "未指定查询参数(需要提供keyword或participant之一)" - - logger.info( - f"[dream][tool] 调用 search_chat_history(keyword={keyword}, participant={participant}) " - f"(作用域 chat_id={chat_id})" - ) - - # 构建查询条件 - query = ChatHistory.select().where(ChatHistory.chat_id == chat_id) - - # 执行查询(按时间倒序,最近的在前) - records = list(query.order_by(ChatHistory.start_time.desc()).limit(50)) - - filtered_records: List[ChatHistory] = [] - - for record in records: - participant_matched = True # 如果没有participant条件,默认为True - keyword_matched = True # 如果没有keyword条件,默认为True - - # 检查参与人匹配 - if participant: - participant_matched = False - participants_list: List[str] = [] - if record.participants: - try: - participants_data = ( - json.loads(record.participants) - if isinstance(record.participants, str) - else record.participants - ) - if isinstance(participants_data, list): - participants_list = [str(p).lower() for p in participants_data] - except (json.JSONDecodeError, TypeError, ValueError): - pass - - participant_lower = participant.lower().strip() - if participant_lower and any(participant_lower in p for p in participants_list): - participant_matched = True - - # 检查关键词匹配 - if keyword: - keyword_matched = False - # 解析多个关键词(支持空格、逗号等分隔符) - keywords_list = parse_keywords_string(keyword) - if not keywords_list: - keywords_list = [keyword.strip()] if keyword.strip() else [] - - # 转换为小写以便匹配 - keywords_lower = [kw.lower() for kw in keywords_list if kw.strip()] - - if keywords_lower: - # 在theme、keywords、summary、original_text中搜索 - theme = (record.theme or "").lower() - summary = (record.summary or "").lower() - original_text = (record.original_text or "").lower() - - # 解析record中的keywords JSON - record_keywords_list: List[str] = [] - if record.keywords: - try: - keywords_data = ( - json.loads(record.keywords) if isinstance(record.keywords, str) else record.keywords - ) - if isinstance(keywords_data, list): - record_keywords_list = [str(k).lower() for k in keywords_data] - except (json.JSONDecodeError, TypeError, ValueError): - pass - - # 有容错的全匹配:如果关键词数量>2,允许n-1个关键词匹配;否则必须全部匹配 - matched_count = 0 - for kw in keywords_lower: - kw_matched = ( - kw in theme - or kw in summary - or kw in original_text - or any(kw in k for k in record_keywords_list) - ) - if kw_matched: - matched_count += 1 - - # 计算需要匹配的关键词数量 - total_keywords = len(keywords_lower) - if total_keywords > 2: - # 关键词数量>2,允许n-1个关键词匹配 - required_matches = total_keywords - 1 - else: - # 关键词数量<=2,必须全部匹配 - required_matches = total_keywords - - keyword_matched = matched_count >= required_matches - - # 两者都匹配(如果同时有participant和keyword,需要两者都匹配;如果只有一个条件,只需要该条件匹配) - matched = participant_matched and keyword_matched - - if matched: - filtered_records.append(record) - - if not filtered_records: - if keyword and participant: - keywords_str = "、".join(parse_keywords_string(keyword) if keyword else []) - return f"未找到包含关键词'{keywords_str}'且参与人包含'{participant}'的聊天记录" - elif keyword: - keywords_list = parse_keywords_string(keyword) - keywords_str = "、".join(keywords_list) - if len(keywords_list) > 2: - required_count = len(keywords_list) - 1 - return f"未找到包含至少{required_count}个关键词(共{len(keywords_list)}个)'{keywords_str}'的聊天记录" - else: - return f"未找到包含所有关键词'{keywords_str}'的聊天记录" - elif participant: - return f"未找到参与人包含'{participant}'的聊天记录" - else: - return "未找到相关聊天记录" - - # 如果匹配结果超过20条,不返回具体记录,只返回提示和所有相关关键词 - if len(filtered_records) > 20: - all_keywords_set = set() - for record in filtered_records: - if record.keywords: - try: - keywords_data = ( - json.loads(record.keywords) if isinstance(record.keywords, str) else record.keywords - ) - if isinstance(keywords_data, list): - for k in keywords_data: - k_str = str(k).strip() - if k_str: - all_keywords_set.add(k_str) - except (json.JSONDecodeError, TypeError, ValueError): - continue - - search_label = keyword or participant or "当前条件" - - if all_keywords_set: - keywords_str = "、".join(sorted(all_keywords_set)) - response_text = ( - f"包含“{search_label}”的结果过多,请尝试更多关键词精确查找\n\n" - f'有关"{search_label}"的关键词:\n' - f"{keywords_str}" - ) - else: - response_text = ( - f"包含“{search_label}”的结果过多,请尝试更多关键词精确查找\n\n" - f'有关"{search_label}"的关键词信息为空' - ) - - logger.info( - f"[dream][tool] search_chat_history 匹配结果超过20条,返回关键词汇总提示,总数={len(filtered_records)}" - ) - return response_text - - # 构建结果文本,返回id、theme和keywords(最多20条) - results: List[str] = [] - for record in filtered_records[:20]: - result_parts: List[str] = [] - - # 记忆ID - result_parts.append(f"记忆ID:{record.id}") - - # 主题 - if record.theme: - result_parts.append(f"主题:{record.theme}") - else: - result_parts.append("主题:(无)") - - # 关键词 - if record.keywords: - try: - keywords_data = ( - json.loads(record.keywords) if isinstance(record.keywords, str) else record.keywords - ) - if isinstance(keywords_data, list) and keywords_data: - keywords_str = "、".join([str(k) for k in keywords_data]) - result_parts.append(f"关键词:{keywords_str}") - else: - result_parts.append("关键词:(无)") - except (json.JSONDecodeError, TypeError, ValueError): - result_parts.append("关键词:(无)") - else: - result_parts.append("关键词:(无)") - - results.append("\n".join(result_parts)) - - if not results: - return "未找到相关聊天记录" - - response_text = "\n\n---\n\n".join(results) - - logger.info(f"[dream][tool] search_chat_history 返回 {len(filtered_records)} 条匹配记录") - return response_text - except Exception as e: - logger.error(f"search_chat_history 失败: {e}") - return f"search_chat_history 执行失败: {e}" - - return search_chat_history diff --git a/src/dream/tools/search_jargon_tool.py b/src/dream/tools/search_jargon_tool.py deleted file mode 100644 index 8da8fe77..00000000 --- a/src/dream/tools/search_jargon_tool.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import List - -from src.common.logger import get_logger -from src.common.database.database_model import Jargon -from src.config.config import global_config -from src.chat.utils.utils import parse_keywords_string -from src.bw_learner.learner_utils_old import parse_chat_id_list, chat_id_list_contains - -logger = get_logger("dream_agent") - - -def make_search_jargon(chat_id: str): - async def search_jargon(keyword: str) -> str: - """根据一个或多个关键词搜索当前 chat_id 相关的 Jargon 记录概览(只包含 is_jargon=True,是否跨 chat_id 由 all_global 决定)""" - try: - if not keyword or not keyword.strip(): - return "未指定查询关键词(参数 keyword 为必填,且不能为空)" - - logger.info(f"[dream][tool] 调用 search_jargon(keyword={keyword}) (作用域 chat_id={chat_id})") - - # 基础条件:只查 is_jargon=True 的记录 - query = Jargon.select().where(Jargon.is_jargon) - - # 根据 all_global 配置决定 chat_id 作用域 - if global_config.expression.all_global_jargon: - # 开启全局黑话:只看 is_global=True 的记录,不区分 chat_id - query = query.where(Jargon.is_global) - else: - # 关闭全局黑话:后续在 Python 层按 chat_id 列表过滤(包含 is_global=True) - pass - - # 先按使用次数排序取一批候选,做一个安全上限 - query = query.order_by(Jargon.count.desc()).limit(200) - candidates = list(query) - - if not candidates: - msg = "未找到符合条件的 Jargon 记录。" - logger.info(f"[dream][tool] search_jargon 无记录: {msg}") - return msg - - # 关键词为必填,因此此处必然执行关键词过滤(支持多个关键词,大小写不敏感) - keywords_list = parse_keywords_string(keyword) or [] - if not keywords_list and keyword.strip(): - keywords_list = [keyword.strip()] - keywords_lower = [kw.lower() for kw in keywords_list if kw.strip()] - - # 先按关键词过滤(仅对 content 字段进行匹配) - filtered_keyword: List[Jargon] = [] - for r in candidates: - content = (r.content or "").lower() - - # 只要命中任意一个关键词即可视为匹配(OR 逻辑) - any_matched = False - for kw in keywords_lower: - if not kw: - continue - if kw in content: - any_matched = True - break - - if any_matched: - filtered_keyword.append(r) - - if global_config.expression.all_global_jargon: - # 全局黑话模式:不再做 chat_id 过滤,直接使用关键词过滤结果 - records = filtered_keyword - else: - # 非全局模式:仅保留全局黑话或 chat_id 列表中包含当前 chat_id 的记录 - records = [] - for r in filtered_keyword: - if r.is_global: - records.append(r) - continue - chat_id_list = parse_chat_id_list(r.chat_id) - if chat_id_list_contains(chat_id_list, chat_id): - records.append(r) - - if not records: - scope_note = ( - "(当前为全局黑话模式,仅统计 is_global=True 的条目)" - if global_config.expression.all_global_jargon - else "(当前为按 chat_id 作用域模式,仅统计全局黑话或与当前 chat_id 相关的条目)" - ) - return f"未找到包含关键词'{keyword}'的 Jargon 记录{scope_note}" - - lines: List[str] = [] - for r in records: - is_jargon_str = "是" if r.is_jargon else "否" if r.is_jargon is False else "未判定" - is_global_str = "全局" if r.is_global else "非全局" - lines.append( - f"ID={r.id} | 内容={r.content} | 含义={r.meaning or '无'} | " - f"chat_id={r.chat_id} | {is_global_str} | 是否黑话={is_jargon_str}" - ) - - result = "\n".join(lines) - logger.info(f"[dream][tool] search_jargon 返回 {len(records)} 条记录") - return result - except Exception as e: - logger.error(f"search_jargon 失败: {e}") - return f"search_jargon 执行失败: {e}" - - return search_jargon diff --git a/src/dream/tools/update_chat_history_tool.py b/src/dream/tools/update_chat_history_tool.py deleted file mode 100644 index 2853dddd..00000000 --- a/src/dream/tools/update_chat_history_tool.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Any, Dict, Optional - -from src.common.logger import get_logger -from src.common.database.database_model import ChatHistory -from src.services import database_service as database_api - -logger = get_logger("dream_agent") - - -def make_update_chat_history(chat_id: str): # chat_id 目前未直接使用,预留以备扩展 - async def update_chat_history( - memory_id: int, - theme: Optional[str] = None, - summary: Optional[str] = None, - keywords: Optional[str] = None, - ) -> str: - """按字段更新 chat_history(字符串字段要求 JSON 的字段须传入已序列化的字符串)""" - try: - logger.info( - f"[dream][tool] 调用 update_chat_history(memory_id={memory_id}, " - f"theme={bool(theme)}, summary={bool(summary)}, keywords={bool(keywords)})" - ) - record = ChatHistory.get_or_none(ChatHistory.id == memory_id) - if not record: - msg = f"未找到 ID={memory_id} 的 ChatHistory 记录,无法更新。" - logger.info(f"[dream][tool] update_chat_history 未找到记录: {msg}") - return msg - - data: Dict[str, Any] = {} - if theme is not None: - data["theme"] = theme - if summary is not None: - data["summary"] = summary - if keywords is not None: - data["keywords"] = keywords - - if not data: - return "未提供任何需要更新的字段。" - - await database_api.db_save(ChatHistory, data=data, key_field="id", key_value=memory_id) - msg = f"已更新 ChatHistory 记录 ID={memory_id},更新字段={list(data.keys())}。" - logger.info(f"[dream][tool] update_chat_history 完成: {msg}") - return msg - except Exception as e: - logger.error(f"update_chat_history 失败: {e}") - return f"update_chat_history 执行失败: {e}" - - return update_chat_history diff --git a/src/dream/tools/update_jargon_tool.py b/src/dream/tools/update_jargon_tool.py deleted file mode 100644 index 7ef17cb6..00000000 --- a/src/dream/tools/update_jargon_tool.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any, Dict, Optional - -from src.common.logger import get_logger -from src.common.database.database_model import Jargon -from src.services import database_service as database_api - -logger = get_logger("dream_agent") - - -def make_update_jargon(chat_id: str): # chat_id 目前未直接使用,预留以备扩展 - async def update_jargon( - jargon_id: int, - meaning: Optional[str] = None, - is_global: Optional[bool] = None, - is_jargon: Optional[bool] = None, - content: Optional[str] = None, - ) -> str: - """按字段更新 Jargon 记录,可用于修正含义、调整全局性、标记是否为黑话等""" - try: - logger.info( - f"[dream][tool] 调用 update_jargon(jargon_id={jargon_id}, " - f"meaning={bool(meaning)}, is_global={is_global}, is_jargon={is_jargon}, content={bool(content)})" - ) - record = Jargon.get_or_none(Jargon.id == jargon_id) - if not record: - msg = f"未找到 ID={jargon_id} 的 Jargon 记录,无法更新。" - logger.info(f"[dream][tool] update_jargon 未找到记录: {msg}") - return msg - - data: Dict[str, Any] = {} - if meaning is not None: - data["meaning"] = meaning - if is_global is not None: - data["is_global"] = is_global - if is_jargon is not None: - data["is_jargon"] = is_jargon - if content is not None: - data["content"] = content - - if not data: - return "未提供任何需要更新的字段。" - - await database_api.db_save(Jargon, data=data, key_field="id", key_value=jargon_id) - msg = f"已更新 Jargon 记录 ID={jargon_id},更新字段={list(data.keys())}。" - logger.info(f"[dream][tool] update_jargon 完成: {msg}") - return msg - except Exception as e: - logger.error(f"update_jargon 失败: {e}") - return f"update_jargon 执行失败: {e}" - - return update_jargon diff --git a/src/main.py b/src/main.py index 8b43c9f8..9d8e06a6 100644 --- a/src/main.py +++ b/src/main.py @@ -23,7 +23,6 @@ from src.plugin_runtime.integration import get_plugin_runtime_manager # 导入消息API和traceback模块 from src.common.message_server import get_global_api -from src.dream.dream_agent import start_dream_scheduler from src.bw_learner.expression_auto_check_task import ExpressionAutoCheckTask from src.prompt.prompt_manager import prompt_manager @@ -145,7 +144,6 @@ class MainSystem: try: tasks = [ emoji_manager.periodic_emoji_maintenance(), - start_dream_scheduler(), self.app.run(), self.server.run(), ] diff --git a/src/services/__init__.py b/src/services/__init__.py index 74bd007b..7d6f8337 100644 --- a/src/services/__init__.py +++ b/src/services/__init__.py @@ -2,6 +2,6 @@ 核心服务层 提供与具体插件系统无关的核心业务服务。 -内部模块(chat、dream、memory 等)应直接使用此层, +内部模块(chat、memory 等)应直接使用此层, 而 plugin_system.apis 仅作为面向插件的薄包装。 """