mirror of
https://github.com/Warky-Devs/vecna.git
synced 2026-05-05 01:26:58 +00:00
feat: 🎉 Vectors na Vectors, the begining
Translate 1536 <-> 768 , 3072 <-> 2048
This commit is contained in:
168
pkg/config/config.go
Normal file
168
pkg/config/config.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config is the root configuration for vecna.
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Metrics MetricsConfig `mapstructure:"metrics"`
|
||||
Forward ForwardConfig `mapstructure:"forward"`
|
||||
Adapter AdapterConfig `mapstructure:"adapter"`
|
||||
}
|
||||
|
||||
// ServerConfig controls the HTTP listener and inbound auth.
|
||||
type ServerConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Host string `mapstructure:"host"`
|
||||
APIKeys []string `mapstructure:"api_keys"`
|
||||
}
|
||||
|
||||
// MetricsConfig controls Prometheus metrics exposure.
|
||||
type MetricsConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Path string `mapstructure:"path"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
}
|
||||
|
||||
// ForwardConfig holds all named forwarding targets.
|
||||
type ForwardConfig struct {
|
||||
Default string `mapstructure:"default"`
|
||||
Targets map[string]ForwardTarget `mapstructure:"targets"`
|
||||
}
|
||||
|
||||
// ForwardTarget is a named backing embedding model with one or more endpoints.
|
||||
type ForwardTarget struct {
|
||||
Endpoints []EndpointConfig `mapstructure:"endpoints"`
|
||||
Model string `mapstructure:"model"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
APIType string `mapstructure:"api_type"`
|
||||
TimeoutSecs int `mapstructure:"timeout_secs"`
|
||||
CooldownSecs int `mapstructure:"cooldown_secs"`
|
||||
PriorityDecay int `mapstructure:"priority_decay"`
|
||||
PriorityRecovery int `mapstructure:"priority_recovery"`
|
||||
}
|
||||
|
||||
// EndpointConfig is a single URL within a ForwardTarget.
|
||||
type EndpointConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Priority int `mapstructure:"priority"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
}
|
||||
|
||||
// AdapterConfig selects and tunes the dimension adapter.
|
||||
type AdapterConfig struct {
|
||||
Type string `mapstructure:"type"`
|
||||
SourceDim int `mapstructure:"source_dim"`
|
||||
TargetDim int `mapstructure:"target_dim"`
|
||||
TruncateMode string `mapstructure:"truncate_mode"`
|
||||
PadMode string `mapstructure:"pad_mode"`
|
||||
Seed int64 `mapstructure:"seed"`
|
||||
MatrixFile string `mapstructure:"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{".", 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(".")
|
||||
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); 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
|
||||
}
|
||||
}
|
||||
137
pkg/config/update.go
Normal file
137
pkg/config/update.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SaveTarget adds or replaces a named ForwardTarget in the config file at path.
|
||||
// Only JSON config files are written in-place. For other formats an error is
|
||||
// returned describing what to add manually.
|
||||
func SaveTarget(path, name string, target ForwardTarget) error {
|
||||
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(path), "."))
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read config %s: %w", path, err)
|
||||
}
|
||||
|
||||
switch ext {
|
||||
case "json":
|
||||
return saveTargetJSON(path, data, name, target)
|
||||
default:
|
||||
snippet, _ := json.MarshalIndent(map[string]ForwardTarget{name: target}, "", " ")
|
||||
return fmt.Errorf(
|
||||
"auto-update not supported for .%s files\n"+
|
||||
"Add the following to the 'forward.targets' section of %s:\n\n%s",
|
||||
ext, path, snippet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveBrokenEndpoints removes failing endpoints from the config file.
|
||||
// broken maps target name → set of failing endpoint URLs.
|
||||
// If all endpoints of a target are removed, the target itself is deleted.
|
||||
// Returns the list of removed items as human-readable strings.
|
||||
func RemoveBrokenEndpoints(path string, broken map[string][]string) ([]string, error) {
|
||||
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(path), "."))
|
||||
if ext != "json" {
|
||||
return nil, fmt.Errorf("auto-update not supported for .%s files; edit %s manually", ext, path)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read config %s: %w", path, err)
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse %s: %w", path, err)
|
||||
}
|
||||
|
||||
var removed []string
|
||||
|
||||
for targetName, failedURLs := range broken {
|
||||
target, ok := cfg.Forward.Targets[targetName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
failSet := make(map[string]bool, len(failedURLs))
|
||||
for _, u := range failedURLs {
|
||||
failSet[u] = true
|
||||
}
|
||||
|
||||
kept := target.Endpoints[:0]
|
||||
for _, ep := range target.Endpoints {
|
||||
if failSet[ep.URL] {
|
||||
removed = append(removed, fmt.Sprintf("endpoint %s (target %q)", ep.URL, targetName))
|
||||
} else {
|
||||
kept = append(kept, ep)
|
||||
}
|
||||
}
|
||||
|
||||
if len(kept) == 0 {
|
||||
delete(cfg.Forward.Targets, targetName)
|
||||
removed = append(removed, fmt.Sprintf("target %q (all endpoints failed)", targetName))
|
||||
if cfg.Forward.Default == targetName {
|
||||
cfg.Forward.Default = ""
|
||||
}
|
||||
} else {
|
||||
target.Endpoints = kept
|
||||
cfg.Forward.Targets[targetName] = target
|
||||
}
|
||||
}
|
||||
|
||||
if len(removed) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(path, append(out, '\n'), 0o600); err != nil {
|
||||
return nil, fmt.Errorf("write %s: %w", path, err)
|
||||
}
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
// WriteConfig serialises cfg as indented JSON and atomically overwrites path.
|
||||
func WriteConfig(path string, cfg Config) error {
|
||||
out, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(path, append(out, '\n'), 0o600); err != nil {
|
||||
return fmt.Errorf("write %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveTargetJSON(path string, data []byte, name string, target ForwardTarget) error {
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return fmt.Errorf("parse %s: %w", path, err)
|
||||
}
|
||||
|
||||
if cfg.Forward.Targets == nil {
|
||||
cfg.Forward.Targets = make(map[string]ForwardTarget)
|
||||
}
|
||||
cfg.Forward.Targets[name] = target
|
||||
if cfg.Forward.Default == "" {
|
||||
cfg.Forward.Default = name
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(path, append(out, '\n'), 0o600); err != nil {
|
||||
return fmt.Errorf("write %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user