Files
AskaEth 72b95df22b
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
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)。
2026-10-04 17:57:08 +08:00

54 lines
3.1 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 config
// 本文件承载「上下文预算」的配置语义(design/04 §1.3 上下文溢出压缩 +
// 附录 A.3 的主人裁决)。推导本身在 context_budget.go。
//
// CONTEXT_MAX_TOKENS 是**三态**取值:
//
// - 未设置(默认) = 按模型窗口推导(DeriveContextBudget);
// - > 0 = 显式覆盖(原样使用);
// - 0 / 负数 = 关闭压缩(行为与引入推导之前逐字节一致)。
//
// 「未设置」不能用 0 表达:0 已经被「关闭压缩」占用。因此「有没有配过这个键」
// 必须单独记录(ContextConfig.MaxTokensSet,由 Load 在解码阶段填),
// 这正是 design/04 附录 A.3 第 2 条要求的「用 os.LookupEnv 判未设置,不要靠 0」。
//
// 旧口径(「默认模型 gpt-4o 窗口 128K ÷ 4 ⇒ 32K」)随本切片作废:默认模型的
// 真实窗口已达 1M token(DevDocs/reference/deepseek/facts-for-cyrene.md),
// 固定 32K 会既丢上下文、又因反复压缩作废 KV 缓存前缀而反复烧钱(附录 A.2)。
// ContextMaxTokensEnv 是上下文预算的配置键名。它必须与 ContextConfig.MaxTokens
// 的 env 标签**逐字一致**(Load 用它判定「未设置」),一致性由
// context_budget_test.go 的 TestContextMaxTokensEnvMatchesTag 钉住。
const ContextMaxTokensEnv = "CONTEXT_MAX_TOKENS"
// ContextConfig 承载运行时循环的上下文预算(design/04 §1.3)。
// 它与 MCP / Capacity 一样是运行时调优量,由组合根消费。
type ContextConfig struct {
// MaxTokens 是触发溢出压缩的上下文预算,单位是循环离线估算的 token 数
// (CONTEXT_MAX_TOKENS)。三态语义见本文件顶部说明。
//
// **它刻意没有 default 标签**:未配置时值为 0,而「0 且未配置」与
// 「显式 0」由 MaxTokensSet 区分 —— 前者按窗口推导,后者关闭压缩。
MaxTokens int `env:"CONTEXT_MAX_TOKENS"`
// MaxTokensSet 报告 CONTEXT_MAX_TOKENS 是否由进程环境或 .env 文件给出。
// 它由 Load 填写,不参与反射解码(没有 env 标签)。
//
// 判定规则(DeriveContextBudget 是唯一判据):MaxTokens > 0 一律按显式覆盖、
// < 0 一律按关闭;只有 MaxTokens == 0 需要本标志区分「显式 0 = 关闭」与
// 「未设置 = 推导」。因此手工构造 Config 的调用方给出非零值时不必同步本标志,
// 但**要表达「显式 0 = 关闭」就必须置真**。
MaxTokensSet bool
// DocumentMaxInjectChars 是**单份附件正文注入模型可见上下文**的字符上限
// (DOCUMENT_MAX_INJECT_CHARS)。0(默认)= 按推导出的上下文预算算
// (loop.DocumentInjectCharsFor);> 0 = 显式覆盖。
//
// 它与「sidecar 存多少」是两个数(design/24 §9.6 连带项、切片 T1 §7.2):
// sidecar 存抽取得到的全文,只受安全上限 documents.MaxSidecarBytes 约束;
// 本项只决定每回合往上下文里注入多少,截断因此发生在**装配期**,
// 于是清单行的续读游标才真是「继续读的起点」。
DocumentMaxInjectChars int `env:"DOCUMENT_MAX_INJECT_CHARS"`
}