Grok Build · 記憶檢索

從檔案變更到混合排序

查詢前先同步髒檔案,再結合 FTS5 BM25 與可選的 sqlite-vec KNN。合併分數經過時間衰減、來源權重與存取增益,最後可選擇啟用 MMR 多樣性重排。

課程目標按真實順序複述 sync-on-search、FTS、embedding、KNN、合併加權與 MMR,並能說明 embedding 失敗和 MMR 關閉時各自發生什麼。
核心視覺 · 完整檢索路徑
Watcher 髒路徑create · modify · remove sync-on-searchreindex_file / delete_path 使用者查詢query FTS5 BM25始終可用 · 關鍵詞候選 Embedding + KNN可用時走 sqlite-vec embedding 失敗FTS-only 合併與排序decay × source weight × access boostMMR 可選,隨後 truncate SearchResultmax_results
教學化結構圖:失敗分支回到 FTS-only;MMR 是 opt-in,預設不會重排。
流水線中的真實機制
01 · SYNC

查詢前同步

MemoryFileWatcher 累積變化的 Markdown 路徑。backend 在 search 開始時重新索引新增或修改檔案,並刪除已移除檔案的舊 chunk。

02 · FTS

BM25 候選

先執行普通 FTS,再補充 global 與 workspace 來源查詢,降低 session 數量過多造成的擠出。

03 · VECTOR

可選 KNN

僅在 sqlite-vec 與 provider 可用時嵌入 query。embedding 報錯會記錄 warning,並傳入 None 繼續 FTS-only。

04 · SCORE

歸一化與合併

BM25 分數與向量 L2 距離分別歸一化。雙路命中時按權重合並,同時保證結果不低於該 chunk 的 FTS 分數。

05 · WEIGHT

時間與來源

session 按半衰期指數衰減,global 與 workspace 視為 evergreen。隨後乘 source weight 與適度的 access boost。

06 · DIVERSITY

可選 MMR

開啟後按相關性與 snippet 的 Jaccard 差異做貪心重排。最後截斷到 max_results。

兩個容易誤讀的開關

Embedding 失敗

向量路徑停止,FTS 結果仍進入 hybrid_search_merge。頁面或呼叫方無需把 embedding 故障當成整次搜尋失敗。

fallback = FTS-only

MMR 預設狀態

MmrConfig::default() 設定 enabled: false 與 lambda: 0.7。0.7 只在顯式開啟 MMR 後生效。

enabled = false
真實原始碼證據
crates/codegen/xai-grok-memory/src/search.rs · 第 146 至 190 行節選
pub async fn hybrid_search(
    index: &MemoryIndex,
    embedding_provider: Option<&dyn EmbeddingProvider>,
    query: &str,
    config: &MemorySearchConfig,
) -> Result<Vec<SearchResult>, Box<dyn std::error::Error>> {
    let candidate_limit = config.max_results * 3;
    let mut fts_results =
        index.search_fts(query, candidate_limit).unwrap_or_default();
    /* 补充 evergreen FTS 候选的源码在此处 */

    let vec_available = index.vec_available();
    let query_embedding = if vec_available {
        if let Some(provider) = embedding_provider {
            match provider.embed_batch(&[query]).await {
                Ok(embeddings) if !embeddings.is_empty() =>
                    Some(embeddings.into_iter().next().unwrap()),
                Ok(_) => None,
                Err(e) => {
                    tracing::warn!(error = %e,
                        "embedding query failed, falling back to FTS-only");
                    None
                }
            }
        } else { None }
    } else { None };

    hybrid_search_merge(index, fts_results, query_embedding.as_deref(), config)
}
crates/codegen/xai-grok-memory/src/backend.rs:search() 中執行 watcher 同步與查詢 crates/codegen/xai-grok-memory/src/watcher.rs:MemoryFileWatcher crates/codegen/xai-grok-memory/src/mmr.rs:mmr_rerank crates/codegen/xai-grok-config-types/src/memory.rs:MmrConfig 預設值
原始碼快照說明:依據本地倉庫 grok-build-main,核對日期 2026-07-17。程式碼塊保留真實函式與分支,唯一的摺疊處已用註釋說明;流程圖明確標為教學化結構圖。
課堂練習
06

推演一次降級查詢

假設 watcher 發現一個檔案被修改,query embedding 隨後失敗,MMR 保持預設配置。請按順序寫出索引更新、候選生成、加權排序與最終截斷,並標出沒有發生的兩個步驟。

Takeaway:記憶檢索具備可降級與可同步兩條關鍵保障。FTS 始終提供基礎候選,向量搜尋按可用性增強;時間衰減和來源權重調整排序;MMR 需要顯式開啟;watcher 讓外部 Markdown 修改在下一次查詢前進入索引。