feat(config,runtime): 上下文预算按模型窗口推导 + usage/缓存可观测 + sidecar 存全文(T1)
- 窗口声明:models.json 组级 contextWindow + env LLM_CONTEXT_WINDOW(per-group materialize 播种,0 不写盘、负数报错)。 - 预算三态:CONTEXT_MAX_TOKENS 未设置 ⇒ 按窗口推导;>0 显式覆盖;0|负 关闭压缩(不变)。推导 budget = min(窗口×比例, 窗口−输出保留),比例默认 2/3(CONTEXT_BUDGET_RATIO,1M ⇒ 699050 tokens ≈ 683Ki);输出保留 = max(32768, 该组 maxTokens) 且封顶窗口/2(防配置矛盾时退化成 1 token);窗口未声明 ⇒ 回落 131072 + 恰好一条 WARN。接线在组合根 wire_context.go(唯一决策点)。 - usage/缓存可观测(此前**完全不可观测**):Response 增 CacheHit/CacheMiss/Reasoning tokens + CacheHitRate;解析 OpenAI 兼容(prompt_cache_hit_tokens/prompt_cache_miss_tokens/cached_tokens/reasoning_tokens)与 anthropic(cache_read_input_tokens/cache_creation_input_tokens)两种形状;记录点在适配器 record(换入口不丢成本日志),日志无凭据;单次输入 ≥预算 90% 记 WARN(不阻断)。 - 根治清单「续读」假承诺:抽取期不再按注入预算截断(sidecar 存全文,只受 MaxSidecarBytes=8 MiB 安全上限,ExtractorVersion v1→v2 旧 sidecar 失效重抽);注入预算改在装配期生效 ⇒ ref.Chars/Truncated 是注入侧事实、游标真是继续读的起点;document-read 的 limit 上限改为独立具名常量。 - 顺手修越界:config.go 505 行(超 500)拆出 llm.go。 - 主会话独立验收:隔离 worktree(HEAD + 本切片 39 文件,排除在途 gateway)build/vet 绿、全树 59 包 0 FAIL、linux+darwin 交叉编译绿;变异独立复现(比例改 0.25 ⇒ 推导用例红 262144 vs 699050)。
This commit is contained in:
1 parent
5b2b385524
commit
72b95df22b
39 files changed
+2496
-377
No files matched your search
@@ -97,10 +97,29 @@ type anthropicBlock struct {
|
||||
}
|
||||
|
||||
// anthropicUsage 上报 token 消耗。Anthropic 上报的是 input_tokens
|
||||
// 与 output_tokens,而不是单一总量。
|
||||
// 与 output_tokens,而不是单一总量;提示缓存另有两个字段
|
||||
// (cache_creation_input_tokens = 本次写入缓存的输入 token,
|
||||
// cache_read_input_tokens = 本次从缓存读到的输入 token)。它们全都容忍缺失:
|
||||
// 未启用缓存的端点只会给前两个。
|
||||
type anthropicUsage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
CacheCreationInputTokens int `json:"cache_creation_input_tokens"`
|
||||
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
||||
}
|
||||
|
||||
// applyAnthropicUsage 把一个 usage 对象折进 Response(只填用量字段,
|
||||
// input_tokens 与 output_tokens 的既有口径不变)。缓存两档的对应关系:
|
||||
// 命中 = cache_read_input_tokens;未命中 = input_tokens + cache_creation_input_tokens
|
||||
// —— Anthropic 的 input_tokens **不含**缓存读到的部分,若只取它,命中率会被算成 0。
|
||||
func applyAnthropicUsage(out *Response, u *anthropicUsage) {
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
out.PromptTokens = u.InputTokens
|
||||
out.CompletionTokens = u.OutputTokens
|
||||
out.CacheHitTokens = u.CacheReadInputTokens
|
||||
out.CacheMissTokens = u.InputTokens + u.CacheCreationInputTokens
|
||||
}
|
||||
|
||||
// AnthropicChatAdapter 调用 Anthropic Messages(/v1/messages)端点。
|
||||
@@ -206,7 +225,7 @@ func (a *AnthropicChatAdapter) Chat(ctx context.Context, req Request, opts Optio
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
a.record(req, start, out.TotalTokens)
|
||||
a.record(req, start, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -263,7 +282,7 @@ func (a *AnthropicChatAdapter) ChatStream(ctx context.Context, req Request, onEv
|
||||
return Response{}, err
|
||||
}
|
||||
out.Model = model
|
||||
a.record(req, start, out.TotalTokens)
|
||||
a.record(req, start, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -381,14 +400,17 @@ func (a *AnthropicChatAdapter) upstreamError(resp *http.Response) error {
|
||||
return fmt.Errorf("model: %w: status %d: %s", ErrUpstream, resp.StatusCode, strings.TrimSpace(string(snippet)))
|
||||
}
|
||||
|
||||
func (a *AnthropicChatAdapter) record(req Request, start time.Time, tokens int) {
|
||||
// record 上报一次成功调用的用量:先记一条结构化用量日志(含缓存命中率,
|
||||
// 所有调用点都覆盖 —— 见 usage.go),再喂给可选的 CallRecord 计账出口。
|
||||
func (a *AnthropicChatAdapter) record(req Request, start time.Time, resp Response) {
|
||||
logUsage(a.log, a.name, req, resp, time.Since(start))
|
||||
if a.recorder == nil {
|
||||
return
|
||||
}
|
||||
a.recorder(CallRecord{
|
||||
Model: req.Model,
|
||||
Purpose: req.Purpose,
|
||||
Tokens: tokens,
|
||||
Tokens: resp.TotalTokens,
|
||||
TookMs: time.Since(start).Milliseconds(),
|
||||
})
|
||||
}
|
||||
@@ -413,10 +435,9 @@ func responseFromAnthropicWire(wire *anthropicResponse) Response {
|
||||
out.Message = Message{Role: RoleAssistant, Content: []Content{{Type: ContentTypeText, Text: out.Content}}}
|
||||
out.ToolCalls = toolCalls
|
||||
out.StopReason = wire.StopReason
|
||||
if u := wire.Usage; u != nil {
|
||||
out.PromptTokens = u.InputTokens
|
||||
out.CompletionTokens = u.OutputTokens
|
||||
out.TotalTokens = u.InputTokens + u.OutputTokens
|
||||
applyAnthropicUsage(&out, wire.Usage)
|
||||
if wire.Usage != nil {
|
||||
out.TotalTokens = wire.Usage.InputTokens + wire.Usage.OutputTokens
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user