package search import ( "log/slog" "os" "time" "git.wntrmute.dev/kyle/sift/internal/config" "git.wntrmute.dev/kyle/sift/internal/dcache" "git.wntrmute.dev/kyle/sift/internal/search/dial" ) const ( defaultSearx = "http://127.0.0.1:8888" defaultBrave = "https://api.search.brave.com/res/v1/llm/context" defaultTav = "https://api.tavily.com/search" ) // Build constructs sources from config. Missing keys are listed on Engine.Degraded // and logged once; those sources are not attempted. A source whose DNS is down // at boot stays in the list and is retried on Search. func Build(cfg config.Search, cache *dcache.DB, emb Embedder, log *slog.Logger) *Engine { timeout := config.ParseDuration(cfg.Timeout, 8*time.Second) ttl := config.ParseDuration(cfg.CacheTTL, time.Hour) k := cfg.K if k <= 0 { k = DefaultK } e := &Engine{ Timeout: timeout, DefaultK: k, RerankOn: cfg.RerankOn(), Cache: cache, CacheTTL: ttl, Embedder: emb, Log: log, TavilyCap: cfg.Tavily.DailyCreditCap, } if cfg.SearxNG.Enabled { u := cfg.SearxNG.URL if u == "" { u = defaultSearx } addPinned(e, log, NameSearxNG, u, dial.Options{Loopback: true, Timeout: timeout}, func(c *dial.Client) Source { return &SearxNG{Client: c} }) } if cfg.BraveLLM.Enabled { key := os.Getenv("BRAVE_API_KEY") if key == "" { e.Degraded = append(e.Degraded, "search.brave_llm: missing key") if log != nil { log.Error("search source", "source", NameBraveLLM, "err", "missing key") } } else { u := cfg.BraveLLM.URL if u == "" { u = defaultBrave } addPinned(e, log, NameBraveLLM, u, dial.Options{Timeout: timeout}, func(c *dial.Client) Source { return &BraveLLM{Client: c, Key: key} }) } } if cfg.Tavily.Enabled { key := os.Getenv("TAVILY_API_KEY") if key == "" { e.Degraded = append(e.Degraded, "search.tavily: missing key") if log != nil { log.Error("search source", "source", NameTavily, "err", "missing key") } } else { u := cfg.Tavily.URL if u == "" { u = defaultTav } addPinned(e, log, NameTavily, u, dial.Options{Timeout: timeout}, func(c *dial.Client) Source { return &Tavily{Client: c, Key: key} }) } } return e } func addPinned(e *Engine, log *slog.Logger, name, raw string, opt dial.Options, wrap func(*dial.Client) Source) { c, err := dial.New(raw, opt) if err != nil { e.Degraded = append(e.Degraded, "search."+name+": unavailable") if log != nil { log.Error("search source", "source", name, "err", "unavailable") } return } e.Sources = append(e.Sources, wrap(c)) if !c.Ready() && log != nil { log.Error("search source unresolved, will retry", "source", name) } }