feat(search): P3.1 filters and multi-query merge

Native include/exclude domains, time_range, topic, exact_match, min_score,
and up to 4 queries fused with RRF. Honesty notes stay off the Warn/fail
path. Tavily keeps include_answer false and sets auto_parameters false.
Cache key is canonical JSON. CLI flags and skill recipe included.
This commit is contained in:
2026-09-29 15:30:19 -07:00
parent 5c6a4a40ec
commit 8b26776cdd
21 changed files with 1606 additions and 195 deletions
+18 -10
View File
@@ -59,7 +59,7 @@ func (s *SearxNG) Name() string { return NameSearxNG }
func (s *SearxNG) Ready() bool { return s != nil && s.Client.Ready() }
func (s *BraveLLM) Ready() bool { return s != nil && s.Client.Ready() }
func (s *SearxNG) Search(ctx context.Context, query string, k int) SourceOut {
func (s *SearxNG) Search(ctx context.Context, query string, k int, f Filter) SourceOut {
if s == nil || s.Client == nil {
return SourceOut{Warn: NameSearxNG + ": error"}
}
@@ -71,10 +71,17 @@ func (s *SearxNG) Search(ctx context.Context, query string, k int) SourceOut {
if err != nil {
return SourceOut{Warn: warnErr(NameSearxNG, err)}
}
qtext, notes := searxQueryAndNotes(query, f)
q := u.Query()
q.Set("q", query)
q.Set("q", qtext)
q.Set("format", "json")
q.Set("pageno", "1")
if f.TimeRange != "" {
q.Set("time_range", f.TimeRange)
}
if f.Topic == "news" {
q.Set("categories", "news")
}
u.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
@@ -92,10 +99,11 @@ func (s *SearxNG) Search(ctx context.Context, query string, k int) SourceOut {
}
var env struct {
Results []struct {
URL string `json:"url"`
Title string `json:"title"`
Content string `json:"content"`
Score float64 `json:"score"`
URL string `json:"url"`
Title string `json:"title"`
Content string `json:"content"`
Score float64 `json:"score"`
PublishedDate string `json:"publishedDate"`
} `json:"results"`
}
if err := readJSON(resp, &env); err != nil {
@@ -107,9 +115,9 @@ func (s *SearxNG) Search(ctx context.Context, query string, k int) SourceOut {
if r.URL == "" {
continue
}
hits = append(hits, Hit{URL: r.URL, Title: r.Title, Snippet: r.Content, Score: r.Score, Source: NameSearxNG})
hits = append(hits, Hit{URL: r.URL, Title: r.Title, Snippet: r.Content, Score: r.Score, Source: NameSearxNG, PublishedDate: r.PublishedDate})
}
return SourceOut{Hits: hits}
return SourceOut{Hits: hits, Notes: notes}
}
// BraveLLM is GET {url}?q= with X-Subscription-Token.
@@ -120,7 +128,7 @@ type BraveLLM struct {
func (s *BraveLLM) Name() string { return NameBraveLLM }
func (s *BraveLLM) Search(ctx context.Context, query string, k int) SourceOut {
func (s *BraveLLM) Search(ctx context.Context, query string, k int, f Filter) SourceOut {
if s == nil || s.Client == nil {
return SourceOut{Warn: NameBraveLLM + ": error"}
}
@@ -159,7 +167,7 @@ func (s *BraveLLM) Search(ctx context.Context, query string, k int) SourceOut {
if len(hits) >= 0 {
credits = 1
}
return SourceOut{Hits: hits, Credits: credits}
return SourceOut{Hits: hits, Credits: credits, Notes: braveNotes(f)}
}
func mapBrave(raw []byte) []Hit {