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 경로를 누적합니다. 백엔드는 검색 시작 시 추가되거나 수정된 파일을 재인덱싱하고, 삭제된 파일의 오래된 chunk를 제거합니다.

02 · FTS

BM25 후보

먼저 표준 FTS를 실행하고, global 및 workspace 소스 쿼리로 보완하여 세션 수 과다로 인한 밀어내기를 줄입니다.

03 · VECTOR

선택적 KNN

sqlite-vec와 provider가 사용 가능할 때만 쿼리를 임베딩합니다. Embedding 오류는 warning으로 기록되며, None을 전달하여 FTS-only를 계속합니다.

04 · SCORE

정규화 및 병합

BM25 점수와 벡터 L2 거리를 각각 정규화합니다. 이중 경로 히트 시 가중치로 병합하되, 결과가 해당 chunk의 FTS 점수보다 낮지 않도록 보장합니다.

05 · WEIGHT

시간 및 소스

세션은 반감기로 지수 감쇠하며, 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: falselambda: 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: 메모리 검색에는 두 가지 핵심 보장이 있습니다: 우아한 다운그레이드와 sync-on-search. FTS는 항상 기본 후보를 제공하고 벡터 검색은 가용성에 따라 강화합니다. 시간 감쇠와 소스 가중치가 순위를 조정하며, MMR은 명시적 활성화가 필요합니다. watcher는 외부 Markdown 수정이 다음 쿼리 전에 인덱싱되도록 보장합니다.