通道:新增 model/session_ctx.go 的 WithSessionID / SessionIDFrom(与 tool.WithTurnPersona
同族的 ctx 范式,避开 model 反向 import loop 的环),loop 在 Run 顶部播种;适配器
record(ctx,...) 接会话;usageSessionArg 空串映射为 NULL。
兼容:sink 未注入或会话未播种时行为逐字不变;既有 model 测试零断言改动即全绿。
文件:model/{session_ctx.go(新), usage.go, usage_store.go, openai.go, anthropic.go,
usage_session_test.go(新)} + loop/{run.go, usage_session_test.go(新)}。
变异三处:去 loop 打标 / 写侧不绑会话 / 适配器不读 ctx ⇒ 各自红行,还原复绿。
契约与日志字段集未动;读侧不按 session 过滤,面板汇总口径不变。
50 lines
2.6 KiB
Go
50 lines
2.6 KiB
Go
package model
|
||
|
||
import "context"
|
||
|
||
// 本文件承载「本次调用属于哪个会话」的 context 通道(切片 U1 留的接线缺口)。
|
||
//
|
||
// 背景:模型适配器跑在 loop 之下、没有会话概念,于是 `llm_usage.session_id`
|
||
// 一直是 NULL —— 列与索引在切片 U1 已建好(迁移 0017),缺的是**从循环边界
|
||
// 把会话标识带到适配器边界**的那一跳(usage.go / usage_store.go 此前都留了
|
||
// 注释说明这一点)。没有这一跳,「哪次调用贵在哪个会话身上」这个问题永远答不出来。
|
||
//
|
||
// 通道形态与 tool/turncontext.go、loop/session_ctx.go 同族:key 由**消费方**
|
||
// (本包)持有,上游 loop 在 Run 顶部播种一次;此后所有经该 ctx 发起的模型调用
|
||
// —— 流式的 ChatStream 与非流式的 Chat(agent.RunStep)**两条**路径,以及
|
||
// fallback 链、persona 栈这些原样透传 ctx 的包装 —— 在适配器内部都能读到它。
|
||
// 反过来(key 放在 loop 里)做不到:本包在 loop 之下,导入 loop 会成环(铁律:
|
||
// 单二进制内模块间只走进程内接口 + 依赖注入)。
|
||
//
|
||
// 为什么不挂在 model.Request 上:Request 是要编码成线格式发给提供商的那份边界
|
||
// 对象,而会话标识属于**本地记账元数据** —— 挂上去就等于给「将来某个适配器顺手
|
||
// 把它序列化出去」留了口子,且 buildRequest 之外的调用点(后台流程、直接调用
|
||
// 适配器的包装)全都要各自记得填。ctx 值只在进程内流动,覆盖面还更全。
|
||
//
|
||
// 纪律:它只是记账标签,**绝不**参与身份/权限判定(红线:身份由 channel 层
|
||
// actor 元数据 + 策略引擎决定,绝不靠任何透传字段推断)。未播种时读到空串,
|
||
// 落库据此写 NULL 而不是空串。
|
||
|
||
// sessionIDKey 是本次模型调用所属会话 id 的 context key。
|
||
type sessionIDKey struct{}
|
||
|
||
// WithSessionID 把会话 id 挂到 ctx 上(循环在 Run 顶部调用一次)。
|
||
//
|
||
// 空 id 是空操作:不挂载即「本次调用没有会话归属」(管理面板试连、未接循环的
|
||
// 后台流程),读取侧返回空串,用量记 NULL —— 与接线前逐字一致。
|
||
func WithSessionID(ctx context.Context, sessionID string) context.Context {
|
||
if ctx == nil || sessionID == "" {
|
||
return ctx
|
||
}
|
||
return context.WithValue(ctx, sessionIDKey{}, sessionID)
|
||
}
|
||
|
||
// SessionIDFrom 返回 ctx 上挂载的会话 id;未挂载(或 ctx 为 nil)时返回空串。
|
||
func SessionIDFrom(ctx context.Context) string {
|
||
if ctx == nil {
|
||
return ""
|
||
}
|
||
s, _ := ctx.Value(sessionIDKey{}).(string)
|
||
return s
|
||
}
|