mirror of
https://github.com/Warky-Devs/vecna.git
synced 2026-05-05 01:26:58 +00:00
* Introduced ExtraMapConfig to allow multiple adapter configurations. * Updated server and handler to utilize extra maps for routing. * Added dashboard handler for metrics visualization.
196 lines
8.6 KiB
Go
196 lines
8.6 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/go-viper/mapstructure/v2"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Config is the root configuration for vecna.
|
|
type Config struct {
|
|
Server ServerConfig `mapstructure:"server" json:"server" yaml:"server" xml:"server"`
|
|
Metrics MetricsConfig `mapstructure:"metrics" json:"metrics" yaml:"metrics" xml:"metrics"`
|
|
Forward ForwardConfig `mapstructure:"forward" json:"forward" yaml:"forward" xml:"forward"`
|
|
Adapter AdapterConfig `mapstructure:"adapter" json:"adapter" yaml:"adapter" xml:"adapter"`
|
|
ExtraMaps map[string]ExtraMapConfig `mapstructure:"extra_maps" json:"extra_maps" yaml:"extra_maps" xml:"extra_maps"`
|
|
}
|
|
|
|
// ServerConfig controls the HTTP listener and inbound auth.
|
|
type ServerConfig struct {
|
|
Port int `mapstructure:"port" json:"port" yaml:"port" xml:"port"`
|
|
Host string `mapstructure:"host" json:"host" yaml:"host" xml:"host"`
|
|
APIKeys []string `mapstructure:"api_keys" json:"api_keys" yaml:"api_keys" xml:"api_keys"`
|
|
}
|
|
|
|
// MetricsConfig controls Prometheus metrics exposure.
|
|
type MetricsConfig struct {
|
|
Enabled bool `mapstructure:"enabled" json:"enabled" yaml:"enabled" xml:"enabled"`
|
|
Path string `mapstructure:"path" json:"path" yaml:"path" xml:"path"`
|
|
APIKey string `mapstructure:"api_key" json:"api_key" yaml:"api_key" xml:"api_key"`
|
|
}
|
|
|
|
// ForwardConfig holds all named forwarding targets.
|
|
type ForwardConfig struct {
|
|
Default string `mapstructure:"default" json:"default" yaml:"default" xml:"default"`
|
|
Targets map[string]ForwardTarget `mapstructure:"targets" json:"targets" yaml:"targets" xml:"targets"`
|
|
}
|
|
|
|
// ForwardTarget is a named backing embedding model with one or more endpoints.
|
|
type ForwardTarget struct {
|
|
Endpoints []EndpointConfig `mapstructure:"endpoints" json:"endpoints" yaml:"endpoints" xml:"endpoints"`
|
|
Model string `mapstructure:"model" json:"model" yaml:"model" xml:"model"`
|
|
APIKey string `mapstructure:"api_key" json:"api_key" yaml:"api_key" xml:"api_key"`
|
|
APIType string `mapstructure:"api_type" json:"api_type" yaml:"api_type" xml:"api_type"`
|
|
TimeoutSecs int `mapstructure:"timeout_secs" json:"timeout_secs" yaml:"timeout_secs" xml:"timeout_secs"`
|
|
CooldownSecs int `mapstructure:"cooldown_secs" json:"cooldown_secs" yaml:"cooldown_secs" xml:"cooldown_secs"`
|
|
PriorityDecay int `mapstructure:"priority_decay" json:"priority_decay" yaml:"priority_decay" xml:"priority_decay"`
|
|
PriorityRecovery int `mapstructure:"priority_recovery" json:"priority_recovery" yaml:"priority_recovery" xml:"priority_recovery"`
|
|
}
|
|
|
|
// EndpointConfig is a single URL within a ForwardTarget.
|
|
type EndpointConfig struct {
|
|
URL string `mapstructure:"url" json:"url" yaml:"url" xml:"url"`
|
|
Priority int `mapstructure:"priority" json:"priority" yaml:"priority" xml:"priority"`
|
|
APIKey string `mapstructure:"api_key" json:"api_key" yaml:"api_key" xml:"api_key"`
|
|
}
|
|
|
|
// ExtraMapConfig is a per-mapping override: all adapter fields fall back to the
|
|
// global adapter when unset; ForwardTarget selects a named target from
|
|
// forward.targets (empty = global forward.default).
|
|
type ExtraMapConfig struct {
|
|
ForwardTarget string `mapstructure:"forward_target" json:"forward_target,omitempty" yaml:"forward_target,omitempty" xml:"forward_target,omitempty"`
|
|
Type string `mapstructure:"type" json:"type,omitempty" yaml:"type,omitempty" xml:"type,omitempty"`
|
|
SourceDim int `mapstructure:"source_dim" json:"source_dim,omitempty" yaml:"source_dim,omitempty" xml:"source_dim,omitempty"`
|
|
TargetDim int `mapstructure:"target_dim" json:"target_dim,omitempty" yaml:"target_dim,omitempty" xml:"target_dim,omitempty"`
|
|
TruncateMode string `mapstructure:"truncate_mode" json:"truncate_mode,omitempty" yaml:"truncate_mode,omitempty" xml:"truncate_mode,omitempty"`
|
|
PadMode string `mapstructure:"pad_mode" json:"pad_mode,omitempty" yaml:"pad_mode,omitempty" xml:"pad_mode,omitempty"`
|
|
Seed int64 `mapstructure:"seed" json:"seed,omitempty" yaml:"seed,omitempty" xml:"seed,omitempty"`
|
|
MatrixFile string `mapstructure:"matrix_file" json:"matrix_file,omitempty" yaml:"matrix_file,omitempty" xml:"matrix_file,omitempty"`
|
|
}
|
|
|
|
// AdapterConfig selects and tunes the dimension adapter.
|
|
type AdapterConfig struct {
|
|
Type string `mapstructure:"type" json:"type" yaml:"type" xml:"type"`
|
|
SourceDim int `mapstructure:"source_dim" json:"source_dim" yaml:"source_dim" xml:"source_dim"`
|
|
TargetDim int `mapstructure:"target_dim" json:"target_dim" yaml:"target_dim" xml:"target_dim"`
|
|
TruncateMode string `mapstructure:"truncate_mode" json:"truncate_mode" yaml:"truncate_mode" xml:"truncate_mode"`
|
|
PadMode string `mapstructure:"pad_mode" json:"pad_mode" yaml:"pad_mode" xml:"pad_mode"`
|
|
Seed int64 `mapstructure:"seed" json:"seed" yaml:"seed" xml:"seed"`
|
|
MatrixFile string `mapstructure:"matrix_file" json:"matrix_file" yaml:"matrix_file" xml:"matrix_file"`
|
|
}
|
|
|
|
// extensions viper will detect automatically.
|
|
var extensions = []string{"json", "yaml", "toml"}
|
|
|
|
// ResolveFile returns the config file path that would be used by Load.
|
|
// If cfgFile is non-empty it is returned as-is.
|
|
// Otherwise the default search paths are checked; if no existing file is found,
|
|
// the preferred default (~/.vecna.json) is returned so callers can create it.
|
|
func ResolveFile(cfgFile string) string {
|
|
if cfgFile != "" {
|
|
return cfgFile
|
|
}
|
|
home, _ := os.UserHomeDir()
|
|
dirs := []string{"/config", ".", home, home + "/.config/vecna"}
|
|
for _, dir := range dirs {
|
|
for _, ext := range extensions {
|
|
path := dir + "/vecna." + ext
|
|
if _, err := os.Stat(path); err == nil {
|
|
return path
|
|
}
|
|
}
|
|
}
|
|
return home + "/vecna.json"
|
|
}
|
|
|
|
// Load reads configuration from the given file path (empty = search defaults),
|
|
// environment variables (prefix VECNA_), and applies built-in defaults.
|
|
func Load(cfgFile string) (*Config, error) {
|
|
v := viper.New()
|
|
|
|
// Defaults
|
|
v.SetDefault("server.port", 8080)
|
|
v.SetDefault("server.host", "0.0.0.0")
|
|
v.SetDefault("metrics.enabled", false)
|
|
v.SetDefault("metrics.path", "/metrics")
|
|
v.SetDefault("adapter.type", "truncate")
|
|
v.SetDefault("adapter.truncate_mode", "from_end")
|
|
v.SetDefault("adapter.pad_mode", "at_end")
|
|
|
|
// Environment
|
|
v.SetEnvPrefix("VECNA")
|
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
v.AutomaticEnv()
|
|
|
|
// Config file
|
|
if cfgFile != "" {
|
|
v.SetConfigFile(cfgFile)
|
|
} else {
|
|
home, _ := os.UserHomeDir()
|
|
v.SetConfigName("vecna")
|
|
// No SetConfigType — viper detects format from file extension (json, yaml, toml, etc.)
|
|
v.AddConfigPath("/config")
|
|
v.AddConfigPath(".")
|
|
v.AddConfigPath(home)
|
|
v.AddConfigPath(home + "/.config/vecna")
|
|
}
|
|
|
|
if err := v.ReadInConfig(); err != nil {
|
|
// Missing config file is acceptable when all required values come from flags/env
|
|
var notFound viper.ConfigFileNotFoundError
|
|
if !errors.As(err, ¬Found) {
|
|
return nil, fmt.Errorf("load config: %w", err)
|
|
}
|
|
}
|
|
|
|
var cfg Config
|
|
if err := v.Unmarshal(&cfg, func(dc *mapstructure.DecoderConfig) {
|
|
// Viper lowercases all keys (e.g. "SourceDim" → "sourcedim"), but
|
|
// mapstructure tags use snake_case ("source_dim"). Strip underscores
|
|
// before comparing so both forms resolve correctly.
|
|
dc.MatchName = func(mapKey, fieldName string) bool {
|
|
norm := func(s string) string {
|
|
return strings.ToLower(strings.ReplaceAll(s, "_", ""))
|
|
}
|
|
return norm(mapKey) == norm(fieldName)
|
|
}
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("unmarshal config: %w", err)
|
|
}
|
|
|
|
applyForwardDefaults(&cfg)
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// applyForwardDefaults fills in zero-value fields on ForwardTarget entries.
|
|
func applyForwardDefaults(cfg *Config) {
|
|
for name, t := range cfg.Forward.Targets {
|
|
if t.TimeoutSecs == 0 {
|
|
t.TimeoutSecs = 30
|
|
}
|
|
if t.CooldownSecs == 0 {
|
|
t.CooldownSecs = 60
|
|
}
|
|
if t.PriorityDecay == 0 {
|
|
t.PriorityDecay = 2
|
|
}
|
|
if t.PriorityRecovery == 0 {
|
|
t.PriorityRecovery = 5
|
|
}
|
|
for i, ep := range t.Endpoints {
|
|
if ep.Priority == 0 {
|
|
t.Endpoints[i].Priority = 10
|
|
}
|
|
if ep.APIKey == "" && t.APIKey != "" {
|
|
t.Endpoints[i].APIKey = t.APIKey
|
|
}
|
|
}
|
|
cfg.Forward.Targets[name] = t
|
|
}
|
|
}
|