Follow-up to infra#70. LiteLLM moved off piguard into k3s and the public llm-api.d-ma.be hostname now upstreams to koala:30401. The piguard:4000 default in the source bit-rots — works today because piguard:4000 is still alive during the 7-day soak, breaks the moment the compose comes down. Pointing the default at the public hostname survives the cutover without needing a follow-up. Production deploys via k3s already override via env (in-cluster Service DNS) so this only affects local dev shells without LITELLM_BASE_URL set. - internal/config/routing.go: comment + envOr fallback - internal/config/routing_test.go: expected value in defaults test - scripts/smoke-routing.sh: shell default task check: clean (tests + vet + govulncheck).
102 lines
3.6 KiB
Go
102 lines
3.6 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
// RoutingConfig holds the runtime configuration for the routing pod.
|
|
// Separate from Config because the routing pod's surface differs from the supervisor's.
|
|
type RoutingConfig struct {
|
|
Port string // ROUTING_PORT, default 3210
|
|
MCPAuthToken string // ROUTING_MCP_TOKEN, optional bearer token
|
|
LiteLLMBaseURL string // LITELLM_BASE_URL, default https://llm-api.d-ma.be
|
|
LiteLLMAPIKey string // LITELLM_API_KEY
|
|
BrainURL string // BRAIN_URL, default http://ingestion.supervisor:3300
|
|
FastModel string // HYPERGUILD_FAST_MODEL, default koala/qwen35-9b-fast
|
|
ThinkingModel string // HYPERGUILD_THINKING_MODEL, default iguana/gemma4-26b
|
|
// RouteLocalFloor and RouteLocalCeil intentionally invert the usual
|
|
// floor < ceil mathematical convention: Floor (default 0.90) is the
|
|
// UPPER boundary — at/above it, always route local; Ceil (default 0.70)
|
|
// is the LOWER boundary — below it, always route Claude. The band in
|
|
// between is the 50/50 sample zone. The naming follows the spec's policy
|
|
// vocabulary; see internal/routing/policy.go for the consumer.
|
|
RouteLocalFloor float64 // HYPERGUILD_ROUTE_LOCAL_FLOOR, default 0.90
|
|
RouteLocalCeil float64 // HYPERGUILD_ROUTE_LOCAL_CEIL, default 0.70
|
|
PassRateTTLSeconds int // HYPERGUILD_PASS_RATE_TTL_SECONDS, default 60
|
|
|
|
// project_create configuration. Empty GiteaMCPURL disables the
|
|
// project_create tool registration so the routing pod still starts
|
|
// in environments where it's not wired up.
|
|
GiteaMCPURL string // GITEA_MCP_URL, e.g. http://koala:30340/mcp
|
|
GiteaMCPToken string // GITEA_MCP_TOKEN, bearer for gitea-mcp
|
|
GiteaOwner string // GITEA_OWNER, default mathias
|
|
GitHubOwner string // GITHUB_OWNER, default mathiasb
|
|
InfraRepo string // INFRA_REPO, default infra
|
|
GitHubPAT string // GITHUB_PAT, repo scope; never logged
|
|
}
|
|
|
|
func LoadRouting() (RoutingConfig, error) {
|
|
cfg := RoutingConfig{
|
|
Port: envOr("ROUTING_PORT", "3210"),
|
|
MCPAuthToken: os.Getenv("ROUTING_MCP_TOKEN"),
|
|
LiteLLMBaseURL: envOr("LITELLM_BASE_URL", "https://llm-api.d-ma.be"),
|
|
LiteLLMAPIKey: os.Getenv("LITELLM_API_KEY"),
|
|
BrainURL: envOr("BRAIN_URL", "http://ingestion.supervisor:3300"),
|
|
FastModel: envOr("HYPERGUILD_FAST_MODEL", "koala/qwen35-9b-fast"),
|
|
ThinkingModel: envOr("HYPERGUILD_THINKING_MODEL", "iguana/gemma4-26b"),
|
|
}
|
|
|
|
floor, err := parseFloatEnv("HYPERGUILD_ROUTE_LOCAL_FLOOR", 0.90)
|
|
if err != nil {
|
|
return RoutingConfig{}, err
|
|
}
|
|
cfg.RouteLocalFloor = floor
|
|
|
|
ceil, err := parseFloatEnv("HYPERGUILD_ROUTE_LOCAL_CEIL", 0.70)
|
|
if err != nil {
|
|
return RoutingConfig{}, err
|
|
}
|
|
cfg.RouteLocalCeil = ceil
|
|
|
|
ttl, err := parseIntEnv("HYPERGUILD_PASS_RATE_TTL_SECONDS", 60)
|
|
if err != nil {
|
|
return RoutingConfig{}, err
|
|
}
|
|
cfg.PassRateTTLSeconds = ttl
|
|
|
|
cfg.GiteaMCPURL = os.Getenv("GITEA_MCP_URL")
|
|
cfg.GiteaMCPToken = os.Getenv("GITEA_MCP_TOKEN")
|
|
cfg.GiteaOwner = envOr("GITEA_OWNER", "mathias")
|
|
cfg.GitHubOwner = envOr("GITHUB_OWNER", "mathiasb")
|
|
cfg.InfraRepo = envOr("INFRA_REPO", "infra")
|
|
cfg.GitHubPAT = os.Getenv("GITHUB_PAT")
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func parseFloatEnv(key string, def float64) (float64, error) {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return def, nil
|
|
}
|
|
f, err := strconv.ParseFloat(v, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("config: %s: %w", key, err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func parseIntEnv(key string, def int) (int, error) {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return def, nil
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("config: %s: %w", key, err)
|
|
}
|
|
return n, nil
|
|
}
|