mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-05 08:01:23 +00:00
118 lines
4.1 KiB
Go
118 lines
4.1 KiB
Go
package generation
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// This file makes provider selection a single choice. A self-hoster sets
|
|
// AI_PROVIDER to a preset (openrouter, groq, ollama, openai, anthropic, custom)
|
|
// and supplies just a key + model; the preset fills in the base URL and the
|
|
// free/un-metered default. OpenRouter is the OSS-agentic-tool pattern: one
|
|
// OpenAI-compatible endpoint + one key fronting every vendor's models, so
|
|
// "switching models" is just changing AI_MODEL.
|
|
|
|
// ProviderSettings are the raw, env-sourced inputs for provider selection.
|
|
type ProviderSettings struct {
|
|
// Provider is the AI_PROVIDER preset. Empty means openai.
|
|
Provider string
|
|
// APIKey is the key for the selected provider (AI_API_KEY).
|
|
APIKey string
|
|
// BaseURL overrides the preset endpoint (AI_BASE_URL).
|
|
BaseURL string
|
|
// Model sets both tiers (AI_MODEL). ModelTrial / ModelPaid override per tier.
|
|
Model string
|
|
ModelTrial string
|
|
ModelPaid string
|
|
// Free marks the model as un-metered (AI_FREE). nil uses the preset default
|
|
// (true for local backends like Ollama).
|
|
Free *bool
|
|
Search SearchClient
|
|
}
|
|
|
|
type providerPreset struct {
|
|
baseURL string
|
|
defaultModel string
|
|
free bool
|
|
anthropic bool
|
|
// needsKey is false for local backends that ignore the bearer token.
|
|
needsKey bool
|
|
}
|
|
|
|
// presetFor returns the built-in preset for a provider name. The second result
|
|
// is false for an unknown name (treated as a custom OpenAI-compatible endpoint).
|
|
func presetFor(name string) (providerPreset, bool) {
|
|
switch name {
|
|
case "openai":
|
|
// Model default handled by newOpenAIProvider (gpt-4o-mini / gpt-4o).
|
|
return providerPreset{baseURL: defaultOpenAIBaseURL, needsKey: true}, true
|
|
case "openrouter":
|
|
return providerPreset{baseURL: "https://openrouter.ai/api/v1", defaultModel: "meta-llama/llama-3.1-8b-instruct:free", needsKey: true}, true
|
|
case "groq":
|
|
return providerPreset{baseURL: "https://api.groq.com/openai/v1", defaultModel: "openai/gpt-oss-20b", needsKey: true}, true
|
|
case "ollama", "local":
|
|
return providerPreset{baseURL: "http://localhost:11434/v1", defaultModel: defaultLocalModel, free: true}, true
|
|
case "anthropic":
|
|
return providerPreset{anthropic: true, needsKey: true}, true
|
|
case "custom", "openai-compatible":
|
|
// Base URL must be supplied via AI_BASE_URL.
|
|
return providerPreset{needsKey: true}, true
|
|
}
|
|
return providerPreset{needsKey: true}, false
|
|
}
|
|
|
|
// Resolve maps settings + the chosen preset into a ProviderConfig for
|
|
// NewProvider. Explicit BaseURL / Free / model values always win over the preset.
|
|
// It errors for a custom/openai-compatible provider with no base URL rather than
|
|
// silently defaulting to OpenAI's public API (which would send the operator's key
|
|
// to the wrong host).
|
|
func Resolve(s ProviderSettings) (ProviderConfig, error) {
|
|
name := strings.ToLower(strings.TrimSpace(s.Provider))
|
|
if name == "" {
|
|
name = "openai"
|
|
}
|
|
preset, _ := presetFor(name)
|
|
|
|
if preset.anthropic {
|
|
return ProviderConfig{AnthropicAPIKey: s.APIKey, Search: s.Search}, nil
|
|
}
|
|
|
|
free := preset.free
|
|
if s.Free != nil {
|
|
free = *s.Free
|
|
}
|
|
apiKey := s.APIKey
|
|
if strings.TrimSpace(apiKey) == "" && free {
|
|
// Local backends ignore the bearer token, but NewProvider needs a
|
|
// non-empty key to select the OpenAI-compatible provider.
|
|
apiKey = "local"
|
|
}
|
|
baseURL := strings.TrimSpace(s.BaseURL)
|
|
if baseURL == "" {
|
|
baseURL = preset.baseURL
|
|
}
|
|
// Only a custom / openai-compatible (or unknown) provider reaches here with an
|
|
// empty base; every built-in preset sets one. Refuse rather than falling
|
|
// through to api.openai.com with a non-OpenAI key.
|
|
if baseURL == "" {
|
|
return ProviderConfig{}, fmt.Errorf("AI_PROVIDER=%q needs AI_BASE_URL: a custom OpenAI-compatible provider has no default endpoint", name)
|
|
}
|
|
return ProviderConfig{
|
|
OpenAIAPIKey: apiKey,
|
|
OpenAIBaseURL: baseURL,
|
|
OpenAIModelTrial: firstNonEmpty(s.ModelTrial, s.Model, preset.defaultModel),
|
|
OpenAIModelPaid: firstNonEmpty(s.ModelPaid, s.Model, preset.defaultModel),
|
|
Local: free,
|
|
Search: s.Search,
|
|
}, nil
|
|
}
|
|
|
|
func firstNonEmpty(vals ...string) string {
|
|
for _, v := range vals {
|
|
if strings.TrimSpace(v) != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|