Files
glpi-neural-brain/internal/config/config.go
T
2026-08-04 03:43:12 +02:00

141 lines
3.7 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
type Config struct {
ListenAddr string
DataDir string
KnowledgeDirs []string
StagingDirs []string
AgentRunsFiles []string
OllamaURL string
ChatModel string
EmbeddingModel string
SearXNGURL string
ScanInterval time.Duration
EnrichInterval time.Duration
SimilarityThreshold float64
RelationThreshold float64
TopK int
MaxContextChars int
AutoEnrich bool
ResearchEnabled bool
APIKey string
}
func Load() (Config, error) {
dataDir := env("BRAIN_DATA_DIR", "./data")
abs, err := filepath.Abs(dataDir)
if err != nil {
return Config{}, err
}
cfg := Config{
ListenAddr: env("BRAIN_LISTEN_ADDR", ":8090"),
DataDir: abs,
KnowledgeDirs: paths("BRAIN_KNOWLEDGE_DIRS"),
StagingDirs: paths("BRAIN_STAGING_DIRS"),
AgentRunsFiles: paths("BRAIN_AGENT_RUNS_FILES"),
OllamaURL: strings.TrimRight(env("OLLAMA_URL", "http://localhost:11434"), "/"),
ChatModel: env("OLLAMA_CHAT_MODEL", "qwen3:8b"),
EmbeddingModel: env("OLLAMA_EMBEDDING_MODEL", "embeddinggemma"),
SearXNGURL: strings.TrimRight(strings.TrimSpace(os.Getenv("SEARXNG_URL")), "/"),
ScanInterval: duration("BRAIN_SCAN_INTERVAL", 20*time.Second),
EnrichInterval: duration("BRAIN_ENRICH_INTERVAL", 90*time.Second),
SimilarityThreshold: number("BRAIN_SIMILARITY_THRESHOLD", 0.68),
RelationThreshold: number("BRAIN_RELATION_THRESHOLD", 0.72),
TopK: integer("BRAIN_TOP_K", 8),
MaxContextChars: integer("BRAIN_MAX_CONTEXT_CHARS", 16000),
AutoEnrich: boolean("BRAIN_AUTO_ENRICH", true),
ResearchEnabled: boolean("BRAIN_RESEARCH_ENABLED", false),
APIKey: strings.TrimSpace(os.Getenv("BRAIN_API_KEY")),
}
if cfg.ScanInterval < 2*time.Second {
return Config{}, fmt.Errorf("BRAIN_SCAN_INTERVAL must be at least 2s")
}
if cfg.EnrichInterval < 10*time.Second {
return Config{}, fmt.Errorf("BRAIN_ENRICH_INTERVAL must be at least 10s")
}
if cfg.SimilarityThreshold < 0 || cfg.SimilarityThreshold > 1 {
return Config{}, fmt.Errorf("invalid similarity threshold")
}
if cfg.RelationThreshold < 0 || cfg.RelationThreshold > 1 {
return Config{}, fmt.Errorf("invalid relation threshold")
}
if cfg.ResearchEnabled && cfg.SearXNGURL == "" {
return Config{}, fmt.Errorf("BRAIN_RESEARCH_ENABLED requires SEARXNG_URL")
}
if err := os.MkdirAll(cfg.DataDir, 0o750); err != nil {
return Config{}, err
}
return cfg, nil
}
func env(k, d string) string {
if v := strings.TrimSpace(os.Getenv(k)); v != "" {
return v
}
return d
}
func paths(k string) []string {
var out []string
for _, v := range strings.Split(os.Getenv(k), ",") {
if v = strings.TrimSpace(v); v != "" {
if a, e := filepath.Abs(v); e == nil {
out = append(out, a)
}
}
}
return out
}
func duration(k string, d time.Duration) time.Duration {
v := strings.TrimSpace(os.Getenv(k))
if v == "" {
return d
}
x, e := time.ParseDuration(v)
if e != nil {
return d
}
return x
}
func number(k string, d float64) float64 {
v := strings.TrimSpace(os.Getenv(k))
if v == "" {
return d
}
x, e := strconv.ParseFloat(v, 64)
if e != nil {
return d
}
return x
}
func integer(k string, d int) int {
v := strings.TrimSpace(os.Getenv(k))
if v == "" {
return d
}
x, e := strconv.Atoi(v)
if e != nil {
return d
}
return x
}
func boolean(k string, d bool) bool {
v := strings.TrimSpace(os.Getenv(k))
if v == "" {
return d
}
x, e := strconv.ParseBool(v)
if e != nil {
return d
}
return x
}