# P0: Recall 降级策略 — 织忆 Go daemon ## 目标 当 bge-embed (8000) 或 Rust IPC sidecar 不可用时,recall 自动降级到 graph.db 关键词搜索,不返回 500 错误。 ## 修改文件 ### 1. `/tmp/memoryweave/go/internal/governance/graph_sqlite.go` 新增方法 `FallbackTextSearch(query, namespace, limit)`: ```go func (gs *SQLiteGraphStore) FallbackTextSearch(query, namespace string, limit int) []map[string]interface{} { // 1. 从 edge properties 中搜索 content 字段(JSON 内 text 字段) // 2. LIKE '%query%' 模糊匹配 nodes 的 name // 3. 按 pagerank DESC 排序 // 4. LIMIT limit } ``` sqlite-go 通过 CGo 操作,参考已有 queryRows 函数(行 1037)。 类似 SearchNodes(行 865)的模式,但搜索 edges 的 properties 字段。 ### 2. `/tmp/memoryweave/go/internal/storage/recall.go` 在 `RecallPipeline` 结构体新增 `GraphStore` 字段: ```go type GraphExpander interface { // ... existing methods } ``` 不用改 interface。在 `Recall` 方法末尾(当前行 149 return nil 之前),如果 candidates 为空且 lanceDB 搜索失败,尝试从 GraphStore 的 FallbackTextSearch 获取结果。 ### 3. `/tmp/memoryweave/go/internal/api/routes/core.go` 在 `Recall` handler(行 209-293)中,当 `a.Pipeline.Recall()` 返回 err 时(行 240),不直接 500,而是调用 graph store 的 fallback 搜索: ```go if err != nil { // Fallback: graph.db keyword search fallbackResults := a.GraphStore.FallbackTextSearch(req.Query, req.Namespace, req.Limit) if len(fallbackResults) > 0 { // Convert fallback results to RecallResult format results = convertFallbackResults(fallbackResults) // return with 200 + warning header } else { respondError(w, 500, "recall failed: "+err.Error()) return } } ``` ## 验证方法 ```bash # 正常状态能搜到 curl -s -X POST -H "X-API-Key: zhiyi-dev-key-2026" \ -d '{"query":"小唯","top_k":3}' \ http://localhost:7821/api/v1/recall | python -c "import json,sys;d=json.load(sys.stdin);print(f'count: {d.get(\"count\",0)}')" # 模拟 bge-embed 挂了 # curl 应该仍返回结果(从 graph.db 关键词搜索) ```