mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
fix(agent): 工具循环占位不再驱动重复发送,输出回执/子任务结果幂等
生产现象:单轮内 output_send__qq 被调用 34 次、持续 514 秒,直到 QQ 插件
自己的循环保险拒绝发送才停下(problem.md)。根因是多环节叠加,核心侧修四处:
1. 工具轮补位文案(process.go)
通用占位「请根据以上工具结果继续。」对纯输出通道调用是错的:异步通道
(qq/wechat)的回复只能经 output_send__* 交付,所以模型「已完成回复」的
表达形式就是一个工具调用,紧随其后的「请继续」会被读成「还要再做一步」,
而能做的「一步」恰好还是再发一条消息。
改为按上一批工具的性质选文案:全部是 output_send__* 时补
「若你的回复已完成,直接返回纯文本即可结束本轮,无需再调用任何工具。」
同时每轮先移除旧占位再补一条,避免占位在 prompt 前缀里线性累积。
(该占位是 zen 网关「最后一条必须是 user」的传输层附加物,HEAD 版本是
无条件内联追加、从不移除。)
2. 输出成功回执(output.go)
「已通过 [qq] 通道发送: map[status:sent]」这类富回执会被读成「这步成功,
继续下一步」。成功改为只回极简标记。
3. proc 桥标量透传(internal/plugin/proc/plugin.go)
插件返回 "ok" 时不再伪造 {status:sent} 覆盖插件真实返回值,否则只改
output.go 不生效。
4. 子任务结果幂等(spawn.go)
child_result 原先读到即删,而完成通知长期留在持久上下文里
(formatMergedTimeline 每轮重新注入),第二次查询必然得到
「不存在或已过期」这个永久失败信号,模型据此认为任务未完成而反复重试。
改为保留结果 + delivered 标记,重复查询返回明确提示;结果按上限有界淘汰。
顺带:agent.go 去掉文档层显式向量器注入(TF-IDF 已内置为 fallback),
cmd/homed/main.go 同步 document.NewStore 的 tokenizer 参数。
测试:internal/agent/core/tooloop_test.go(5 例)、spawn_test.go(3 例)。
This commit is contained in:
@ -98,8 +98,15 @@ type Agent struct {
|
||||
// 子任务异步执行
|
||||
childMu sync.Mutex
|
||||
childNextID int64
|
||||
childResults map[string]string
|
||||
childRunning map[string]bool // 运行中的子任务(child_result 查询时区分'运行中'与'不存在')
|
||||
// childTasks 记录子任务状态:运行中 / 结果 / 是否已交付。
|
||||
//
|
||||
// 为什么保留结果而不是“读到即删”:完成通知会写进持久上下文
|
||||
// (formatMergedTimeline 每轮都重新注入),模型之后还会再查。若读到即删,
|
||||
// 第二次查询就得到“不存在或已过期”这个**永久失败信号**——模型据此认为
|
||||
// 任务未完成,会无限重试/汇报(实测单轮 35 次工具调用、持续 514 秒)。
|
||||
childTasks map[string]*childTaskState
|
||||
// childSeq 给完成的任务排个序,用于有界淘汰。
|
||||
childSeq int64
|
||||
|
||||
// 高优先级打断通道:interceptLoop 注入,process() 在工具循环轮次间非阻塞读取
|
||||
interceptCh chan *agentIO.InputEvent
|
||||
@ -227,8 +234,7 @@ func New(cfg AgentConfig) *Agent {
|
||||
embedder = memory.NewStaticEmbedder(strings.Split(cfg.EmbeddingModelPath, ",")...)
|
||||
}
|
||||
if cfg.DocStore != nil {
|
||||
cfg.DocStore.SetVectorizer(embedder)
|
||||
cfg.DocStore.ReindexWithVectorizer(embedder)
|
||||
// TF-IDF 内置为 fallback,无需外部注入
|
||||
}
|
||||
if cfg.Knowledge != nil {
|
||||
cfg.Knowledge.SetVectorizer(embedder)
|
||||
@ -293,8 +299,7 @@ func New(cfg AgentConfig) *Agent {
|
||||
skillIndex: cfg.SkillIndexProvider,
|
||||
eventBus: cfg.EventBus,
|
||||
selfInputCh: make(chan selfInputMsg, 64),
|
||||
childResults: make(map[string]string),
|
||||
childRunning: make(map[string]bool),
|
||||
childTasks: make(map[string]*childTaskState),
|
||||
interceptCh: make(chan *agentIO.InputEvent, 64),
|
||||
pluginHealth: newPluginHealthTracker(),
|
||||
thinkingEnabled: cfg.ThinkingEnabled,
|
||||
|
||||
Reference in New Issue
Block a user