From 04ce3388477404f1b6493bec7ce5af64e9813079 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Tue, 16 Dec 2025 22:57:56 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(unified=5Fmanager):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=AE=B0=E5=BF=86=E6=A3=80=E7=B4=A2=E8=AF=84=E4=BC=B0?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=A2=9E=E5=BC=BA=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=84=8F=E5=9B=BE=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/llm_models/utils_model.py | 2 +- src/memory_graph/unified_manager.py | 50 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py index 1e4b975c6..2d35f6a8a 100644 --- a/src/llm_models/utils_model.py +++ b/src/llm_models/utils_model.py @@ -979,7 +979,7 @@ class LLMRequest: def _resolve_system_prompt(self, model_set: TaskConfig) -> str | None: """确定是否需要附加统一的system prompt.""" try: - if model_config and model_set is model_config.model_task_config.replyer: + if model_config and (model_set is model_config.model_task_config.replyer or model_set is model_config.model_task_config.replyer_private): return SYSTEM_PROMPT except AttributeError: logger.debug("模型配置缺少replyer定义,无法注入系统提示词") diff --git a/src/memory_graph/unified_manager.py b/src/memory_graph/unified_manager.py index 42d64a659..170ce3eab 100644 --- a/src/memory_graph/unified_manager.py +++ b/src/memory_graph/unified_manager.py @@ -328,41 +328,41 @@ class UnifiedMemoryManager: """ - prompt = f"""你是一个记忆检索评估专家。你的任务是判断当前检索到的“感知记忆”(即时对话)和“短期记忆”(结构化信息)是否足以支撑一次有深度、有上下文的回复。 + prompt = f"""你是“记忆判官”(记忆检索评估专家)。你的任务是:基于给定的历史消息、当前消息,以及我们已经检索到的“感知记忆”和“短期记忆”,判断是否还需要检索“长期记忆”(LTM)来支撑一次准确、完整、上下文一致的回复。 -**核心原则:** -- **适当检索长期记忆有助于提升回复质量。** 当对话涉及到特定话题、人物、事件或需要回忆过去的经历时,应该检索长期记忆。 -- **判断标准:** 只有当现有记忆无法理解用户意图,或无法形成基本、连贯的回复时,才认为“不充足”。检索长期记忆耗时。除非有需要,否则不要检索。 -- **如果用户在讨论某个具体话题,即使现有记忆有一些相关信息,也可以检索长期记忆来补充更多背景。** +**总体偏好(重要):** +- 我们宁可多花一点资源去检索长期记忆,也不要在本该检索时漏检索。 +- 因此:只要存在明显不确定、信息缺口、或需要更精确细节的情况,就倾向于判定“现有记忆不充足”(`is_sufficient: false`)。 -**用户查询:** +**输入:** +**当前用户消息:** {query} -{chat_history_block}**检索到的感知记忆(即时对话,格式:【时间 (聊天流)】消息列表):** +{chat_history_block}**已检索到的感知记忆(即时对话,格式:【时间 (聊天流)】消息列表):** {perceptual_desc or '(无)'} -**检索到的短期记忆(结构化信息,自然语言描述):** +**已检索到的短期记忆(结构化信息,自然语言描述):** {short_term_desc or '(无)'} -**评估指南:** -1. **分析用户意图**:用户在聊什么?是简单闲聊还是有具体话题? -2. **检查现有记忆**: - - 对于闲聊、打招呼、无特定主题的互动 → 现有记忆充足 (`is_sufficient: true`)。 - - 如果涉及具体话题(人物、事件、知识),但现有记忆能提供基本信息 → 现有记忆充足 (`is_sufficient: true`)。 - - 仅当用户明确问及过去的特定事件,或当前信息完全无法理解用户意图时 → 现有记忆不充足 (`is_sufficient: false`)。 +**什么时候必须检索长期记忆(满足任一条 → `is_sufficient: false`):** +1. **用户明确要求回忆/找回过去信息**:例如“你还记得…?”“上次我们说到…?”“帮我回忆一下…/之前…/那天…/某次…” +2. **你对答案没有把握或存在不确定性**:例如无法确定人物/事件/时间/地点/偏好/承诺/任务细节,或只能给出模糊猜测。 +3. **现有记忆不足以给出精确回答**:要给出具体结论、细节、步骤、承诺、时间线、决定依据,但感知/短期记忆缺少关键事实。 +4. **对话依赖用户个体历史**:涉及用户的个人信息、偏好、长期目标、过往经历、已约定事项、持续进行的项目/任务,需要更早的上下文才能回答。 +5. **指代不清或背景缺失**:出现“那个/那件事/他/她/它/之前说的/你知道的”等省略指代,现有记忆不足以唯一指向。 +6. **记忆冲突或碎片化**:感知/短期记忆之间存在矛盾、时间线断裂、或信息片段无法拼成完整图景。 -**输出格式(JSON):** -```json -{{ - "is_sufficient": true/false, - "confidence": 0.85, - "reasoning": "在这里解释你的判断理由。例如:‘用户只是在打招呼,现有记忆已足够,无需检索长期记忆。’或‘用户问到了一个具体的历史事件,现有记忆完全没有相关信息,必须检索长期记忆。’", - "missing_aspects": ["缺失的信息1", "缺失的信息2"], - "additional_queries": ["补充query1", "补充query2"] -}} -``` +**什么时候可以不检索(同时满足全部条件 → `is_sufficient: true`):** +- 用户只是闲聊/打招呼/情绪表达/泛化问题(不依赖用户个人历史),且现有记忆已足以给出可靠且一致的回复; +- 你能在不猜测的情况下回答,且不需要更早的细节来保证准确性。 -请输出JSON:""" +**输出要求(JSON):** +- `is_sufficient`: `true` 表示“无需检索长期记忆”;`false` 表示“需要检索长期记忆” +- `confidence`: 0~1,表示你对该判断的把握;若你偏向检索但仍不确定,也应输出较低/中等置信度并保持 `is_sufficient: false` +- `missing_aspects`: 列出阻碍精确回答的缺失点(可为空数组) +- `additional_queries`: 给出 1~5 条用于检索长期记忆的补充 query(尽量短、可检索、包含关键实体/事件/时间线线索;可为空数组) + +请仅输出 JSON(可以用 ```json 包裹,也可以直接输出纯 JSON):""" # 调用记忆裁判模型 if not model_config.model_task_config: From 936d5d3a0e7a39a7be987cd0e241c8adc0dde978 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Tue, 16 Dec 2025 23:40:11 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor(short=5Fterm=5Fmanager):=20?= =?UTF-8?q?=E7=AE=80=E5=8C=96=E7=9F=AD=E6=9C=9F=E8=AE=B0=E5=BF=86=E8=BD=AC?= =?UTF-8?q?=E7=A7=BB=E7=AD=96=E7=95=A5=EF=BC=8C=E4=BB=85=E5=9C=A8=E6=BB=A1?= =?UTF-8?q?=E9=A2=9D=E6=97=B6=E6=95=B4=E6=89=B9=E8=BD=AC=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memory_graph/short_term_manager.py | 55 ++---------- src/memory_graph/short_term_pressure_patch.md | 13 ++- src/memory_graph/unified_manager.py | 86 +++++-------------- 3 files changed, 36 insertions(+), 118 deletions(-) diff --git a/src/memory_graph/short_term_manager.py b/src/memory_graph/short_term_manager.py index 45911547c..0c598d6d6 100644 --- a/src/memory_graph/short_term_manager.py +++ b/src/memory_graph/short_term_manager.py @@ -639,54 +639,17 @@ class ShortTermMemoryManager: def get_memories_for_transfer(self) -> list[ShortTermMemory]: """ - 获取需要转移到长期记忆的记忆(改进版:转移优先于删除) + 获取需要转移到长期记忆的记忆(简化版:满额整批转移) - 优化的转移策略: - 1. 优先选择重要性 >= 阈值的记忆进行转移 - 2. 如果高重要性记忆已清空但仍超过容量,则考虑转移低重要性记忆 - 3. 仅当转移不能解决容量问题时,才进行强制删除(由 force_cleanup_overflow 处理) - - 返回: - 需要转移的记忆列表(优先返回高重要性,次选低重要性) + 策略: + - 当短期记忆数量达到上限(>= max_memories)时,返回当前全部短期记忆; + - 没满则返回空列表,不触发转移。 """ - # 单次遍历:同时分类高重要性和低重要性记忆 - high_importance_memories = [] - low_importance_memories = [] - - for mem in self.memories: - if mem.importance >= self.transfer_importance_threshold: - high_importance_memories.append(mem) - else: - low_importance_memories.append(mem) - - # 策略1:优先返回高重要性记忆进行转移 - if high_importance_memories: - logger.debug( - f"转移候选: 发现 {len(high_importance_memories)} 条高重要性记忆待转移" - ) - return high_importance_memories - - # 策略2:如果没有高重要性记忆但总体超过容量上限, - # 返回一部分低重要性记忆用于转移(而非删除) - if len(self.memories) > self.max_memories: - # 计算需要转移的数量(目标:降到上限) - num_to_transfer = len(self.memories) - self.max_memories - - # 按创建时间排序低重要性记忆,优先转移最早的(可能包含过时信息) - low_importance_memories.sort(key=lambda x: x.created_at) - to_transfer = low_importance_memories[:num_to_transfer] - - if to_transfer: - logger.debug( - f"转移候选: 发现 {len(to_transfer)} 条低重要性记忆待转移 " - f"(当前容量 {len(self.memories)}/{self.max_memories})" - ) - return to_transfer - - # 策略3:容量充足,无需转移 - logger.debug( - f"转移检查: 无需转移 (当前容量 {len(self.memories)}/{self.max_memories})" - ) + if self.max_memories <= 0: + return [] + if len(self.memories) >= self.max_memories: + logger.debug(f"转移候选: 短期记忆已满,准备整批转移 {len(self.memories)} 条") + return list(self.memories) return [] def force_cleanup_overflow(self, keep_ratio: float | None = None) -> int: diff --git a/src/memory_graph/short_term_pressure_patch.md b/src/memory_graph/short_term_pressure_patch.md index 10b97a167..093ef6d8d 100644 --- a/src/memory_graph/short_term_pressure_patch.md +++ b/src/memory_graph/short_term_pressure_patch.md @@ -1,15 +1,12 @@ -# 短期记忆压力泄压补丁 +# 短期记忆压力泄压补丁(已弃用) ## 📋 概述 -在高频消息场景下,短期记忆层(`ShortTermMemoryManager`)可能在自动转移机制触发前快速堆积大量记忆,当达到容量上限(`max_memories`)时可能阻塞后续写入。本功能提供一个**可选的泄压开关**,在容量溢出时自动删除低优先级记忆,防止系统阻塞。 +该文档描述的“泄压删除”与“复杂自动转移”机制已不再作为默认策略使用:现在短期记忆采用最简单策略——**只有当短期记忆满额时,才整批转移全部短期记忆到长期记忆;没满就不处理**。因此,本补丁说明仅供历史参考。 -**关键特性**: -- ✅ 默认开启(在高频场景中保护系统),可关闭保持向后兼容 -- ✅ 基于重要性和时间的智能删除策略 -- ✅ 异步持久化,不阻塞主流程 -- ✅ 可通过配置文件或代码灵活控制 -- ✅ 支持自定义保留比例 +**当前行为(简化版)**: +- ✅ 短期记忆未满:不触发转移 +- ✅ 短期记忆满额:一次性整批转移全部短期记忆到长期记忆 --- diff --git a/src/memory_graph/unified_manager.py b/src/memory_graph/unified_manager.py index 170ce3eab..04a82b0db 100644 --- a/src/memory_graph/unified_manager.py +++ b/src/memory_graph/unified_manager.py @@ -112,8 +112,6 @@ class UnifiedMemoryManager: self._initialized = False self._auto_transfer_task: asyncio.Task | None = None self._auto_transfer_interval = max(10.0, float(long_term_auto_transfer_interval)) - # 优化:降低最大延迟时间,加快转移节奏 (原为 300.0) - self._max_transfer_delay = min(max(30.0, self._auto_transfer_interval), 60.0) self._transfer_wakeup_event: asyncio.Event | None = None logger.info("统一记忆管理器已创建") @@ -574,11 +572,7 @@ class UnifiedMemoryManager: logger.debug("自动转移任务已启动并触发首次检查") async def _auto_transfer_loop(self) -> None: - """自动转移循环(批量缓存模式,优化:更高效的缓存管理)""" - transfer_cache: list[ShortTermMemory] = [] - cached_ids: set[str] = set() - cache_size_threshold = max(1, self._config["long_term"].get("batch_size", 1)) - last_transfer_time = time.monotonic() + """自动转移循环(简化版:短期记忆满额时整批转移)""" while True: try: @@ -595,67 +589,25 @@ class UnifiedMemoryManager: else: await asyncio.sleep(sleep_interval) - memories_to_transfer = self.short_term_manager.get_memories_for_transfer() - - if memories_to_transfer: - # 优化:批量构建缓存而不是逐条添加 - new_memories = [] - for memory in memories_to_transfer: - mem_id = getattr(memory, "id", None) - if not (mem_id and mem_id in cached_ids): - new_memories.append(memory) - if mem_id: - cached_ids.add(mem_id) - - if new_memories: - transfer_cache.extend(new_memories) - logger.debug( - f"自动转移缓存: 新增{len(new_memories)}条, 当前缓存{len(transfer_cache)}/{cache_size_threshold}" - ) - + # 最简单策略:仅当短期记忆满额时,直接整批转移全部短期记忆;没满则不处理 max_memories = max(1, getattr(self.short_term_manager, "max_memories", 1)) - occupancy_ratio = len(self.short_term_manager.memories) / max_memories - time_since_last_transfer = time.monotonic() - last_transfer_time + if len(self.short_term_manager.memories) < max_memories: + continue - if occupancy_ratio >= 1.0 and not transfer_cache: - removed = self.short_term_manager.force_cleanup_overflow() - if removed > 0: - logger.warning( - f"短期记忆占用率 {occupancy_ratio:.0%},已强制删除 {removed} 条低重要性记忆泄压" - ) + batch = list(self.short_term_manager.memories) + if not batch: + continue - # 优化:优先级判断重构(早期 return) - should_transfer = ( - len(transfer_cache) >= cache_size_threshold - or occupancy_ratio >= 0.5 - or (transfer_cache and time_since_last_transfer >= self._max_transfer_delay) - or len(self.short_term_manager.memories) >= self.short_term_manager.max_memories + logger.info( + f"短期记忆已满({len(batch)}/{max_memories}),开始整批转移到长期记忆" ) + result = await self.long_term_manager.transfer_from_short_term(batch) - if should_transfer and transfer_cache: - logger.debug( - f"准备批量转移: {len(transfer_cache)}条短期记忆到长期记忆 (占用率 {occupancy_ratio:.0%})" + if result.get("transferred_memory_ids"): + await self.short_term_manager.clear_transferred_memories( + result["transferred_memory_ids"] ) - - # 优化:直接传递列表而不再复制 - result = await self.long_term_manager.transfer_from_short_term(transfer_cache) - - if result.get("transferred_memory_ids"): - transferred_ids = set(result["transferred_memory_ids"]) - await self.short_term_manager.clear_transferred_memories( - result["transferred_memory_ids"] - ) - - # 优化:使用生成器表达式保留未转移的记忆 - transfer_cache = [ - m - for m in transfer_cache - if getattr(m, "id", None) not in transferred_ids - ] - cached_ids.difference_update(transferred_ids) - - last_transfer_time = time.monotonic() - logger.debug(f"✅ 批量转移完成: {result}") + logger.debug(f"✅ 整批转移完成: {result}") except asyncio.CancelledError: logger.debug("自动转移循环被取消") @@ -674,10 +626,16 @@ class UnifiedMemoryManager: await self.initialize() try: - memories_to_transfer = self.short_term_manager.get_memories_for_transfer() + max_memories = max(1, getattr(self.short_term_manager, "max_memories", 1)) + if len(self.short_term_manager.memories) < max_memories: + return { + "message": f"短期记忆未满({len(self.short_term_manager.memories)}/{max_memories}),不触发转移", + "transferred_count": 0, + } + memories_to_transfer = list(self.short_term_manager.memories) if not memories_to_transfer: - return {"message": "没有需要转移的记忆", "transferred_count": 0} + return {"message": "短期记忆为空,无需转移", "transferred_count": 0} # 执行转移 result = await self.long_term_manager.transfer_from_short_term(memories_to_transfer) From 61b372b23fb2abc7cb7d110280d9af6374f6cf17 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Wed, 17 Dec 2025 11:51:52 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(bot=5Fconfig=5Ftemplate):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=9F=AD=E6=9C=9F=E8=AE=B0=E5=BF=86=E6=BA=A2?= =?UTF-8?q?=E5=87=BA=E7=AD=96=E7=95=A5=E7=9A=84=E6=B3=A8=E9=87=8A=E5=92=8C?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- template/bot_config_template.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 3db0cd02c..3a759a911 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -312,9 +312,6 @@ short_term_transfer_threshold = 0.6 # 转移到长期记忆的重要性阈值 short_term_enable_force_cleanup = true # 开启压力泄压(建议高频场景开启) short_term_search_top_k = 5 # 搜索时返回的最大数量 short_term_decay_factor = 0.98 # 衰减因子 -short_term_overflow_strategy = "transfer_all" # 短期记忆溢出策略 -# "transfer_all": 一次性转移所有记忆到长期记忆,然后删除低重要性记忆(默认推荐) -# "selective_cleanup": 选择性清理,仅转移高重要性记忆,直接删除低重要性记忆 # 长期记忆层配置 use_judge = true # 使用评判模型决定是否检索长期记忆