Files
AskaEth 59a1ac4056 feat(model): llm_usage.session_id 真正落库(补完 U1 缺口)
通道:新增 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 过滤,面板汇总口径不变。
2026-10-05 08:04:49 +08:00

50 lines
2.6 KiB
Go
Raw Permalink 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 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
}