mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 17:27:31 +00:00
fix(memory): 回填事件必须照常归档 —— 纠正 NoMemory 语义被错误扩大到存储层
用户指出:doc 召回部分需要回填记忆,因为 doc 召回后原文在 doc 层已经删除了。
## 我上一轮的错误(PR #22)
把「工具输出不进记忆」与「回填事件不归档」塞进了同一个 NoMemory 标记:
doc_query → Consume(L2 副本删除)→ 回填 L1(标记 NoMemory)
→ L1 裁掉它 → Prune 跳过归档(错误实现)
→ 三层都不在 ⇒ 内容永久丢失
Consume 已经把 L2 副本删掉,**L1 是唯一副本**。裁掉时不归档 = 丢数据。
这不是自激防护,这是把数据丢掉当成防护。
## 正确区分(两件事不能共用一个标记)
| 对象 | 进计算层 | 进归档 | 理由 |
|---|---|---|---|
| doc_query 的**工具返回值**(正文摘录) | ❌ | ❌ | 它是事件的摘录副本,再向量化 = 重复注入 ⇒ 自激 |
| **回填的事件本身**(原文) | ❌ | ✅ **必须** | L2 副本已删,L1 是唯一副本;不归档即丢失 |
## 修法
ContextEvent.NoMemory 语义定为「**不参与计算,照常归档**」,
与 ToolDef.NoMemory 一致:只影响计算层,不影响存储层。
自激的入口在"重新计算向量"那一侧(textForVector 返回空),
不在"归档"这一侧 —— 删掉 Prune 里的跳过逻辑。
## 判据(改写了那条断言错误行为的判据)
TestSelfExcite_RestoredEventsNotReArchived 现在断言三件事:
1. 回填事件带 NoMemory 标记且未被向量化(不参与计算)
2. 前提成立:L2 里确实已无该事件(Consume 删掉了)
3. ★ 被裁掉后必须能落回 L2(否则内容永久丢失)
变异验证:
- Prune 重新跳过 NoMemory ⇒ 红,报「三层都不在,内容永久丢失」
- restoreDocEvents 不置 NoMemory ⇒ 红(会重复加权)
## 验证
- go build ./... / go vet ./... 干净
- go test ./... -count=1:58 包 0 失败
This commit is contained in:
@ -327,9 +327,21 @@ func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall, channel string, turnS
|
||||
// 工具是动态注册的,"不存在"是常态而非异常(插件未加载/已卸载/崩溃)。
|
||||
// 文案必须让模型知道该做什么,而不是含糊的"执行失败"——
|
||||
// 后者会让模型反复重试同一个不存在的名字。
|
||||
return toolOutcome{Text: fmt.Sprintf("工具 %s 不存在或未注册:它可能属于未加载/已崩溃的插件。"+
|
||||
"先调 get_plugin_tools(\"\") 看当前可用工具,或 output_list_channels 看通道;"+
|
||||
"确认名称无误后再调用", tc.Name)}
|
||||
// ★ 提示必须**先指向 skill**(2026-10-22)。
|
||||
//
|
||||
// 按需插件(auto_start=false)的工具不在初始清单里,而它的
|
||||
// skill 正是"这个插件是什么、要怎么用、需要先启动什么"的唯一
|
||||
// 说明(SDK autostart.go 的 OnDemandNeedsSkill 就是这条契约)。
|
||||
//
|
||||
// 此前这里只提 get_plugin_tools / output_list_channels,
|
||||
// 一个字没提 skill —— 模型调用了、失败了,提示却不告诉它去
|
||||
// 看说明书,于是只能盲目试工具名。
|
||||
return toolOutcome{Text: fmt.Sprintf("工具 %s 不存在或未注册(它可能属于未加载的插件)。按这个顺序处理:\n"+
|
||||
"1. 先看【可用技能】里有没有相关的:有就先 skill_info(\"技能名\") 读全文 —— "+
|
||||
"skill 里写着该插件的正确用法、已知坑,以及需要先启动哪个插件;\n"+
|
||||
"2. 没有相关 skill 就调 get_plugin_tools(\"\") 查当前可用的插件与工具;\n"+
|
||||
"3. 通道相关的不确定时用 output_list_channels 看有哪些通道。\n"+
|
||||
"确认之后再调用,不要反复重试同一个名字。", tc.Name)}
|
||||
}
|
||||
return toolOutcome{Text: fmt.Sprintf("工具 %s 执行失败: %v", tc.Name, err)}
|
||||
}
|
||||
@ -1305,13 +1317,15 @@ func (a *Agent) restoreDocEvents(d *document.Doc) int {
|
||||
Source: ev.Source,
|
||||
Input: input,
|
||||
Response: response,
|
||||
// ★ 回填事件**不参与记忆计算、不再归档回 L2**。
|
||||
// ★ 回填事件**不参与记忆计算**,但**照常参与归档**。
|
||||
//
|
||||
// 它们本来就在记忆体系里(来自 L2,可能已蒸馏进 L3)。
|
||||
// 不标记的后果是自激震荡:
|
||||
// doc_query 从 L2 取出 → Prune 因相关而保留它们、
|
||||
// 把真正的新事件挤掉 → 下一轮 Prune 又归档回 L2 →
|
||||
// 再被 doc_query 召回 ⇒ 块在 L1↔L2 之间反复搬迁。
|
||||
// 为什么要标记:它们的内容刚在 L2 里被向量化过一次,
|
||||
// 再算一次会让同一内容在向量空间里被重复加权(自激)。
|
||||
//
|
||||
// ★★ 为什么仍要归档(不能顺手关掉):
|
||||
// Consume 已经把 L2 副本删了,L1 是**唯一副本**。
|
||||
// 裁掉时不归档 = 三层都不在 = 内容永久丢失。
|
||||
// (这个错犯过:曾用同一个 NoMemory 同时关掉计算与归档。)
|
||||
NoMemory: true,
|
||||
ToolsUsed: append([]string(nil), ev.ToolsUsed...),
|
||||
ToolResults: func() []ToolResultItem {
|
||||
|
||||
Reference in New Issue
Block a user