mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
Revert "fix(memory): 中断的取件样板不再写入 L1(颗粒度=单条消息)"
This reverts commit 3a7440a2fe.
This commit is contained in:
@ -410,15 +410,6 @@ func (c *RelevanceContext) Save() error {
|
|||||||
// (另一处 InsertByTimestamp 同样处理)。所以明文拦截放在这里,
|
// (另一处 InsertByTimestamp 同样处理)。所以明文拦截放在这里,
|
||||||
// 而不是在每个调用点 —— 逐个补必漏(实测踩到:只拦了 f.Msgs,
|
// 而不是在每个调用点 —— 逐个补必漏(实测踩到:只拦了 f.Msgs,
|
||||||
// 而 f.CleanInput / f.ToolResults / f.Response 都是独立副本)。
|
// 而 f.CleanInput / f.ToolResults / f.Response 都是独立副本)。
|
||||||
// snapshotEvents 返回当前事件的浅拷贝(判据/诊断用;调用方不得修改元素)。
|
|
||||||
func (c *RelevanceContext) snapshotEvents() []*ContextEvent {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
out := make([]*ContextEvent, len(c.events))
|
|
||||||
copy(out, c.events)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RelevanceContext) Append(evt ContextEvent) {
|
func (c *RelevanceContext) Append(evt ContextEvent) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
// 中断**消息本身**不得进入记忆(用户口径,2026-10-27,颗粒度=单条消息)。
|
|
||||||
//
|
|
||||||
// 中断的 CleanInput 是触发文本("来自「X」的私聊消息(message_id=…)先用
|
|
||||||
// qq_get_message 取正文…"),它是指令不是用户的话。存进 L1 的后果实测:
|
|
||||||
// 时间线里每轮都是同一句样板,真正的对话内容只剩工具回执碎片 ——
|
|
||||||
// 用户回一句 "233719",模型却问"那串数字什么意思"(指代消解失效)。
|
|
||||||
//
|
|
||||||
// 但**本轮的工具回执与回复必须照常入库**:qq_get_message 的正文各自声明
|
|
||||||
// NoMemory=false,那才是要记的内容。判据就是这两条一起成立。
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newInterruptTestContext 建一个不落盘的 L1(判据只看内存态)。
|
|
||||||
func newInterruptTestContext() *RelevanceContext {
|
|
||||||
return NewRelevanceContext("", memory.NewStaticEmbedder(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterruptTriggerTextNotStoredButToolResultsAre(t *testing.T) {
|
|
||||||
rc := newInterruptTestContext()
|
|
||||||
turn := ContextEvent{
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Source: "agent",
|
|
||||||
Input: "", // finishInputTask 对中断轮置空
|
|
||||||
Response: "已经回你了。",
|
|
||||||
ToolResults: []ToolResultItem{{Name: "qq_get_message", Output: `{"content":"233719"}`}},
|
|
||||||
}
|
|
||||||
rc.Append(turn)
|
|
||||||
|
|
||||||
got := rc.snapshotEvents()
|
|
||||||
if len(got) != 1 {
|
|
||||||
t.Fatalf("应写入一条事件,实际 %d", len(got))
|
|
||||||
}
|
|
||||||
if got[0].Input != "" {
|
|
||||||
t.Fatalf("中断触发文本不得入库,实际 Input=%q", got[0].Input)
|
|
||||||
}
|
|
||||||
if len(got[0].ToolResults) != 1 {
|
|
||||||
t.Fatalf("工具回执必须保留(内容侧),实际 %+v", got[0].ToolResults)
|
|
||||||
}
|
|
||||||
if got[0].Response == "" {
|
|
||||||
t.Fatal("agent 回复应保留")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只清"取件指令样板",内核中断的正文必须保留(测试 D1=B:中断上下文要
|
|
||||||
// 加载回被抢占任务之上)。
|
|
||||||
func TestInjectionBoilerplateOnly(t *testing.T) {
|
|
||||||
boiler := "来自「—/」的私聊消息(message_id=814968683, user_id=2198972886)。先用qq_get_message(message_id=814968683)取正文;若取不到(消息已过期),改用qq_get_history(user_id=2198972886)按会话拉取上下文。用output_send__qq回复对方"
|
|
||||||
if !isInjectionBoilerplate(boiler) {
|
|
||||||
t.Fatal("插件注入的取件样板应被识别")
|
|
||||||
}
|
|
||||||
if isInjectionBoilerplate("紧急打断:立刻停下手上的活") {
|
|
||||||
t.Fatal("内核中断的正文不是样板,不得清空")
|
|
||||||
}
|
|
||||||
if isInjectionBoilerplate("帮我看下日志") {
|
|
||||||
t.Fatal("普通输入不得被判成样板")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 非中断轮必须原样保留用户输入(不能被同一条规则误伤)。
|
|
||||||
func TestNormalTurnKeepsInput(t *testing.T) {
|
|
||||||
rc := newInterruptTestContext()
|
|
||||||
rc.Append(ContextEvent{Timestamp: time.Now(), Source: "webui", Input: "帮我看下日志"})
|
|
||||||
got := rc.snapshotEvents()
|
|
||||||
if len(got) != 1 || got[0].Input != "帮我看下日志" {
|
|
||||||
t.Fatalf("普通轮输入不能被清空,实际 %+v", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -528,26 +528,6 @@ func (a *Agent) prepareInputTask(evt *agentIO.InputEvent) (*TaskFrame, taskTermi
|
|||||||
return f, terminalNone
|
return f, terminalNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInjectionBoilerplate 判断一段中断文本是否只是**取件指令样板**。
|
|
||||||
//
|
|
||||||
// 为什么不是"所有中断都清空":内核中断(如 overflow 抢占、用户 L4 打断)的
|
|
||||||
// 正文本身是有意义的内容,测试 D1=B 明确要求「中断任务的上下文要加载回被抢占
|
|
||||||
// 任务之上」。真正污染记忆的是插件注入的那句样板:
|
|
||||||
//
|
|
||||||
// 来自「X」的私聊消息(message_id=…) 先用 qq_get_message 取正文;若取不到…
|
|
||||||
//
|
|
||||||
// 它每轮一字不差,存进 L1 只会把真实内容挤掉(实测导致指代消解失效)。
|
|
||||||
func isInjectionBoilerplate(text string) bool {
|
|
||||||
t := strings.TrimSpace(text)
|
|
||||||
if t == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(t, "来自「") && strings.Contains(t, "message_id=") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return strings.Contains(t, "取正文") && strings.Contains(t, "先用")
|
|
||||||
}
|
|
||||||
|
|
||||||
// finishInputTask 执行 processInput 的后半段(日志、上下文提交、回执、记忆候选)。
|
// finishInputTask 执行 processInput 的后半段(日志、上下文提交、回执、记忆候选)。
|
||||||
//
|
//
|
||||||
// 只在任务真正结束时调用一次——这正是不变量 I5(每任务恰一次终态)的落点。
|
// 只在任务真正结束时调用一次——这正是不变量 I5(每任务恰一次终态)的落点。
|
||||||
@ -578,23 +558,10 @@ func (a *Agent) finishInputTask(f *TaskFrame, out stepOutcome) {
|
|||||||
|
|
||||||
// 本轮捕获的媒体一起挂到这条事件上:用户上传的、插件注入的,以及模型调
|
// 本轮捕获的媒体一起挂到这条事件上:用户上传的、插件注入的,以及模型调
|
||||||
// multimodal_see_picture / see_video 时经 SetToolBlocks 注入的。
|
// multimodal_see_picture / see_video 时经 SetToolBlocks 注入的。
|
||||||
// ★ 中断**消息本身**不进记忆(2026-10-27 用户口径,颗粒度=单条消息,不是单轮):
|
|
||||||
// 中断的 CleanInput 是"来自「X」的私聊消息(message_id=…)先用 qq_get_message 取正文…"
|
|
||||||
// 这类触发文本 —— 它是给模型的指令,不是用户说的话。存进 L1 的后果:
|
|
||||||
// a) 记忆里堆满同一句样板(每轮一字不差),时间线里全是它;
|
|
||||||
// b) 真正的对话内容反而只能靠工具回执片段体现 → 指代消解失效
|
|
||||||
// (实测:用户回"233719",模型问"那串数字什么意思")。
|
|
||||||
//
|
|
||||||
// 所以只清掉**这条消息**:Input 置空,而本轮的工具回执(qq_get_message 正文等,
|
|
||||||
// 它们各自声明 NoMemory=false)与 agent 的回复照常入库 —— 内容该记的照记。
|
|
||||||
input := f.CleanInput
|
|
||||||
if f.IsInterrupt && isInjectionBoilerplate(input) {
|
|
||||||
input = ""
|
|
||||||
}
|
|
||||||
turnEvt := ContextEvent{
|
turnEvt := ContextEvent{
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
Source: "agent",
|
Source: "agent",
|
||||||
Input: input,
|
Input: f.CleanInput,
|
||||||
Response: f.Response,
|
Response: f.Response,
|
||||||
ToolsUsed: f.ToolsUsed,
|
ToolsUsed: f.ToolsUsed,
|
||||||
ToolResults: f.ToolResults,
|
ToolResults: f.ToolResults,
|
||||||
|
|||||||
Reference in New Issue
Block a user