feat(config): 新增 LLM_MULTIMODAL —— 让 env-only 部署能声明「主模型自身能收图」(默认 false,行为不变)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 起因(Android 会话质询):其测到生效配置 multimodal:false 却与 DeepSeek 视觉文档矛盾。核实结论:
  **既非设计选择也非漏配,而是 env 路径的表达力限制** —— `data/models.json` 不存在 ⇒ 走 `GroupFromEnv`,
  而它此前硬编 false(理由:不得声称端点可能不具备的能力);该标志的正式载体只有 models.json 组(面板写入)。
  后果:env-only 部署里图像**恒走 KindVisionTool**(每回合多一次完整模型调用 + 模型只见文字描述=有损)。
- 修法:`LLMConfig.Multimodal`(`env:"LLM_MULTIMODAL" default:"false"`)+ `GroupFromEnv` 两个 provider 分叉都接入;
  **只对 env 合成组生效**(有有效 models.json 时组文件优先,与 LLM_REASONING_KIND 同口径);非 bool **启动报错**不静默
- 复核:config 包测试绿(5 条:默认不变 / 显式 true 生效且组仍 Validate / 进程 env 端到端 / 非法值 fail-fast /
  组文件优先两方向);**变异 3 条**(分叉写死 false、default 改 true、env 覆盖组文件)均已还原
- 交付给主人的动作:**在 `.env` 写 `LLM_MULTIMODAL=true`** 即可让图直进主模型上下文(改完需重启后端生效)
This commit is contained in:
AskaEth committed 2026-10-04 16:30:25 +08:00
1 parent 6c164f920b
commit 4adfc9a2df
5 files changed
+214 -21

No files matched your search

+32
View File
@@ -169,6 +169,38 @@ func TestLoad_LlmFallbackMaxTokens(t *testing.T) {
}
}
// TestLoad_LlmMultimodal 钉住 LLM_MULTIMODAL 的解码面(裁决 14.4.1):默认 false
// (= 与引入本键之前逐字节一致)、.env 与进程 env 两条路都能声明、非法值**报错**
// 而不是静默回退成 false(静默的「配了当没配」正是本键要消灭的东西)。
func TestLoad_LlmMultimodal(t *testing.T) {
t.Setenv("JWT_SECRET", strings.Repeat("s", 40))
unsetEnvForTest(t, "LLM_MULTIMODAL")
cfg, err := Load("")
if err != nil {
t.Fatalf("Load() error: %v", err)
}
if cfg.LLM.Multimodal {
t.Error("LLM.Multimodal = true, want default false")
}
path := writeEnvFile(t, "LLM_MULTIMODAL=true\n")
cfg, err = Load(path)
if err != nil {
t.Fatalf("Load(%q) error: %v", path, err)
}
if !cfg.LLM.Multimodal {
t.Error("LLM.Multimodal = false, want true from .env")
}
t.Setenv("LLM_MULTIMODAL", "maybe")
if _, err := Load(""); err == nil {
t.Fatal("expected an error for LLM_MULTIMODAL=maybe, got nil")
} else if !strings.Contains(err.Error(), "LLM_MULTIMODAL") {
t.Errorf("error should mention LLM_MULTIMODAL, got: %v", err)
}
}
func TestLoad_MissingRequiredFails(t *testing.T) {
// Do not set JWT_SECRET.
if _, err := Load(""); err == nil {