feat(rag): 检索质量评测 —— 单路检索能力 + recall@k/MRR 评测台
- 引擎抽 searchPaths(三路召回) + SearchByMode(vector/fulltext/graph/hybrid, 纯检索不 rerank,公平对比);kb_search 加 mode 参数(空=生产含rerank), gateway KbSearch 透传 mode - scripts/rageval.py:标注语料+查询 → 四模式 recall@k/MRR 对比表(可复用) - live 量化:纯语义改写让全文0.88/图谱0.75 漏召回,混合 1.00 兜回, 混合=各路上界的稳健组合 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -341,7 +341,15 @@ func (g *Gateway) kbSearch(ctx context.Context, call *contract.ToolCall) *contra
|
||||
if !g.rag.Ready() {
|
||||
return &contract.ToolResult{OK: true, Content: "[]"}
|
||||
}
|
||||
hits, err := g.rag.Search(ctx, kb, q, topK)
|
||||
// mode 空=生产混合检索(含 rerank);显式 vector/fulltext/graph/hybrid=评测用单路/纯融合(不 rerank)。
|
||||
mode, _ := call.Args["mode"].(string)
|
||||
var hits []rag.Hit
|
||||
var err error
|
||||
if mode == "" {
|
||||
hits, err = g.rag.Search(ctx, kb, q, topK)
|
||||
} else {
|
||||
hits = g.rag.SearchByMode(ctx, kb, q, topK, mode)
|
||||
}
|
||||
if err != nil {
|
||||
return &contract.ToolResult{OK: false, Error: "kb_search: " + err.Error()}
|
||||
}
|
||||
|
||||
@@ -233,16 +233,7 @@ func (e *Engine) Search(ctx context.Context, kb, query string, topK int) ([]Hit,
|
||||
}
|
||||
fanout := topK * 3
|
||||
|
||||
// 向量路
|
||||
vecs, err := e.embed().Embed(ctx, []string{query})
|
||||
if err != nil || len(vecs) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
vecHits, _ := e.mv.search(ctx, kb, vecs[0], fanout)
|
||||
// 全文路
|
||||
ftHits := e.bleve.search(kb, query, fanout)
|
||||
// 图谱路(GraphRAG:查询提到的实体的相连三元组)
|
||||
graphHits := e.graph.search(ctx, kb, query, fanout)
|
||||
vecHits, ftHits, graphHits := e.searchPaths(ctx, kb, query, fanout)
|
||||
// RRF 融合(三路,按文本去重)
|
||||
cand := rrf([][]Hit{vecHits, ftHits, graphHits}, fanout)
|
||||
log.Printf("[rag] hybrid: 向量=%d 全文=%d 图谱=%d → 融合=%d", len(vecHits), len(ftHits), len(graphHits), len(cand))
|
||||
@@ -277,6 +268,43 @@ func (e *Engine) DeleteDoc(ctx context.Context, kb, fileID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// searchPaths 跑三路召回,返回各路命中(供混合融合与离线评测按单路对比)。
|
||||
func (e *Engine) searchPaths(ctx context.Context, kb, query string, fanout int) (vec, ft, graph []Hit) {
|
||||
if vecs, err := e.embed().Embed(ctx, []string{query}); err == nil && len(vecs) > 0 {
|
||||
vec, _ = e.mv.search(ctx, kb, vecs[0], fanout)
|
||||
}
|
||||
ft = e.bleve.search(kb, query, fanout)
|
||||
graph = e.graph.search(ctx, kb, query, fanout)
|
||||
return
|
||||
}
|
||||
|
||||
// SearchByMode 按指定模式返回 topK(评测用,纯检索不 rerank,便于公平对比)。
|
||||
// mode: vector|fulltext|graph|hybrid(RRF)。
|
||||
func (e *Engine) SearchByMode(ctx context.Context, kb, query string, topK int, mode string) []Hit {
|
||||
if !e.Ready() || topK <= 0 {
|
||||
if topK <= 0 {
|
||||
topK = 5
|
||||
}
|
||||
}
|
||||
fanout := topK * 3
|
||||
vec, ft, graph := e.searchPaths(ctx, kb, query, fanout)
|
||||
var hits []Hit
|
||||
switch mode {
|
||||
case "vector":
|
||||
hits = vec
|
||||
case "fulltext":
|
||||
hits = ft
|
||||
case "graph":
|
||||
hits = graph
|
||||
default: // hybrid
|
||||
hits = rrf([][]Hit{vec, ft, graph}, fanout)
|
||||
}
|
||||
if len(hits) > topK {
|
||||
hits = hits[:topK]
|
||||
}
|
||||
return hits
|
||||
}
|
||||
|
||||
func (e *Engine) Close() {
|
||||
if e.mv != nil {
|
||||
e.mv.close()
|
||||
|
||||
Reference in New Issue
Block a user