feat(config,runtime): 上下文预算按模型窗口推导 + usage/缓存可观测 + sidecar 存全文(T1)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 窗口声明: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:
AskaEth committed 2026-10-04 17:57:08 +08:00
1 parent 5b2b385524
commit 72b95df22b
39 files changed
+2496 -377

No files matched your search

+27
View File
@@ -267,6 +267,33 @@ type Response struct {
PromptTokens int
CompletionTokens int
TotalTokens int
// CacheHitTokens / CacheMissTokens 是**输入侧**的两档 token 数(design/04
// 附录 A.1:提供商按缓存命中/未命中两档计价,命中价约为未命中的 1/50)。
// 两种线格式都解析进这里:
//
// - OpenAI 兼容:usage.prompt_cache_hit_tokens / prompt_cache_miss_tokens
// (DeepSeek),缺失时用 prompt_tokens_details.cached_tokens 作命中数;
// - Anthropic:usage.cache_read_input_tokens 作命中数,
// input_tokens + cache_creation_input_tokens 作未命中数。
//
// 提供商未上报(缺字段)时为零 —— 缺字段是**容错**路径,绝不 panic、绝不
// 影响正文解析。
CacheHitTokens int
CacheMissTokens int
// ReasoningTokens 是完成侧里属于**思维链**的 token 数(OpenAI 兼容的
// usage.completion_tokens_details.reasoning_tokens)。Anthropic 不上报它
// (思考 token 已计入 output_tokens),因此那里恒为 0。
ReasoningTokens int
}
// CacheHitRate 返回输入侧的缓存命中率(hit / (hit+miss))。提供商未上报任何
// 输入侧明细、或两档都是 0 时返回 0 —— 不编造一个 100% 或 0% 之外的假值。
func (r Response) CacheHitRate() float64 {
total := r.CacheHitTokens + r.CacheMissTokens
if total <= 0 {
return 0
}
return float64(r.CacheHitTokens) / float64(total)
}
// CallRecord 描述一次成功完成的 LLM 调用,供 M9