Files
kimandClaude Sonnet 5 70c1e1c23f RAG 기억 검색에 하이브리드 검색(벡터+키워드) + 재랭킹 추가
Chroma 벡터검색만으로는 IP·모델명 같은 정확한 용어를 놓치는 경우가 있어
SQLite FTS5(trigram) 키워드검색을 병합하고, 애매한 경우(예: 클로서버 vs
지서버 GPU 스펙 혼동)만 LLM 재랭킹으로 오답을 걸러내도록 함. 재랭킹은
후보가 없거나 확실한 단일매치일 때는 건너뛰어 대부분의 대화에서는 지연시간
증가가 거의 없음. 기존 570개 기록은 scripts/backfill-fts.ts로 백필.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 15:12:50 +09:00

40 lines
1.6 KiB
TypeScript

// One-time backfill: mirror existing Chroma vector-memory records into the new FTS5
// keyword-search sidecar (memory-fts.ts, added 2026-08-06 hybrid-search upgrade). Only
// needed once — addVector() keeps the two in sync for everything written after this ran.
import { ChromaClient } from 'chromadb';
import { USER_FACTS_COLLECTION, DAILY_EXTRACTS_COLLECTION } from '../src/gateway/memory/memory-vector';
import { upsertFtsRecord } from '../src/gateway/memory/memory-fts';
async function backfillCollection(client: ChromaClient, name: string) {
const col = await client.getOrCreateCollection({ name, embeddingFunction: null });
const count = await col.count();
console.log(`[${name}] ${count} records`);
const batchSize = 200;
let offset = 0;
let written = 0;
while (offset < count) {
const res = await col.get({ limit: batchSize, offset });
const ids = res.ids || [];
const docs = res.documents || [];
const metas = res.metadatas || [];
for (let i = 0; i < ids.length; i++) {
const text = docs[i] || '';
if (!text) continue;
const workspace = String((metas[i] as any)?.workspace || '');
upsertFtsRecord(name, ids[i], text, workspace);
written++;
}
offset += batchSize;
}
console.log(`[${name}] backfilled ${written} records into FTS`);
}
async function main() {
const client = new ChromaClient({ host: 'localhost', port: 8100 });
await backfillCollection(client, USER_FACTS_COLLECTION);
await backfillCollection(client, DAILY_EXTRACTS_COLLECTION);
console.log('done');
}
main().catch(e => { console.error(e); process.exit(1); });