package config // 本文件承载多供应商 LLM 与其本地第三级的配置块(design/04 §3.1;裁决 03.4.3)。 // 它从 config.go 拆出来有两条理由:config.go 已贴着 AGENTS §10 的 500 行硬上限, // 而「LLM 各供应商各自的端点/密钥/模型/推理形状」本来就是一块自洽的配置面。 import "strings" // LLMConfig holds the multi-supplier LLM adapter settings. Provider selects the // wire protocol; every provider has its own independent base/key/model/fallback // so multiple vendors can coexist. type LLMConfig struct { // Provider declares which protocol to use: "openai" (OpenAI-compatible // /chat/completions) or "anthropic" (Anthropic Messages /v1/messages). Provider string `env:"LLM_PROVIDER" default:"openai"` // OpenAI-compatible provider (default). Kept flat for backward compatibility // with the historic LLM_BASE_URL/API_KEY/MODEL/FALLBACK_MODEL env names. BaseURL string `env:"LLM_BASE_URL"` APIKey string `env:"LLM_API_KEY"` Model string `env:"LLM_MODEL"` Fallback string `env:"LLM_FALLBACK_MODEL"` // VisionModel is the dedicated model behind the vision tool (image // understanding). It is the tool's model, not the main model's ability: the // per-persona/per-request routing decision belongs to the vision Router, and // the main model's own image input capability is declared by the active // models.json group's multimodal flag (裁决 14.4.1/14.4.5)。 VisionModel string `env:"LLM_VISION_MODEL"` // OCRModel is the dedicated model behind the OCR tool. Empty reuses // VisionModel, so OCR keeps working without a second endpoint (裁决 14.4.1 // 的「独立视觉模型 + OCR 模型」)。 OCRModel string `env:"LLM_OCR_MODEL"` // Multimodal 声明**主模型自身**能否收图:models.json 组 `multimodal` 的 env // 等价物,只作用于 .env 合成的组,默认 false(语义与分工见 GroupFromEnv)。 Multimodal bool `env:"LLM_MULTIMODAL" default:"false"` // ContextWindow 声明该端点的**模型上下文窗口**(token,LLM_CONTEXT_WINDOW)。 // 0(默认)表示未声明,此时上下文预算推导回落到 // config.DefaultModelContextWindow 并记一条 WARN(design/04 附录 A.3)。 // 负数在 Load 阶段直接报错(窗口没有负数语义)。 // // 它是 models.json 组 `contextWindow` 的 env 等价物(per-group 播种,语义见 // runtimeconfig.Materialize);本仓目前没有任何 provider 侧窗口发现代码, // 因此窗口是**声明式**的: `GET /models` 的自动发现另立设计。 ContextWindow int `env:"LLM_CONTEXT_WINDOW"` // Local is the optional third degradation level (裁决 03.4.3): primary → // fallback → local. It defaults to off because the current machine cannot run // a local model comfortably; when off the chain keeps its two-level shape. Local LocalLLMConfig // FallbackMaxTokens caps the last-resort fallback model in a group's chain // (primary → fallback). It is the final safety budget: it only applies when // the chain actually has a fallback step, that group sets no per-group // MaxTokens (models.json maxTokens) and the request carries no explicit // budget. Zero disables the cap (the adapter default then applies). FallbackMaxTokens int `env:"LLM_FALLBACK_MAX_TOKENS" default:"8192"` // ReasoningKind declares this OpenAI-compatible endpoint's reasoning-switch // wire shape (裁决 03.4.2/08.4.1: 不同提供商的开关形状不同,必须逐 provider // 独立适配). Values mirror runtimeconfig.ReasoningKind*: "" or "auto" infers // the vendor from the model name/base URL and sends nothing when the shape is // ambiguous (the conservative default, so an existing .env is unchanged); // "deepseek" / "openai" / "vllm" / "dashscope" select that endpoint's shape // explicitly. It is the env-fallback twin of the models.json group's // `reasoning.kind`, which is the primary carrier (hot-reloadable, panel-edited). ReasoningKind string `env:"LLM_REASONING_KIND" default:""` // Anthropic native provider, used when Provider=anthropic. Independent // config block so each vendor is a self-contained config. AnthropicBaseURL string `env:"ANTHROPIC_BASE_URL"` AnthropicAPIKey string `env:"ANTHROPIC_API_KEY"` AnthropicModel string `env:"ANTHROPIC_MODEL"` AnthropicFallback string `env:"ANTHROPIC_FALLBACK_MODEL"` } // LocalLLMConfig configures the optional local model rung — the third level of // the primary → fallback → local degradation chain (裁决 03.4.3). It is opt-in // and defaults to off: a machine that cannot run a local model comfortably must // not silently pay for one. It becomes an active chain step only when Enabled is // true and both BaseURL and Model are set; anything less is reported and skipped // rather than producing a half-configured step. type LocalLLMConfig struct { // Enabled turns the local rung on. Default false (project red line: 本地模型可开关,默认关). Enabled bool `env:"LLM_LOCAL_ENABLED" default:"false"` // Provider is the local endpoint's wire protocol: "openai" (llama.cpp / // ollama / vLLM compatible endpoints) or "anthropic". Empty means openai. Provider string `env:"LLM_LOCAL_PROVIDER" default:"openai"` // BaseURL is the local endpoint's base URL (e.g. http://127.0.0.1:8080/v1). BaseURL string `env:"LLM_LOCAL_BASE_URL"` // APIKey is the local endpoint's key; empty is normal for a local server. APIKey string `env:"LLM_LOCAL_API_KEY"` // Model is the local model name. Without it the rung stays off. Model string `env:"LLM_LOCAL_MODEL"` } // Configured reports whether the local rung has enough configuration to act as a // chain step. Enabled alone is not enough: a missing endpoint or model must not // become a step that can only fail. func (l LocalLLMConfig) Configured() bool { return l.Enabled && strings.TrimSpace(l.BaseURL) != "" && strings.TrimSpace(l.Model) != "" }