package model import "context" // 本文件实现「固定角色」的 LLM 栈视图(裁决 03.4.1 的 per-persona provider: // 跨协议换 adapter)。 // // 为什么需要它:跨协议的 per-persona 路由要求「请求携带 persona」才能选链, // 而 persona 只存在于 loop 的会话环境里(挂在 loop 自己的私有 ctx 键上), // 且 model 不能反向 import loop。但 loop 取适配器的位置恰好是**按角色**的: // `loop.streamStep` 用 `ag.Adapter()`,而 agent 池是一角色一个 agent // (design/22 §4.2 方案 A)。于是「这个 agent 服务哪个角色」由装配处一次性 // 决定、由适配器视图携带,是唯一不外泄、可测、且不动 loop/agent 结构的载体。 // // 视图只做两件事:把角色贴到每个请求上(PersonaID),并按该角色路由模型名; // 选链与降级仍由 RuntimeLLM 完成(它看不到 loop 的角色解析结果)。 // PersonaScoped 由「固定服务某个角色」的适配器视图实现。agent 的非流式路径 // (internal/runtime/agent/request.go)读它,好让同一个 persona 钩子在 // RunStep 与流式主路径上都成立。空字符串表示该视图不绑定角色。 type PersonaScoped interface { // ScopedPersona 返回该视图固定的角色 id。 ScopedPersona() string } // PersonaStack 是 LLM 栈的角色视图:它实现 Adapter(贴角色后委托给底层栈) // 与 ModelRouter(按角色路由模型名)。把它当作某一角色的 agent 的 // adapter 与 router 注入,该 agent 的每个请求就都带上了这个角色。 // // 它是无状态的,可安全并发使用。 type PersonaStack struct { persona string inner *RuntimeLLM } // ForPersona 返回服务 persona 的栈视图。空白 persona 返回一个不绑定角色的 // 视图(等价于底层栈),使「没解析出角色」的装配处保持既有行为。 func (m *RuntimeLLM) ForPersona(persona string) *PersonaStack { return &PersonaStack{persona: trimKey(persona), inner: m} } // ScopedPersona 实现 PersonaScoped。 func (p *PersonaStack) ScopedPersona() string { return p.persona } // Name 实现 Adapter。带上角色后缀,使 CallRecord 与日志能分辨是哪个角色在 // 说话;未绑定角色时与底层栈同名。 func (p *PersonaStack) Name() string { if p.persona == "" { return p.inner.Name() } return p.inner.Name() + ":" + p.persona } // Chat 实现 Adapter:贴上角色后委托给底层栈。 func (p *PersonaStack) Chat(ctx context.Context, req Request, opts Options) (Response, error) { req.PersonaID = p.persona return p.inner.Chat(ctx, req, opts) } // ChatStream 实现 Adapter:贴上角色后委托给底层栈。 func (p *PersonaStack) ChatStream(ctx context.Context, req Request, onEvent func(StreamEvent)) (Response, error) { req.PersonaID = p.persona return p.inner.ChatStream(ctx, req, onEvent) } // RouteRequest 实现 ModelRouter:按该视图的角色路由模型名,未绑定角色时 // RouteRequestForPersona 会回落到消息 role 的同一条老路。它刻意不实现 // PersonaRouter:loop 在拿到真实角色时自己调 RouteRequestForPersona, // 视图再顶一层只会让「谁的角色说了算」含糊。 func (p *PersonaStack) RouteRequest(req Request) (string, bool) { return p.inner.RouteRequestForPersona(p.persona, req) }