Details (original message): Add dual-tier storage (embedded SQLite + optional PostgreSQL), tenant owner scoping, API key auth, versioned dual-dialect migrations, integrity hardening, and batched event persistence
52 lines
1.5 KiB
Go
52 lines
1.5 KiB
Go
package store
|
||
|
||
import (
|
||
core "agent-system-core"
|
||
)
|
||
|
||
// searchKnowledgePG:PG 档知识检索(设计 D16)。
|
||
// 单查询混合:tsvector('simple' 配置,词级)OR trigram LIKE 子串(中文兜底;
|
||
// LIKE 由 pg_trgm GIN 索引加速)。排序:ts_rank 优先,命中时间次之。
|
||
func (s *Store) searchKnowledgePG(owner string, q core.RetrievalQuery) ([]core.RetrievalHit, error) {
|
||
topK := q.TopK
|
||
if topK <= 0 {
|
||
topK = 5
|
||
}
|
||
text := q.Text
|
||
rows, err := s.db.Query(s.q(`
|
||
SELECT k.id,k.owner,k.project_id,k.source,k.title,k.ref,k.chunk,k.created_at,
|
||
ts_rank(k.tsv, websearch_to_tsquery('simple', ?)) AS r
|
||
FROM knowledge k
|
||
WHERE (k.owner=? OR k.owner='')
|
||
AND (? = '' OR k.tsv @@ websearch_to_tsquery('simple', ?) OR k.chunk LIKE ? OR k.title LIKE ?)
|
||
ORDER BY r DESC, k.created_at DESC
|
||
LIMIT ?`), text, owner, text, text, "%"+text+"%", "%"+text+"%", topK*2)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var out []core.RetrievalHit
|
||
for rows.Next() {
|
||
var d core.KnowledgeDoc
|
||
var own, ca string
|
||
var rank float64
|
||
if err := rows.Scan(&d.ID, &own, &d.ProjectID, &d.Source, &d.Title, &d.Ref, &d.Chunk, &ca, &rank); err != nil {
|
||
return nil, err
|
||
}
|
||
d.Owner = own
|
||
d.CreatedAt, _ = parseTime(ca)
|
||
if !knowledgeVisible(owner, q, d) {
|
||
continue
|
||
}
|
||
score := rank
|
||
if score == 0 {
|
||
score = 0.5 // 纯子串命中(trigram/中文兜底)给保底分
|
||
}
|
||
out = append(out, core.RetrievalHit{Doc: d, Score: score, Kind: "fulltext"})
|
||
if len(out) >= topK {
|
||
break
|
||
}
|
||
}
|
||
return out, rows.Err()
|
||
}
|