G1: Recall 管线完整化 — 搜索缓存 + 双向 BFS 导航
改动: - G1.3: graph/navigate API 新增 source+target 参数,调用 NavigateBiDir 双向 BFS - G1.4: recall pipeline 新增 Step 0 搜索缓存检查 + Step 6.5 缓存写入 缓存 TTL 1h,相同 query+namespace 跳过完整管线直接返回 - MMR 多样性去重(G1.1)+ 多跳图谱扩展(G1.2)已在上次提交中存在 测试:双向 BFS 返回 9 条路径,bidirectional=true;缓存命中测试通过
This commit is contained in:
parent
f6f4c832fb
commit
4541b0165a
|
|
@ -65,9 +65,14 @@ func (ga *GraphAPI) Query(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// POST /api/v1/graph/navigate
|
||||
// 支持两种模式:
|
||||
// 1. 单实体 BFS: {"entity": "...", "max_hops": 2}
|
||||
// 2. 双向 BFS: {"source": "...", "target": "...", "max_hops": 3}(设计文档 §2.5.4)
|
||||
func (ga *GraphAPI) Navigate(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Entity string `json:"entity"`
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
MaxHops int `json:"max_hops"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
|
@ -75,14 +80,31 @@ func (ga *GraphAPI) Navigate(w http.ResponseWriter, r *http.Request) {
|
|||
respondError(w, 400, "invalid body")
|
||||
return
|
||||
}
|
||||
if req.Entity == "" {
|
||||
respondError(w, 400, "entity required")
|
||||
return
|
||||
}
|
||||
if req.MaxHops <= 0 {
|
||||
req.MaxHops = 2
|
||||
}
|
||||
|
||||
// 模式 2: 双向 BFS(source + target)
|
||||
if req.Source != "" && req.Target != "" {
|
||||
source := normalizeEntity(req.Source)
|
||||
target := normalizeEntity(req.Target)
|
||||
paths, err := ga.Graph.NavigateBiDir(source, target, req.MaxHops, req.Namespace)
|
||||
if err != nil {
|
||||
respondError(w, 500, "navigate failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
respond(w, 200, map[string]interface{}{
|
||||
"paths": paths, "source": req.Source, "target": req.Target,
|
||||
"bidirectional": true, "count": len(paths),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 模式 1: 单实体 BFS(兼容旧格式)
|
||||
if req.Entity == "" {
|
||||
respondError(w, 400, "entity (or source+target) required")
|
||||
return
|
||||
}
|
||||
entity := normalizeEntity(req.Entity)
|
||||
paths, err := ga.Graph.Navigate(entity, req.MaxHops, req.Namespace)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
|
@ -50,6 +51,16 @@ func (p *RecallPipeline) Recall(query, namespace string, topK int, diversity flo
|
|||
topK = 10
|
||||
}
|
||||
|
||||
// Step 0: 搜索缓存(§2.6 — 相同 query hash → TTL 1h → 命中直接返回)
|
||||
if SearchCacheInstance != nil {
|
||||
if cached, ok := SearchCacheInstance.Get(query, namespace); ok {
|
||||
var results []models.RecallResult
|
||||
if err := json.Unmarshal(cached, &results); err == nil {
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Encode query
|
||||
queryVec, err := p.embedder.EncodeSingle(query)
|
||||
if err != nil {
|
||||
|
|
@ -158,6 +169,11 @@ func (p *RecallPipeline) Recall(query, namespace string, topK int, diversity flo
|
|||
}
|
||||
results = deduped
|
||||
|
||||
// Step 6.5: 写搜索缓存(§2.6 — TTL 1h,下次相同 query 直接返回)
|
||||
if SearchCacheInstance != nil {
|
||||
SearchCacheInstance.Set(query, namespace, results)
|
||||
}
|
||||
|
||||
// Step 7: 预取推送(CO_OCCURS 权重 > 0.6 的配套记忆 → WebSocket)
|
||||
if p.prefetch != nil {
|
||||
// 使用 CO_OCCURS 追踪器收集真实预取候选项
|
||||
|
|
|
|||
Loading…
Reference in New Issue