feat(routing): RoutingConfig + LoadRouting
Typed config struct and env parser for the routing pod. Kept separate from the supervisor Config to avoid forcing routing fields onto the supervisor and vice versa. Uses the existing envOr helper from config.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
78
internal/config/routing.go
Normal file
78
internal/config/routing.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
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 http://piguard:4000
|
||||||
|
LiteLLMAPIKey string // LITELLM_API_KEY
|
||||||
|
BrainURL string // BRAIN_URL, default http://ingestion.supervisor:3300
|
||||||
|
LocalModel string // HYPERGUILD_LOCAL_MODEL, default qwen35
|
||||||
|
ClaudeModel string // HYPERGUILD_CLAUDE_MODEL, default claude-sonnet-4-6
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadRouting() (RoutingConfig, error) {
|
||||||
|
cfg := RoutingConfig{
|
||||||
|
Port: envOr("ROUTING_PORT", "3210"),
|
||||||
|
MCPAuthToken: os.Getenv("ROUTING_MCP_TOKEN"),
|
||||||
|
LiteLLMBaseURL: envOr("LITELLM_BASE_URL", "http://piguard:4000"),
|
||||||
|
LiteLLMAPIKey: os.Getenv("LITELLM_API_KEY"),
|
||||||
|
BrainURL: envOr("BRAIN_URL", "http://ingestion.supervisor:3300"),
|
||||||
|
LocalModel: envOr("HYPERGUILD_LOCAL_MODEL", "qwen35"),
|
||||||
|
ClaudeModel: envOr("HYPERGUILD_CLAUDE_MODEL", "claude-sonnet-4-6"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
65
internal/config/routing_test.go
Normal file
65
internal/config/routing_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mathiasbq/supervisor/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadRoutingDefaults(t *testing.T) {
|
||||||
|
for _, k := range []string{
|
||||||
|
"ROUTING_PORT", "ROUTING_MCP_TOKEN", "LITELLM_BASE_URL", "LITELLM_API_KEY",
|
||||||
|
"BRAIN_URL", "HYPERGUILD_LOCAL_MODEL", "HYPERGUILD_CLAUDE_MODEL",
|
||||||
|
"HYPERGUILD_ROUTE_LOCAL_FLOOR", "HYPERGUILD_ROUTE_LOCAL_CEIL",
|
||||||
|
"HYPERGUILD_PASS_RATE_TTL_SECONDS",
|
||||||
|
} {
|
||||||
|
t.Setenv(k, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadRouting()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "3210", cfg.Port)
|
||||||
|
assert.Equal(t, "", cfg.MCPAuthToken)
|
||||||
|
assert.Equal(t, "http://piguard:4000", cfg.LiteLLMBaseURL)
|
||||||
|
assert.Equal(t, "http://ingestion.supervisor:3300", cfg.BrainURL)
|
||||||
|
assert.Equal(t, "qwen35", cfg.LocalModel)
|
||||||
|
assert.Equal(t, "claude-sonnet-4-6", cfg.ClaudeModel)
|
||||||
|
assert.InDelta(t, 0.90, cfg.RouteLocalFloor, 1e-9)
|
||||||
|
assert.InDelta(t, 0.70, cfg.RouteLocalCeil, 1e-9)
|
||||||
|
assert.Equal(t, 60, cfg.PassRateTTLSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRoutingFromEnv(t *testing.T) {
|
||||||
|
t.Setenv("ROUTING_PORT", "3250")
|
||||||
|
t.Setenv("ROUTING_MCP_TOKEN", "tok-xyz")
|
||||||
|
t.Setenv("LITELLM_BASE_URL", "http://localhost:4000")
|
||||||
|
t.Setenv("LITELLM_API_KEY", "lk")
|
||||||
|
t.Setenv("BRAIN_URL", "http://localhost:3300")
|
||||||
|
t.Setenv("HYPERGUILD_LOCAL_MODEL", "qwen2-7b")
|
||||||
|
t.Setenv("HYPERGUILD_CLAUDE_MODEL", "claude-opus-4-7")
|
||||||
|
t.Setenv("HYPERGUILD_ROUTE_LOCAL_FLOOR", "0.85")
|
||||||
|
t.Setenv("HYPERGUILD_ROUTE_LOCAL_CEIL", "0.65")
|
||||||
|
t.Setenv("HYPERGUILD_PASS_RATE_TTL_SECONDS", "30")
|
||||||
|
|
||||||
|
cfg, err := config.LoadRouting()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "3250", cfg.Port)
|
||||||
|
assert.Equal(t, "tok-xyz", cfg.MCPAuthToken)
|
||||||
|
assert.Equal(t, "http://localhost:4000", cfg.LiteLLMBaseURL)
|
||||||
|
assert.Equal(t, "lk", cfg.LiteLLMAPIKey)
|
||||||
|
assert.Equal(t, "http://localhost:3300", cfg.BrainURL)
|
||||||
|
assert.Equal(t, "qwen2-7b", cfg.LocalModel)
|
||||||
|
assert.Equal(t, "claude-opus-4-7", cfg.ClaudeModel)
|
||||||
|
assert.InDelta(t, 0.85, cfg.RouteLocalFloor, 1e-9)
|
||||||
|
assert.InDelta(t, 0.65, cfg.RouteLocalCeil, 1e-9)
|
||||||
|
assert.Equal(t, 30, cfg.PassRateTTLSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRoutingRejectsBadFloat(t *testing.T) {
|
||||||
|
t.Setenv("HYPERGUILD_ROUTE_LOCAL_FLOOR", "not-a-number")
|
||||||
|
_, err := config.LoadRouting()
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "HYPERGUILD_ROUTE_LOCAL_FLOOR")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user