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:
@@ -25,22 +25,37 @@ var sourceRank = map[string]int{
|
||||
|
||||
// Hit is one search result after mapping. No page body, no vendor answers.
|
||||
type Hit struct {
|
||||
URL string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
Snippet string `json:"snippet"`
|
||||
Score float64 `json:"score"`
|
||||
Source string `json:"source"`
|
||||
URL string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
Snippet string `json:"snippet"`
|
||||
Score float64 `json:"score"`
|
||||
Source string `json:"source"`
|
||||
PublishedDate string `json:"published_date,omitempty"`
|
||||
}
|
||||
|
||||
// Filter is native search knobs. The zero value is P3 behaviour.
|
||||
type Filter struct {
|
||||
IncludeDomains []string
|
||||
ExcludeDomains []string
|
||||
IncludeDomainsMode string // restrict | prefer; empty means restrict when include is set
|
||||
TimeRange string // day|week|month|year
|
||||
StartDate string
|
||||
EndDate string
|
||||
Topic string // general|news|finance; empty means general
|
||||
ExactMatch bool
|
||||
MinScore float64
|
||||
}
|
||||
|
||||
// Source is one upstream. Config-fixed endpoint; never a request URL.
|
||||
type Source interface {
|
||||
Name() string
|
||||
Search(ctx context.Context, query string, k int) SourceOut
|
||||
Search(ctx context.Context, query string, k int, f Filter) SourceOut
|
||||
}
|
||||
|
||||
type SourceOut struct {
|
||||
Hits []Hit
|
||||
Warn string
|
||||
Warn string // failure only (timeout, HTTP error, pin)
|
||||
Notes []string // honesty: filters not applied, recall loss
|
||||
Credits int
|
||||
}
|
||||
|
||||
@@ -80,6 +95,7 @@ func Dedupe(in []Hit) []Hit {
|
||||
continue
|
||||
}
|
||||
if s, ok := seen[canon]; ok {
|
||||
keepDate := firstDate(s.h.PublishedDate, h.PublishedDate)
|
||||
if len(h.Snippet) > len(s.h.Snippet) {
|
||||
src := s.h.Source
|
||||
keep := s.h.URL
|
||||
@@ -91,6 +107,7 @@ func Dedupe(in []Hit) []Hit {
|
||||
s.h.Score = sc
|
||||
}
|
||||
}
|
||||
s.h.PublishedDate = keepDate
|
||||
continue
|
||||
}
|
||||
cp := h
|
||||
@@ -104,6 +121,13 @@ func Dedupe(in []Hit) []Hit {
|
||||
return out
|
||||
}
|
||||
|
||||
func firstDate(a, b string) string {
|
||||
if a != "" {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func sortBySourceOrder(hits []Hit) {
|
||||
sort.SliceStable(hits, func(i, j int) bool {
|
||||
ri, rj := sourceRank[hits[i].Source], sourceRank[hits[j].Source]
|
||||
|
||||
Reference in New Issue
Block a user