156 lines
4.3 KiB
Go
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
|
|
}
|