Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox-Core into dev
This commit is contained in:
@@ -979,7 +979,7 @@ class LLMRequest:
|
|||||||
def _resolve_system_prompt(self, model_set: TaskConfig) -> str | None:
|
def _resolve_system_prompt(self, model_set: TaskConfig) -> str | None:
|
||||||
"""确定是否需要附加统一的system prompt."""
|
"""确定是否需要附加统一的system prompt."""
|
||||||
try:
|
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
|
return SYSTEM_PROMPT
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.debug("模型配置缺少replyer定义,无法注入系统提示词")
|
logger.debug("模型配置缺少replyer定义,无法注入系统提示词")
|
||||||
|
|||||||
@@ -645,97 +645,17 @@ class ShortTermMemoryManager:
|
|||||||
|
|
||||||
def get_memories_for_transfer(self) -> list[ShortTermMemory]:
|
def get_memories_for_transfer(self) -> list[ShortTermMemory]:
|
||||||
"""
|
"""
|
||||||
获取需要转移到长期记忆的记忆
|
获取需要转移到长期记忆的记忆(简化版:满额整批转移)
|
||||||
|
|
||||||
根据 overflow_strategy 选择不同的转移策略:
|
策略:
|
||||||
- "transfer_all": 一次性转移所有记忆(满容量时),然后删除低重要性记忆
|
- 当短期记忆数量达到上限(>= max_memories)时,返回当前全部短期记忆;
|
||||||
- "selective_cleanup": 仅转移高重要性记忆,低重要性记忆直接删除
|
- 没满则返回空列表,不触发转移。
|
||||||
|
|
||||||
返回:
|
|
||||||
需要转移的记忆列表
|
|
||||||
"""
|
"""
|
||||||
if self.overflow_strategy == "transfer_all":
|
if self.max_memories <= 0:
|
||||||
return self._get_transfer_all_strategy()
|
|
||||||
else: # "selective_cleanup" 或其他值默认使用选择性清理
|
|
||||||
return self._get_selective_cleanup_strategy()
|
|
||||||
|
|
||||||
def _get_transfer_all_strategy(self) -> list[ShortTermMemory]:
|
|
||||||
"""
|
|
||||||
"一次性转移所有"策略:当短期记忆满了以后,将所有记忆转移到长期记忆
|
|
||||||
|
|
||||||
返回:
|
|
||||||
需要转移的记忆列表(满容量时返回所有记忆)
|
|
||||||
"""
|
|
||||||
# 如果短期记忆已满或接近满,一次性转移所有记忆
|
|
||||||
if len(self.memories) >= self.max_memories:
|
|
||||||
logger.info(
|
|
||||||
f"转移策略(transfer_all): 短期记忆已满 ({len(self.memories)}/{self.max_memories}),"
|
|
||||||
f"将转移所有 {len(self.memories)} 条记忆到长期记忆"
|
|
||||||
)
|
|
||||||
return self.memories.copy()
|
|
||||||
|
|
||||||
# 如果还没满,检查是否有高重要性记忆需要转移
|
|
||||||
high_importance_memories = [
|
|
||||||
mem for mem in self.memories
|
|
||||||
if mem.importance >= self.transfer_importance_threshold
|
|
||||||
]
|
|
||||||
|
|
||||||
if high_importance_memories:
|
|
||||||
logger.debug(
|
|
||||||
f"转移策略(transfer_all): 发现 {len(high_importance_memories)} 条高重要性记忆待转移"
|
|
||||||
)
|
|
||||||
return high_importance_memories
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"转移策略(transfer_all): 无需转移 (当前容量 {len(self.memories)}/{self.max_memories})"
|
|
||||||
)
|
|
||||||
return []
|
return []
|
||||||
|
if len(self.memories) >= self.max_memories:
|
||||||
def _get_selective_cleanup_strategy(self) -> list[ShortTermMemory]:
|
logger.debug(f"转移候选: 短期记忆已满,准备整批转移 {len(self.memories)} 条")
|
||||||
"""
|
return list(self.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"转移策略(selective): 发现 {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"转移策略(selective): 发现 {len(to_transfer)} 条低重要性记忆待转移 "
|
|
||||||
f"(当前容量 {len(self.memories)}/{self.max_memories})"
|
|
||||||
)
|
|
||||||
return to_transfer
|
|
||||||
|
|
||||||
# 策略3:容量充足,无需转移
|
|
||||||
logger.debug(
|
|
||||||
f"转移策略(selective): 无需转移 (当前容量 {len(self.memories)}/{self.max_memories})"
|
|
||||||
)
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def force_cleanup_overflow(self, keep_ratio: float | None = None) -> int:
|
def force_cleanup_overflow(self, keep_ratio: float | None = None) -> int:
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
# 短期记忆压力泄压补丁
|
# 短期记忆压力泄压补丁(已弃用)
|
||||||
|
|
||||||
## 📋 概述
|
## 📋 概述
|
||||||
|
|
||||||
在高频消息场景下,短期记忆层(`ShortTermMemoryManager`)可能在自动转移机制触发前快速堆积大量记忆,当达到容量上限(`max_memories`)时可能阻塞后续写入。本功能提供一个**可选的泄压开关**,在容量溢出时自动删除低优先级记忆,防止系统阻塞。
|
该文档描述的“泄压删除”与“复杂自动转移”机制已不再作为默认策略使用:现在短期记忆采用最简单策略——**只有当短期记忆满额时,才整批转移全部短期记忆到长期记忆;没满就不处理**。因此,本补丁说明仅供历史参考。
|
||||||
|
|
||||||
**关键特性**:
|
**当前行为(简化版)**:
|
||||||
- ✅ 默认开启(在高频场景中保护系统),可关闭保持向后兼容
|
- ✅ 短期记忆未满:不触发转移
|
||||||
- ✅ 基于重要性和时间的智能删除策略
|
- ✅ 短期记忆满额:一次性整批转移全部短期记忆到长期记忆
|
||||||
- ✅ 异步持久化,不阻塞主流程
|
|
||||||
- ✅ 可通过配置文件或代码灵活控制
|
|
||||||
- ✅ 支持自定义保留比例
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -114,8 +114,6 @@ class UnifiedMemoryManager:
|
|||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._auto_transfer_task: asyncio.Task | None = None
|
self._auto_transfer_task: asyncio.Task | None = None
|
||||||
self._auto_transfer_interval = max(10.0, float(long_term_auto_transfer_interval))
|
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
|
self._transfer_wakeup_event: asyncio.Event | None = None
|
||||||
|
|
||||||
logger.info("统一记忆管理器已创建")
|
logger.info("统一记忆管理器已创建")
|
||||||
@@ -330,41 +328,41 @@ class UnifiedMemoryManager:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prompt = f"""你是一个记忆检索评估专家。你的任务是判断当前检索到的“感知记忆”(即时对话)和“短期记忆”(结构化信息)是否足以支撑一次有深度、有上下文的回复。
|
prompt = f"""你是“记忆判官”(记忆检索评估专家)。你的任务是:基于给定的历史消息、当前消息,以及我们已经检索到的“感知记忆”和“短期记忆”,判断是否还需要检索“长期记忆”(LTM)来支撑一次准确、完整、上下文一致的回复。
|
||||||
|
|
||||||
**核心原则:**
|
**总体偏好(重要):**
|
||||||
- **适当检索长期记忆有助于提升回复质量。** 当对话涉及到特定话题、人物、事件或需要回忆过去的经历时,应该检索长期记忆。
|
- 我们宁可多花一点资源去检索长期记忆,也不要在本该检索时漏检索。
|
||||||
- **判断标准:** 只有当现有记忆无法理解用户意图,或无法形成基本、连贯的回复时,才认为“不充足”。检索长期记忆耗时。除非有需要,否则不要检索。
|
- 因此:只要存在明显不确定、信息缺口、或需要更精确细节的情况,就倾向于判定“现有记忆不充足”(`is_sufficient: false`)。
|
||||||
- **如果用户在讨论某个具体话题,即使现有记忆有一些相关信息,也可以检索长期记忆来补充更多背景。**
|
|
||||||
|
|
||||||
**用户查询:**
|
**输入:**
|
||||||
|
**当前用户消息:**
|
||||||
{query}
|
{query}
|
||||||
|
|
||||||
{chat_history_block}**检索到的感知记忆(即时对话,格式:【时间 (聊天流)】消息列表):**
|
{chat_history_block}**已检索到的感知记忆(即时对话,格式:【时间 (聊天流)】消息列表):**
|
||||||
{perceptual_desc or '(无)'}
|
{perceptual_desc or '(无)'}
|
||||||
|
|
||||||
**检索到的短期记忆(结构化信息,自然语言描述):**
|
**已检索到的短期记忆(结构化信息,自然语言描述):**
|
||||||
{short_term_desc or '(无)'}
|
{short_term_desc or '(无)'}
|
||||||
|
|
||||||
**评估指南:**
|
**什么时候必须检索长期记忆(满足任一条 → `is_sufficient: false`):**
|
||||||
1. **分析用户意图**:用户在聊什么?是简单闲聊还是有具体话题?
|
1. **用户明确要求回忆/找回过去信息**:例如“你还记得…?”“上次我们说到…?”“帮我回忆一下…/之前…/那天…/某次…”
|
||||||
2. **检查现有记忆**:
|
2. **你对答案没有把握或存在不确定性**:例如无法确定人物/事件/时间/地点/偏好/承诺/任务细节,或只能给出模糊猜测。
|
||||||
- 对于闲聊、打招呼、无特定主题的互动 → 现有记忆充足 (`is_sufficient: true`)。
|
3. **现有记忆不足以给出精确回答**:要给出具体结论、细节、步骤、承诺、时间线、决定依据,但感知/短期记忆缺少关键事实。
|
||||||
- 如果涉及具体话题(人物、事件、知识),但现有记忆能提供基本信息 → 现有记忆充足 (`is_sufficient: true`)。
|
4. **对话依赖用户个体历史**:涉及用户的个人信息、偏好、长期目标、过往经历、已约定事项、持续进行的项目/任务,需要更早的上下文才能回答。
|
||||||
- 仅当用户明确问及过去的特定事件,或当前信息完全无法理解用户意图时 → 现有记忆不充足 (`is_sufficient: false`)。
|
5. **指代不清或背景缺失**:出现“那个/那件事/他/她/它/之前说的/你知道的”等省略指代,现有记忆不足以唯一指向。
|
||||||
|
6. **记忆冲突或碎片化**:感知/短期记忆之间存在矛盾、时间线断裂、或信息片段无法拼成完整图景。
|
||||||
|
|
||||||
**输出格式(JSON):**
|
**什么时候可以不检索(同时满足全部条件 → `is_sufficient: true`):**
|
||||||
```json
|
- 用户只是闲聊/打招呼/情绪表达/泛化问题(不依赖用户个人历史),且现有记忆已足以给出可靠且一致的回复;
|
||||||
{{
|
- 你能在不猜测的情况下回答,且不需要更早的细节来保证准确性。
|
||||||
"is_sufficient": true/false,
|
|
||||||
"confidence": 0.85,
|
|
||||||
"reasoning": "在这里解释你的判断理由。例如:‘用户只是在打招呼,现有记忆已足够,无需检索长期记忆。’或‘用户问到了一个具体的历史事件,现有记忆完全没有相关信息,必须检索长期记忆。’",
|
|
||||||
"missing_aspects": ["缺失的信息1", "缺失的信息2"],
|
|
||||||
"additional_queries": ["补充query1", "补充query2"]
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
请输出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:
|
if not model_config.model_task_config:
|
||||||
@@ -576,11 +574,7 @@ class UnifiedMemoryManager:
|
|||||||
logger.debug("自动转移任务已启动并触发首次检查")
|
logger.debug("自动转移任务已启动并触发首次检查")
|
||||||
|
|
||||||
async def _auto_transfer_loop(self) -> None:
|
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:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -597,67 +591,25 @@ class UnifiedMemoryManager:
|
|||||||
else:
|
else:
|
||||||
await asyncio.sleep(sleep_interval)
|
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))
|
max_memories = max(1, getattr(self.short_term_manager, "max_memories", 1))
|
||||||
occupancy_ratio = len(self.short_term_manager.memories) / max_memories
|
if len(self.short_term_manager.memories) < max_memories:
|
||||||
time_since_last_transfer = time.monotonic() - last_transfer_time
|
continue
|
||||||
|
|
||||||
if occupancy_ratio >= 1.0 and not transfer_cache:
|
batch = list(self.short_term_manager.memories)
|
||||||
removed = self.short_term_manager.force_cleanup_overflow()
|
if not batch:
|
||||||
if removed > 0:
|
continue
|
||||||
logger.warning(
|
|
||||||
f"短期记忆占用率 {occupancy_ratio:.0%},已强制删除 {removed} 条低重要性记忆泄压"
|
logger.info(
|
||||||
|
f"短期记忆已满({len(batch)}/{max_memories}),开始整批转移到长期记忆"
|
||||||
)
|
)
|
||||||
|
result = await self.long_term_manager.transfer_from_short_term(batch)
|
||||||
# 优化:优先级判断重构(早期 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
|
|
||||||
)
|
|
||||||
|
|
||||||
if should_transfer and transfer_cache:
|
|
||||||
logger.debug(
|
|
||||||
f"准备批量转移: {len(transfer_cache)}条短期记忆到长期记忆 (占用率 {occupancy_ratio:.0%})"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 优化:直接传递列表而不再复制
|
|
||||||
result = await self.long_term_manager.transfer_from_short_term(transfer_cache)
|
|
||||||
|
|
||||||
if result.get("transferred_memory_ids"):
|
if result.get("transferred_memory_ids"):
|
||||||
transferred_ids = set(result["transferred_memory_ids"])
|
|
||||||
await self.short_term_manager.clear_transferred_memories(
|
await self.short_term_manager.clear_transferred_memories(
|
||||||
result["transferred_memory_ids"]
|
result["transferred_memory_ids"]
|
||||||
)
|
)
|
||||||
|
logger.debug(f"✅ 整批转移完成: {result}")
|
||||||
# 优化:使用生成器表达式保留未转移的记忆
|
|
||||||
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}")
|
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.debug("自动转移循环被取消")
|
logger.debug("自动转移循环被取消")
|
||||||
@@ -676,10 +628,16 @@ class UnifiedMemoryManager:
|
|||||||
await self.initialize()
|
await self.initialize()
|
||||||
|
|
||||||
try:
|
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:
|
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)
|
result = await self.long_term_manager.transfer_from_short_term(memories_to_transfer)
|
||||||
|
|||||||
@@ -312,9 +312,6 @@ short_term_transfer_threshold = 0.6 # 转移到长期记忆的重要性阈值
|
|||||||
short_term_enable_force_cleanup = true # 开启压力泄压(建议高频场景开启)
|
short_term_enable_force_cleanup = true # 开启压力泄压(建议高频场景开启)
|
||||||
short_term_search_top_k = 5 # 搜索时返回的最大数量
|
short_term_search_top_k = 5 # 搜索时返回的最大数量
|
||||||
short_term_decay_factor = 0.98 # 衰减因子
|
short_term_decay_factor = 0.98 # 衰减因子
|
||||||
short_term_overflow_strategy = "transfer_all" # 短期记忆溢出策略
|
|
||||||
# "transfer_all": 一次性转移所有记忆到长期记忆,然后删除低重要性记忆(默认推荐)
|
|
||||||
# "selective_cleanup": 选择性清理,仅转移高重要性记忆,直接删除低重要性记忆
|
|
||||||
|
|
||||||
# 长期记忆层配置
|
# 长期记忆层配置
|
||||||
use_judge = true # 使用评判模型决定是否检索长期记忆
|
use_judge = true # 使用评判模型决定是否检索长期记忆
|
||||||
|
|||||||
Reference in New Issue
Block a user