根因(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」红
44 lines
2.0 KiB
Go
44 lines
2.0 KiB
Go
package tool
|
||
|
||
import "context"
|
||
|
||
// 本文件承载「本回合角色」的 context 通道(切片 KB1)。
|
||
//
|
||
// 背景:一个 Run 里每轮要回答的**角色**由循环解析(loop 的 personaFor:
|
||
// 会话环境的显式角色,否则循环配置的默认角色),提示词、记忆注入与情绪
|
||
// 都按它取值。工具侧此前拿不到这个角色,于是 knowledge 工具把可选参数
|
||
// `persona` 缺席时的空串直接透给记忆检索器,由后者以 ErrMissingPersona
|
||
// 硬失败 —— 模型既不知道该填哪个角色 id(参数说明还写着 optional),
|
||
// 也就只能对同一堵墙反复重试(安卓侧实测的连续两次
|
||
// 「工具调用失败 · knowledge」)。
|
||
//
|
||
// 通道形态与 allowset.go 同族:循环在 Run 顶部播种一次,工具经
|
||
// Dispatcher.Invoke 的 ctx(它由本轮 ctx 派生)读取。身份与权限**不**
|
||
// 走这条通道 —— 它只是「本回合以谁的名义说话」这个作用域元数据,与
|
||
// SessionEnv 的注释同一纪律(项目红线:身份由 actor 元数据 + 策略引擎
|
||
// 决定,绝不从这里推断)。
|
||
|
||
// turnPersonaKey 是本回合角色的 context key。
|
||
type turnPersonaKey struct{}
|
||
|
||
// WithTurnPersona 把本回合角色挂到 ctx 上(循环在 Run 顶部调用一次)。
|
||
// 空角色是空操作:不挂载即「本回合没有解析出角色」,读取侧返回空串,
|
||
// 让依赖角色作用域的工具据此给出可消化的拒绝,而不是把空角色透给
|
||
// 下游存储换回一条无从纠正的错误。
|
||
func WithTurnPersona(ctx context.Context, personaID string) context.Context {
|
||
if ctx == nil || personaID == "" {
|
||
return ctx
|
||
}
|
||
return context.WithValue(ctx, turnPersonaKey{}, personaID)
|
||
}
|
||
|
||
// TurnPersonaFrom 返回 ctx 上挂载的本回合角色;未挂载(或 ctx 为 nil)时
|
||
// 返回空串。
|
||
func TurnPersonaFrom(ctx context.Context) string {
|
||
if ctx == nil {
|
||
return ""
|
||
}
|
||
s, _ := ctx.Value(turnPersonaKey{}).(string)
|
||
return s
|
||
}
|