mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 18:38:11 +00:00
把「踢出去(prune)」与「取进来(recall)」从两处各写一遍,收敛为 memoryPass(query, trigger, prune, recall) 单一入口,统一: - 同一份清洗后的 query(避免噪声带偏相关性打分); - 同一次 token 预算与召回截断; - 同一条带 trigger 的审计日志(谁、据什么触发了哪种操作)。 落地: - 新增 memorypass.go:memoryPass + pruneByQuery(原 pruneOnInput 的执行体); - pruneOnInput 只解析声明,执行委托 memoryPass; - stepToolAfter 的裁剪/召回改为一次 memoryPass 调用(去掉重复的 topK 逻辑); - 抽出 recallText,输入侧 buildTaskMemoryContext 与工具侧 recallTextFor 共用; - 输入侧召回 query 改用 CleanInput(清洗文本),与裁剪侧同一语义; - QQ qq_get_history 补齐声明 ContextPolicy=prune + RecallPolicy=auto (内容类工具:真实聊天正文既当轮用完即裁,又据正文召回)。 测试:新增 memorypass_test.go,锁死 no-op / 两轴同时生效 / 正交不互相触发 / 输入侧用清洗 query。go build/vet 干净,internal/... 全绿,qq 插件模块测试通过。
66 lines
2.8 KiB
Go
66 lines
2.8 KiB
Go
package core
|
||
|
||
import "log"
|
||
|
||
// memoryPassOut 是一次记忆操作(取进来 / 踢出去)的结果。
|
||
type memoryPassOut struct {
|
||
// Archived 是被归档进文档记忆的低相关 L0 事件数(prune 的输出)。
|
||
Archived int
|
||
// RecallText 是可注入 prompt 的记忆索引文本(recall 的输出,空串表示无)。
|
||
RecallText string
|
||
}
|
||
|
||
// memoryPass 是「取进来(召回)」与「踢出去(裁剪)」的**唯一入口**。
|
||
//
|
||
// prune 与 recall 是两根正交的声明轴(默认值刻意相反:裁剪是破坏性的、
|
||
// 默认关;召回是只读增量、默认开),但两者都建立在**同一份清洗后的 query**
|
||
// 之上。调用点只负责解析声明,这里统一做三件各写一遍就会写歪的事:
|
||
//
|
||
// 1. 同一 query:prune 与 recall 用同一个查询向量来源,避免「裁错事件、
|
||
// 召回错记忆」——原始内容里的 ANSI/base64/JSON 噪声会把相关性打分带偏。
|
||
// 2. 同一次预算:召回文本按 token 预算截断只做一次(见 recallText)。
|
||
// 3. 同一条审计:谁(trigger)据什么触发了哪种操作都落一条日志,
|
||
// 否则又是一个「幕后发生、查不出是谁」的机制(对齐 prune 的设计初衷)。
|
||
//
|
||
// 边界:prune 与 recall 目前仍走**各自的相关性空间**(prune 用 L0 事件的
|
||
// 稠密/词向量给已有事件打分,recall 用图 + TF-IDF 实体索引)。真正的
|
||
// 「一次打分」要先统一打分空间(后续步骤);这里统一的是**入口、query、
|
||
// 预算与审计**——这已是「一个过程」的可审计外壳,剩下的差在打分空间。
|
||
func (a *Agent) memoryPass(query, trigger string, prune, recall bool) memoryPassOut {
|
||
var out memoryPassOut
|
||
if a == nil || (!prune && !recall) {
|
||
return out
|
||
}
|
||
if prune {
|
||
out.Archived = a.pruneByQuery(query)
|
||
}
|
||
if recall && query != "" {
|
||
out.RecallText = a.recallTextFor(query, trigger)
|
||
}
|
||
if out.Archived > 0 || out.RecallText != "" {
|
||
log.Printf("[agent] memory pass (%s): archived=%d recalled=%d chars",
|
||
trigger, out.Archived, len(out.RecallText))
|
||
}
|
||
return out
|
||
}
|
||
|
||
// pruneByQuery 按相关性把低相关 L0 事件归档进文档记忆,返回归档数。
|
||
//
|
||
// **不做声明判定**——声明已由调用方(pruneOnInput / stepToolAfter)解析,
|
||
// 这里只负责执行。放在 memoryPass 内部是为了让裁剪与召回共享入口。
|
||
func (a *Agent) pruneByQuery(query string) int {
|
||
if a == nil || a.context == nil {
|
||
return 0
|
||
}
|
||
// **动态上下文**是父 agent 专属能力:轻量内核(驻留子)用传统上下文,
|
||
// 不做按相关度的裁剪与向 doc 记忆的归档(子也没有 doc 记忆)。
|
||
if a.isLightKernel() {
|
||
return 0
|
||
}
|
||
topK := a.maxContextSize - 1
|
||
if topK < 1 {
|
||
topK = 1
|
||
}
|
||
return a.context.Prune(query, topK, a.docStore)
|
||
}
|