mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +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:
@ -345,16 +345,30 @@ func (p *Plugin) invokeOutput(channel string, args map[string]interface{}) (inte
|
||||
return nil, err // 真实失败上报,模型可感知并重试
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return map[string]interface{}{"status": "sent"}, nil
|
||||
return map[string]interface{}{"status": "ok"}, nil
|
||||
}
|
||||
|
||||
var res map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &res); err != nil {
|
||||
return map[string]interface{}{"status": "sent"}, nil
|
||||
if err := json.Unmarshal(raw, &res); err == nil {
|
||||
if _, ok := res["status"]; !ok {
|
||||
res["status"] = "sent"
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
if _, ok := res["status"]; !ok {
|
||||
res["status"] = "sent"
|
||||
|
||||
// 插件返回的是标量(如 "ok")——**原样透传,不要伪造 status**。
|
||||
//
|
||||
// 为什么必须透传:核心 output.go 会把 map 结果格式化成富回执
|
||||
// ("已通过 [qq] 通道发送: map[status:sent]"),模型看到"发送成功 +
|
||||
// 详情"会把这一步当成"上一步完成、继续下一步"的信号,形成
|
||||
// output_send 回声循环。插件(如 qq)刻意返回极简的 "ok" 就是为了
|
||||
// 掐断这个信号;早期实现在这里把非 map 响应替换成 {status:sent},
|
||||
// 等于把它又变回富回执——**只改插件永远修不掉这个循环**。
|
||||
var scalar interface{}
|
||||
if err := json.Unmarshal(raw, &scalar); err != nil {
|
||||
return map[string]interface{}{"status": "ok"}, nil
|
||||
}
|
||||
return res, nil
|
||||
return scalar, nil
|
||||
}
|
||||
|
||||
// 编译期确认 Plugin 具备 registry 需要的启停形状。
|
||||
|
||||
Reference in New Issue
Block a user