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

156 lines
4.3 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
EnrichStepDelay time.Duration
EnrichBatchSize int
EnrichAnchors int
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),
EnrichStepDelay: duration("BRAIN_ENRICH_STEP_DELAY", 3*time.Second),
EnrichBatchSize: integer("BRAIN_ENRICH_BATCH_SIZE", 3),
EnrichAnchors: integer("BRAIN_ENRICH_ANCHORS", 48),
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.EnrichStepDelay < 0 {
return Config{}, fmt.Errorf("BRAIN_ENRICH_STEP_DELAY must not be negative")
}
if cfg.EnrichBatchSize < 1 || cfg.EnrichBatchSize > 25 {
return Config{}, fmt.Errorf("BRAIN_ENRICH_BATCH_SIZE must be between 1 and 25")
}
if cfg.EnrichAnchors < 1 || cfg.EnrichAnchors > 2048 {
return Config{}, fmt.Errorf("BRAIN_ENRICH_ANCHORS must be between 1 and 2048")
}
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
}