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
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
|
||||
@@ -40,11 +41,17 @@ type fillSpec struct {
|
||||
|
||||
// groupFillSpecs 是 `.env` 默认层覆盖的键全集,顺序即日志顺序。
|
||||
//
|
||||
// 为什么是这四个键:`.env` 侧确实有对应变量(LLM_PROVIDER / LLM_BASE_URL /
|
||||
// 为什么是这几个键:`.env` 侧确实有对应变量(LLM_PROVIDER / LLM_BASE_URL /
|
||||
// LLM_API_KEY / LLM_MODEL 及其 ANTHROPIC_* 对偶),也就是 GroupFromEnv 从 `.env`
|
||||
// LLM 块写进合成组的那几个字段。`id` / `maxTokens` / `personaModels` /
|
||||
// `personaProviders` 在 `.env` 里没有出处,因此永远不由 env 补。
|
||||
//
|
||||
// `contextWindow` 是唯一被纳入的**整型**键(LLM_CONTEXT_WINDOW 的 per-group 播种,
|
||||
// design/04 附录 A.3 第 1 条)。它用十进制字符串承载,但 0 不构成一个可补的默认值
|
||||
// —— models.json 里 0 与「键被省略」是同一个字节形态(omitempty),
|
||||
// 因此 env 只补正数窗口,绝不让「面板里清掉窗口 → 重启被 env 静默补回」成立
|
||||
// (与下面 fallback/multimodal/reasoning.kind 被排除的理由同源)。
|
||||
//
|
||||
// `fallback` / `multimodal` / `reasoning.kind` 刻意**不在表内**:它们带
|
||||
// omitempty / omitzero,在 models.json 里「面板清空该键」与「从未写过该键」是同一个
|
||||
// 字节形态(键直接不写)。把它们纳入补齐,「面板里清掉 → 重启被 `.env` 静默补回」
|
||||
@@ -55,6 +62,27 @@ var groupFillSpecs = []fillSpec{
|
||||
{key: "baseUrl", get: func(g Group) string { return g.BaseURL }, set: func(g *Group, v string) { g.BaseURL = v }},
|
||||
{key: "apiKey", get: func(g Group) string { return g.APIKey }, set: func(g *Group, v string) { g.APIKey = v }},
|
||||
{key: "model", get: func(g Group) string { return g.Model }, set: func(g *Group, v string) { g.Model = v }},
|
||||
{key: "contextWindow", get: func(g Group) string { return positiveIntText(g.ContextWindow) },
|
||||
set: func(g *Group, v string) { g.ContextWindow = parsePositiveInt(v) }},
|
||||
}
|
||||
|
||||
// positiveIntText 把一个「0 = 未声明」的整型渲染成可补的字符串:0 不算默认值
|
||||
// (见 groupFillSpecs 的说明),因此返回空串表示「env 层没有可补的值」。
|
||||
func positiveIntText(v int) string {
|
||||
if v <= 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.Itoa(v)
|
||||
}
|
||||
|
||||
// parsePositiveInt 解析 env 层给出的整数;解析不出来(理论上不会发生:值来自
|
||||
// GroupFromEnv 的规范化十进制)时按未声明处理,绝不写进一个瞎猜的数。
|
||||
func parsePositiveInt(raw string) int {
|
||||
n, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil || n <= 0 {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Fill 是一条补齐或跳过的记录:来源恒为 env 的那个键,落在哪个组上。
|
||||
|
||||
Reference in new issue
Block a user