feat(model): LLM token 预算可配置化 — models.json每模型maxTokens + LLM_FALLBACK_MAX_TOKENS回退兜底
- config.go: LLMConfig.FallbackMaxTokens(LLM_FALLBACK_MAX_TOKENS, 默认8192) - runtimeconfig/models.go: Group.MaxTokens(models.json每模型独立预算, 负值拒绝) - model/fallback.go: Chain.WithTokenBudget + applyBudget; runtime.go buildSnapshot接入(active.MaxTokens, env.FallbackMaxTokens) - 优先级: 显式Request.MaxTokens > 组maxTokens > 仅多步链末端的LLM_FALLBACK_MAX_TOKENS > 适配器默认8192; 单步链不动env兜底(避免OpenAI主模型行为回归) - gateway admin: AdminLlmProvider.MaxTokens 透传/回显/负值400 - .env.example + .env(LLM_FALLBACK_MAX_TOKENS=8192); Docs/api-client v0.27 + api-reference + design/04 - 新增测试(config/runtimeconfig/budget_test/admin_write), build/vet/test绿+3平台交叉编译
This commit is contained in:
1 parent
f347681034
commit
53002f1e11
13 files changed
+384
-15
No files matched your search
@@ -231,7 +231,7 @@ func TestAdminAddLlmProviderPersistsAndReloads(t *testing.T) {
|
||||
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
||||
|
||||
resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/llm",
|
||||
map[string]any{"id": "deepseek", "provider": "openai", "baseUrl": "https://api.deepseek.com/v1", "apiKey": "sk-ds", "model": "deepseek-chat", "multimodal": true}, tok)
|
||||
map[string]any{"id": "deepseek", "provider": "openai", "baseUrl": "https://api.deepseek.com/v1", "apiKey": "sk-ds", "model": "deepseek-chat", "multimodal": true, "maxTokens": 1234}, tok)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("status = %d, want 201 (body=%s)", resp.StatusCode, body)
|
||||
}
|
||||
@@ -243,7 +243,7 @@ func TestAdminAddLlmProviderPersistsAndReloads(t *testing.T) {
|
||||
if err := json.Unmarshal(decodeBody(t, body)["data"], &res); err != nil {
|
||||
t.Fatalf("decode add: %v", err)
|
||||
}
|
||||
if !res.Persisted || res.Path != modelsPath || res.Provider.Model != "deepseek-chat" {
|
||||
if !res.Persisted || res.Path != modelsPath || res.Provider.Model != "deepseek-chat" || res.Provider.MaxTokens != 1234 {
|
||||
t.Fatalf("add res = %+v", res)
|
||||
}
|
||||
|
||||
@@ -252,8 +252,8 @@ func TestAdminAddLlmProviderPersistsAndReloads(t *testing.T) {
|
||||
if warn != nil {
|
||||
t.Fatalf("load models warn = %v", warn)
|
||||
}
|
||||
if len(groups) == 0 || groups[0].ID != "deepseek" || groups[0].APIKey != "sk-ds" {
|
||||
t.Fatalf("file groups = %+v, want deepseek first with sk-ds", groups)
|
||||
if len(groups) == 0 || groups[0].ID != "deepseek" || groups[0].APIKey != "sk-ds" || groups[0].MaxTokens != 1234 {
|
||||
t.Fatalf("file groups = %+v, want deepseek first with sk-ds and maxTokens 1234", groups)
|
||||
}
|
||||
|
||||
// The running stack reflects the new active provider/model (hot reload).
|
||||
@@ -270,8 +270,8 @@ func TestAdminAddLlmProviderPersistsAndReloads(t *testing.T) {
|
||||
if err := json.Unmarshal(decodeBody(t, gBody)["data"], &list); err != nil {
|
||||
t.Fatalf("decode list: %v", err)
|
||||
}
|
||||
if len(list.Providers) == 0 || list.Providers[0].Model != "deepseek-chat" {
|
||||
t.Fatalf("list = %+v, want deepseek-chat first", list.Providers)
|
||||
if len(list.Providers) == 0 || list.Providers[0].Model != "deepseek-chat" || list.Providers[0].MaxTokens != 1234 {
|
||||
t.Fatalf("list = %+v, want deepseek-chat first with maxTokens 1234", list.Providers)
|
||||
}
|
||||
if strings.Contains(string(gBody), "sk-ds") || strings.Contains(string(gBody), "sk-env") {
|
||||
t.Fatalf("API key leaked in GET /admin/llm: %s", gBody)
|
||||
|
||||
Reference in new issue
Block a user