package config import ( "fmt" "reflect" ) // LoadLLMDefaults 按既有解析入口读取 `.env` 默认层的 LLM 块。它是面板 // 「重置为 env 默认」动作(切片 R1,见 runtimeconfig.ResetFromEnv)的**对照基准**: // 那个动作要知道「`.env` 到底声明了哪些键」,而网关自己绝不 fopen `.env`。 // // 它复用 parseDotEnv 与同一份 `env:"…"` 标签解码器,因此优先级与 Load 逐字一致: // 进程环境变量 > `.env` > 标签默认值(生产走 systemd/env_file 时不配 `.env`, // 结果就是进程环境里那一份)。envPath 为空表示不看 `.env`(与 Load 的约定一致)。 // // 为什么只解 LLM 子树、而不是整份 Load:整份 Load 会在缺 JWT_SECRET 这类必填项时 // 失败,而本读取只需要 LLM 块的声明;窗口负数在 Load 阶段就会报错(窗口没有负数 // 语义),这里保持同一口径,不另立一套判定。 func LoadLLMDefaults(envPath string) (LLMConfig, error) { fromEnv := map[string]string{} if envPath != "" { m, err := parseDotEnv(envPath) if err != nil { return LLMConfig{}, fmt.Errorf("config: read %s: %w", envPath, err) } fromEnv = m } var llm LLMConfig if err := decodeValue(reflect.ValueOf(&llm), fromEnv); err != nil { return LLMConfig{}, err } if llm.ContextWindow < 0 { return LLMConfig{}, fmt.Errorf("config: LLM_CONTEXT_WINDOW 不能为负(%d):0 = 未声明(按回落窗口推导)", llm.ContextWindow) } return llm, nil }