feat(llm): 用量落库 + 只读汇总端点 + 面板用量小卡(契约 v0.64)
- model 层新增可选 UsageSink(nil ⇒ 逐字不变,既有用例零改动) - 组合根注入 UsageWriter:有界队列 1024 + 单写者 + 批量事务(128/批、2s 兜底), 队列满丢弃 + 限流 WARN,绝不阻塞模型调用;sink 挂 RuntimeLLM(热重载不断) - 迁移 0017:表 llm_usage(13 列,session_id 可空)+ idx_created_at + idx_session_id - 新端点 GET /api/v1/admin/llm/usage(owner|admin 读;缺省近 24h、上限 90 天、 超限 400 不静默裁剪;hitRate = cacheHit/(cacheHit+cacheMiss),分母 0 ⇒ 0; 绝不含凭据与正文;未接线/查库失败 ⇒ 500 usage_unavailable) - LLM_USAGE_RETENTION(默认 720h,≤0 = 关闭清理):启动清一次 + 每小时 - 面板 LLM 页「用量(近 24h)」小卡 + Docs 两份同步 v0.64 - 已知缺口(如实登记):session_id 恒 NULL,需 loop 侧在请求上打标,留列后接
This commit is contained in:
1 parent
18381f65fd
commit
be38651ba4
32 files changed
+2233
-44
No files matched your search
@@ -57,20 +57,26 @@ func NewChatChain(cfg config.LLMConfig) (*Chain, error) {
|
||||
// 配置好时才会追加;否则链保持两级形状,并
|
||||
// 上报原因。
|
||||
func NewChatChainWithLog(cfg config.LLMConfig, log *slog.Logger) (*Chain, error) {
|
||||
return newChainWith(cfg, log, nil)
|
||||
}
|
||||
|
||||
// newChainWith 是 NewChatChainWithLog 加上用量 sink(切片 U1)。sink 为 nil 时
|
||||
// 逐字等于后者:适配器只记日志。组合根经 RuntimeLLM 注入真实写入器。
|
||||
func newChainWith(cfg config.LLMConfig, log *slog.Logger, sink UsageSink) (*Chain, error) {
|
||||
var chain *Chain
|
||||
var err error
|
||||
switch cfg.Provider {
|
||||
case ProviderOpenAI, "":
|
||||
chain, err = newOpenAIChain(cfg, WithOpenAILogger(log))
|
||||
chain, err = newOpenAIChain(cfg, WithOpenAILogger(log), WithUsageSink(sink))
|
||||
case ProviderAnthropic:
|
||||
chain, err = newAnthropicChain(cfg, WithAnthropicLogger(log))
|
||||
chain, err = newAnthropicChain(cfg, WithAnthropicLogger(log), WithAnthropicUsageSink(sink))
|
||||
default:
|
||||
return nil, fmt.Errorf("model: %w: provider %q", ErrUnknownProvider, cfg.Provider)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if step, ok := localChainStep(cfg, log); ok {
|
||||
if step, ok := localChainStep(cfg, log, sink); ok {
|
||||
chain.steps = append(chain.steps, step)
|
||||
}
|
||||
return chain, nil
|
||||
@@ -80,8 +86,8 @@ func NewChatChainWithLog(cfg config.LLMConfig, log *slog.Logger) (*Chain, error)
|
||||
// 当该级关闭、未配置或其提供商未知时,返回 ok=false
|
||||
// (若该级被要求但配置不全,则附一条点出缺失字段的 WARN)
|
||||
// ——一个半配置的步骤只会失败,那还不如没有
|
||||
// 它。
|
||||
func localChainStep(cfg config.LLMConfig, log *slog.Logger) (Step, bool) {
|
||||
// 它。用量 sink 照旧透传:本地级也是模型调用,同样要计成本。
|
||||
func localChainStep(cfg config.LLMConfig, log *slog.Logger, sink UsageSink) (Step, bool) {
|
||||
local := cfg.Local
|
||||
// Configured() 本身已要求 Enabled,所以该级默认关闭,
|
||||
// 被禁用的级永远不会成为链步骤。只有当该级确实被
|
||||
@@ -102,9 +108,9 @@ func localChainStep(cfg config.LLMConfig, log *slog.Logger) (Step, bool) {
|
||||
}
|
||||
switch local.Provider {
|
||||
case ProviderAnthropic:
|
||||
return Step{Model: local.Model, Adapter: NewAnthropicChat(localCfg, WithAnthropicLogger(log))}, true
|
||||
return Step{Model: local.Model, Adapter: NewAnthropicChat(localCfg, WithAnthropicLogger(log), WithAnthropicUsageSink(sink))}, true
|
||||
case ProviderOpenAI, "":
|
||||
return Step{Model: local.Model, Adapter: NewOpenAIChat(localCfg, WithOpenAILogger(log))}, true
|
||||
return Step{Model: local.Model, Adapter: NewOpenAIChat(localCfg, WithOpenAILogger(log), WithUsageSink(sink))}, true
|
||||
default:
|
||||
warn(log, "local model rung skipped: unknown provider",
|
||||
"provider", local.Provider, "field", "LLM_LOCAL_PROVIDER")
|
||||
|
||||
Reference in new issue
Block a user