fix(gateway,loop,tools): 失败回合必落可见回复 + knowledge 角色可信
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

根因(agent-191,均给出文件:行):
1) knowledge 的 persona 在 schema 里是 optional,模型省略时工具把空串透给
   memory.Retriever → 硬校验 ErrMissingPersona → 模型只能重复调用同一堵墙。
   修:新增 tool/turncontext.go(loop 播种本回合角色),knowledge 依次取
   「参数 → 本回合角色 → 可消化拒绝」,拒绝文案明确「重试不会成功」。
2) persistAssistantReply 对空文本直接 return,而三条正常收尾出口(空回复 /
   轮次用尽 / 回合内部报错)都会给空文本 ⇒ 历史只剩 user 消息、客户端静默。
   修:新增 session_turn_reply.go 的 persistTurnReply(唯一落库入口 + 按原因
   的中文诚实说明);中断 / 陈旧代次 / 静默内部流程(backthink、子代理调度)
   仍不落库,真实文本行为逐字未改。

测试:knowledge_test 改断言 + turn_persona_test(新)+ session_turn_reply_test(新,7 条)
变异:去掉 loop 角色发布 ⇒ loop×2 + gateway「工具执行次数 = 0, want 2」红
This commit is contained in:
AskaEth committed 2026-10-04 22:14:42 +08:00
1 parent 3be70f8232
commit 18381f65fd
9 files changed
+838 -11

No files matched your search

@@ -124,6 +124,10 @@ func (o *Orchestrator) persistUserMessages(ctx context.Context, sessionID string
// 在未接线 history 存储,或该回合没有产出文本
// 时是空操作(被中断/出错的回合保持 res 为 nil)。该写入在一个全新的
// 后台 context 下运行,因此被取消的回合 context 绝不可能中断它。
//
// 它是**裸写入**,判定属于 persistTurnReply(session_turn_reply.go):收尾路径
// 经后者进入,由它保证「归本代次、且不是被取消的回合一定有一条可见消息」——
// 空文本在这里仍然是空操作,因此本函数绝不自作主张编造内容。
func (o *Orchestrator) persistAssistantReply(sessionID string, res *loop.Result, deviceID, mode, personaID string) {
if o.history == nil || res == nil || res.Text == "" {
return