feat(llm): 声明真实上下文窗口 + 工作区间与窗口分离

问题:core.llm.model=AUTO,而 ModelContextWindow("auto") 匹配不到任何分支、
掉进 default 32768 —— 该源真实窗口是 1M(实测 990,034 token 的 prompt 通过),
内核却按小 30 倍的窗口算全部预算。

三处改动:
1. ModelContextWindow 补 deepseek-v4/v3 → 1M;推断不出时打日志(静默降级是
   这次问题的成因,不能再默默退回一个小值)。
2. sourceFieldDefs 补 context_window 声明:它早已被 readInt 读进 LLMSource
   并透传到 provider,但没进这张表 ⇒ WebUI 里看不见也改不了。
3. ComputeTokenBudget 新增 maxTargetTokens=600000:窗口 1M 不等于按 838K
   (80%)干活。标称窗口≠有效窗口,600K 是该源最优工作区间,所以把
   「窗口上限」(会不会被上游拒)与「工作区间」(预算分配)分开。

配套 core.llm.max_tokens 4096→32768(实测长输出样本达 18,272 token,
16384 仍会截断;上限是 cap 不是目标,短问答零成本)。

测试:tokenbudget_test.go 钉死封顶生效且小窗口不受影响;
context_window_test.go 钉死推断值与显式声明优先级。
This commit is contained in:
JianFeeeee
2026-09-19 14:24:31 +08:00
parent 59478de8fc
commit 0420bb845f
5 changed files with 142 additions and 10 deletions

View File

@ -0,0 +1,51 @@
package core
import (
"context"
"testing"
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
)
// windowProvider 是只声明窗口的 Provider 桩(预算是纯函数,不需要真推理)。
type windowProvider struct{ n int }
func (windowProvider) Name() string { return "window-stub" }
func (windowProvider) Chat(context.Context, *agentAPI.CompletionRequest) (*agentAPI.CompletionResponse, error) {
return nil, nil
}
func (windowProvider) ChatStream(context.Context, *agentAPI.CompletionRequest) (<-chan agentAPI.StreamChunk, error) {
return nil, nil
}
func (p windowProvider) MaxContextTokens() int { return p.n }
// 回归(2026-09-19):窗口声明为 1M 后,工作面不能被 80% 带到 838K ——
// 该源最优区间是 600K,超过就只剩成本与延迟。
func TestComputeTokenBudgetCapsTargetAtWorkingBand(t *testing.T) {
const sys = "系统提示词"
b := ComputeTokenBudget(windowProvider{1048576}, sys)
if b.MaxContext != 1048576 {
t.Errorf("MaxContext 应保留真实窗口:%d", b.MaxContext)
}
if b.TargetUsage != 600000 {
t.Errorf("TargetUsage 应封顶在 600K(最优区间),实际 %d", b.TargetUsage)
}
avail := 600000 - EstimateTokens(sys)
if b.MemoryTokens != avail/3 {
t.Errorf("MemoryTokens=%d want %d", b.MemoryTokens, avail/3)
}
if b.MemoryTokens+b.ContextTokens != avail {
t.Errorf("两部分应恰好用完可用预算:%d + %d != %d", b.MemoryTokens, b.ContextTokens, avail)
}
}
// 封顶只作用于“窗口远大于工作区间”的情形;普通窗口仍按 80% 走,
// 不能让这条约束把小窗口的预算也一起改掉。
func TestComputeTokenBudgetSmallWindowUnchanged(t *testing.T) {
b := ComputeTokenBudget(windowProvider{32768}, "x")
if b.TargetUsage != 26214 {
t.Errorf("小窗口应仍取 80%%:got %d want %d", b.TargetUsage, 26214)
}
}