Files
Cyrene_v2/internal/runtime/tool/turncontext.go
T
AskaEth 18381f65fd
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(gateway,loop,tools): 失败回合必落可见回复 + knowledge 角色可信
根因(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」红
2026-10-04 22:14:42 +08:00

44 lines
2.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}