Hybrid Search
medium15 minhybrid-search.ts
Keyword search misses paraphrases and vector search misses exact terms. You want both, weighted.
What it shows
- Rank with
BM25Indexalone - Embed notes with
GoogleEmbeddingServiceintoInMemoryEmbeddingAdapter - Compare
keyword,vectorandhybridwithHybridSearch
The code
hybrid-search.ts
import { BM25Index, GoogleEmbeddingService, HybridSearch, InMemoryEmbeddingAdapter } from '@cogitator-ai/memory';
const apiKey = process.env.GOOGLE_API_KEY;
if (!apiKey) throw new Error('Set GOOGLE_API_KEY');
const notes = [
{ id: 'note-1', content: 'TypeScript 5.4 introduces the NoInfer utility type for generic functions.' },
{ id: 'note-2', content: 'Bun is a JavaScript runtime built on JavaScriptCore with fast startup times.' },
{ id: 'note-3', content: 'PostgreSQL 16 adds the pg_stat_io view for detailed I/O statistics.' },
{ id: 'note-4', content: 'The CAP theorem: a distributed system guarantees two of consistency, availability, partition tolerance.' },
{ id: 'note-5', content: 'Rust ownership eliminates data races at compile time; the borrow checker prevents use-after-free.' },
];
const bm25 = new BM25Index();
for (const note of notes) bm25.addDocument(note);
console.log('BM25:', bm25.search('type inference', 2).map((hit) => hit.content));
const embeddingService = new GoogleEmbeddingService({ apiKey, model: 'gemini-embedding-001' });
const embeddingAdapter = new InMemoryEmbeddingAdapter();
for (const note of notes) {
await embeddingAdapter.addEmbedding({
sourceId: note.id,
sourceType: 'document',
content: note.content,
vector: await embeddingService.embed(note.content),
});
}
const search = new HybridSearch({
embeddingAdapter,
embeddingService,
defaultWeights: { bm25: 0.4, vector: 0.6 },
});
for (const note of notes) search.indexDocument(note.id, note.content);
const query = 'memory safety without garbage collection';
for (const strategy of ['keyword', 'vector', 'hybrid'] as const) {
const result = await search.search({ query, strategy, limit: 2, threshold: 0.3 });
if (!result.success) continue;
console.log(strategy, result.data.map((hit) => `[${hit.score.toFixed(3)}] ${hit.content.slice(0, 50)}`));
}Run it
install · bash
pnpm add @cogitator-ai/memoryEnvironment
GOOGLE_API_KEYrun · bash
GOOGLE_API_KEY=your-key npx tsx hybrid-search.tsOr run the full example from a clone of the repository (keys in .env):
repository · bash
npx tsx examples/memory/03-semantic-search.ts