237 lines
9.4 KiB
Go
237 lines
9.4 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Config holds all application configuration.
|
|
// Values are loaded from config.yaml then overridden by env vars prefixed with VEYLANT_.
|
|
// Example: VEYLANT_SERVER_PORT=9090 overrides server.port.
|
|
type Config struct {
|
|
Server ServerConfig `mapstructure:"server"`
|
|
Database DatabaseConfig `mapstructure:"database"`
|
|
Redis RedisConfig `mapstructure:"redis"`
|
|
Keycloak KeycloakConfig `mapstructure:"keycloak"`
|
|
PII PIIConfig `mapstructure:"pii"`
|
|
Log LogConfig `mapstructure:"log"`
|
|
Providers ProvidersConfig `mapstructure:"providers"`
|
|
RBAC RBACConfig `mapstructure:"rbac"`
|
|
Metrics MetricsConfig `mapstructure:"metrics"`
|
|
Routing RoutingConfig `mapstructure:"routing"`
|
|
ClickHouse ClickHouseConfig `mapstructure:"clickhouse"`
|
|
Crypto CryptoConfig `mapstructure:"crypto"`
|
|
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
|
}
|
|
|
|
// RateLimitConfig holds default rate limiting parameters applied to all tenants
|
|
// that have no explicit per-tenant override in the rate_limit_configs table.
|
|
type RateLimitConfig struct {
|
|
// DefaultTenantRPM is the default tenant-wide requests per minute limit.
|
|
DefaultTenantRPM int `mapstructure:"default_tenant_rpm"`
|
|
// DefaultTenantBurst is the maximum burst size for a tenant bucket.
|
|
DefaultTenantBurst int `mapstructure:"default_tenant_burst"`
|
|
// DefaultUserRPM is the default per-user requests per minute limit within a tenant.
|
|
DefaultUserRPM int `mapstructure:"default_user_rpm"`
|
|
// DefaultUserBurst is the maximum burst size for a per-user bucket.
|
|
DefaultUserBurst int `mapstructure:"default_user_burst"`
|
|
}
|
|
|
|
// ClickHouseConfig holds ClickHouse connection settings for the audit log.
|
|
type ClickHouseConfig struct {
|
|
DSN string `mapstructure:"dsn"` // clickhouse://user:pass@host:9000/db
|
|
MaxConns int `mapstructure:"max_conns"`
|
|
DialTimeoutSec int `mapstructure:"dial_timeout_seconds"`
|
|
}
|
|
|
|
// CryptoConfig holds cryptographic settings.
|
|
type CryptoConfig struct {
|
|
// AESKeyBase64 is a base64-encoded 32-byte key for AES-256-GCM prompt encryption.
|
|
// Set via env var VEYLANT_CRYPTO_AES_KEY_BASE64 — never hardcode.
|
|
AESKeyBase64 string `mapstructure:"aes_key_base64"`
|
|
}
|
|
|
|
// RoutingConfig controls the intelligent routing engine behaviour.
|
|
type RoutingConfig struct {
|
|
// CacheTTLSeconds is how long routing rules are cached per tenant before
|
|
// a background refresh. 0 means use the default (30s).
|
|
CacheTTLSeconds int `mapstructure:"cache_ttl_seconds"`
|
|
}
|
|
|
|
// ProvidersConfig holds configuration for all LLM provider adapters.
|
|
type ProvidersConfig struct {
|
|
OpenAI OpenAIConfig `mapstructure:"openai"`
|
|
Anthropic AnthropicConfig `mapstructure:"anthropic"`
|
|
Azure AzureConfig `mapstructure:"azure"`
|
|
Mistral MistralConfig `mapstructure:"mistral"`
|
|
Ollama OllamaConfig `mapstructure:"ollama"`
|
|
}
|
|
|
|
// OpenAIConfig holds OpenAI adapter configuration.
|
|
type OpenAIConfig struct {
|
|
APIKey string `mapstructure:"api_key"`
|
|
BaseURL string `mapstructure:"base_url"`
|
|
TimeoutSeconds int `mapstructure:"timeout_seconds"`
|
|
MaxConns int `mapstructure:"max_conns"`
|
|
}
|
|
|
|
// AnthropicConfig holds Anthropic adapter configuration.
|
|
type AnthropicConfig struct {
|
|
APIKey string `mapstructure:"api_key"`
|
|
BaseURL string `mapstructure:"base_url"`
|
|
Version string `mapstructure:"version"` // Anthropic API version header, e.g. "2023-06-01"
|
|
TimeoutSeconds int `mapstructure:"timeout_seconds"`
|
|
MaxConns int `mapstructure:"max_conns"`
|
|
}
|
|
|
|
// AzureConfig holds Azure OpenAI adapter configuration.
|
|
type AzureConfig struct {
|
|
APIKey string `mapstructure:"api_key"`
|
|
ResourceName string `mapstructure:"resource_name"` // e.g. "my-azure-resource"
|
|
DeploymentID string `mapstructure:"deployment_id"` // e.g. "gpt-4o"
|
|
APIVersion string `mapstructure:"api_version"` // e.g. "2024-02-01"
|
|
TimeoutSeconds int `mapstructure:"timeout_seconds"`
|
|
MaxConns int `mapstructure:"max_conns"`
|
|
}
|
|
|
|
// MistralConfig holds Mistral AI adapter configuration (OpenAI-compatible).
|
|
type MistralConfig struct {
|
|
APIKey string `mapstructure:"api_key"`
|
|
BaseURL string `mapstructure:"base_url"`
|
|
TimeoutSeconds int `mapstructure:"timeout_seconds"`
|
|
MaxConns int `mapstructure:"max_conns"`
|
|
}
|
|
|
|
// OllamaConfig holds Ollama adapter configuration (OpenAI-compatible, local).
|
|
type OllamaConfig struct {
|
|
BaseURL string `mapstructure:"base_url"`
|
|
TimeoutSeconds int `mapstructure:"timeout_seconds"`
|
|
MaxConns int `mapstructure:"max_conns"`
|
|
}
|
|
|
|
// RBACConfig holds role-based access control settings for the provider router.
|
|
type RBACConfig struct {
|
|
// UserAllowedModels lists models accessible to the "user" role (exact or prefix match).
|
|
UserAllowedModels []string `mapstructure:"user_allowed_models"`
|
|
// AuditorCanComplete controls whether auditors can make chat completions.
|
|
// Defaults to false — auditors receive 403 on POST /v1/chat/completions.
|
|
AuditorCanComplete bool `mapstructure:"auditor_can_complete"`
|
|
}
|
|
|
|
// MetricsConfig holds Prometheus metrics configuration.
|
|
type MetricsConfig struct {
|
|
Enabled bool `mapstructure:"enabled"`
|
|
Path string `mapstructure:"path"`
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Port int `mapstructure:"port"`
|
|
ShutdownTimeout int `mapstructure:"shutdown_timeout_seconds"`
|
|
Env string `mapstructure:"env"` // development, staging, production
|
|
TenantName string `mapstructure:"tenant_name"` // display name used in PDF reports
|
|
AllowedOrigins []string `mapstructure:"allowed_origins"` // CORS allowed origins for the React dashboard
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
URL string `mapstructure:"url"`
|
|
MaxOpenConns int `mapstructure:"max_open_conns"`
|
|
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
|
MigrationsPath string `mapstructure:"migrations_path"`
|
|
}
|
|
|
|
type RedisConfig struct {
|
|
URL string `mapstructure:"url"`
|
|
}
|
|
|
|
type KeycloakConfig struct {
|
|
BaseURL string `mapstructure:"base_url"`
|
|
Realm string `mapstructure:"realm"`
|
|
ClientID string `mapstructure:"client_id"`
|
|
}
|
|
|
|
type PIIConfig struct {
|
|
Enabled bool `mapstructure:"enabled"`
|
|
ServiceAddr string `mapstructure:"service_addr"` // gRPC address, e.g. localhost:50051
|
|
TimeoutMs int `mapstructure:"timeout_ms"`
|
|
FailOpen bool `mapstructure:"fail_open"` // if true, pass request through on PII service error
|
|
}
|
|
|
|
type LogConfig struct {
|
|
Level string `mapstructure:"level"` // debug, info, warn, error
|
|
Format string `mapstructure:"format"` // json, console
|
|
}
|
|
|
|
// Load reads configuration from config.yaml (searched in . and ./config)
|
|
// and overrides with environment variables prefixed VEYLANT_.
|
|
func Load() (*Config, error) {
|
|
v := viper.New()
|
|
|
|
v.SetConfigName("config")
|
|
v.SetConfigType("yaml")
|
|
v.AddConfigPath(".")
|
|
v.AddConfigPath("./config")
|
|
|
|
// Env var overrides: VEYLANT_SERVER_PORT → server.port
|
|
v.SetEnvPrefix("VEYLANT")
|
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
v.AutomaticEnv()
|
|
|
|
// Defaults
|
|
v.SetDefault("server.port", 8090)
|
|
v.SetDefault("server.shutdown_timeout_seconds", 30)
|
|
v.SetDefault("server.env", "development")
|
|
v.SetDefault("server.allowed_origins", []string{"http://localhost:3000"})
|
|
v.SetDefault("database.max_open_conns", 25)
|
|
v.SetDefault("database.max_idle_conns", 5)
|
|
v.SetDefault("database.migrations_path", "migrations")
|
|
v.SetDefault("pii.enabled", false)
|
|
v.SetDefault("pii.service_addr", "localhost:50051")
|
|
v.SetDefault("pii.timeout_ms", 100)
|
|
v.SetDefault("pii.fail_open", true)
|
|
v.SetDefault("log.level", "info")
|
|
v.SetDefault("log.format", "json")
|
|
v.SetDefault("providers.openai.base_url", "https://api.openai.com/v1")
|
|
v.SetDefault("providers.openai.timeout_seconds", 30)
|
|
v.SetDefault("providers.openai.max_conns", 100)
|
|
v.SetDefault("providers.anthropic.base_url", "https://api.anthropic.com/v1")
|
|
v.SetDefault("providers.anthropic.version", "2023-06-01")
|
|
v.SetDefault("providers.anthropic.timeout_seconds", 30)
|
|
v.SetDefault("providers.anthropic.max_conns", 100)
|
|
v.SetDefault("providers.azure.api_version", "2024-02-01")
|
|
v.SetDefault("providers.azure.timeout_seconds", 30)
|
|
v.SetDefault("providers.azure.max_conns", 100)
|
|
v.SetDefault("providers.mistral.base_url", "https://api.mistral.ai/v1")
|
|
v.SetDefault("providers.mistral.timeout_seconds", 30)
|
|
v.SetDefault("providers.mistral.max_conns", 100)
|
|
v.SetDefault("providers.ollama.base_url", "http://localhost:11434/v1")
|
|
v.SetDefault("providers.ollama.timeout_seconds", 120)
|
|
v.SetDefault("providers.ollama.max_conns", 10)
|
|
v.SetDefault("rbac.user_allowed_models", []string{"gpt-4o-mini", "gpt-3.5-turbo", "mistral-small"})
|
|
v.SetDefault("rbac.auditor_can_complete", false)
|
|
v.SetDefault("metrics.enabled", true)
|
|
v.SetDefault("metrics.path", "/metrics")
|
|
v.SetDefault("routing.cache_ttl_seconds", 30)
|
|
v.SetDefault("clickhouse.max_conns", 10)
|
|
v.SetDefault("clickhouse.dial_timeout_seconds", 5)
|
|
v.SetDefault("rate_limit.default_tenant_rpm", 1000)
|
|
v.SetDefault("rate_limit.default_tenant_burst", 200)
|
|
v.SetDefault("rate_limit.default_user_rpm", 100)
|
|
v.SetDefault("rate_limit.default_user_burst", 20)
|
|
|
|
if err := v.ReadInConfig(); err != nil {
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
return nil, fmt.Errorf("reading config: %w", err)
|
|
}
|
|
// Config file not found — rely on defaults and env vars only
|
|
}
|
|
|
|
var cfg Config
|
|
if err := v.Unmarshal(&cfg); err != nil {
|
|
return nil, fmt.Errorf("unmarshaling config: %w", err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|