Cogitator
RAG Pipeline

Retrieval Strategies

Retrieve relevant document chunks using similarity search, MMR diversity, hybrid vector+keyword search, or multi-query expansion.

Overview

Retrievers take a query string, embed it, and search the vector store for relevant chunks. All retrievers implement the Retriever interface:

interface Retriever {
  retrieve(query: string, options?: Partial<RetrievalConfig>): Promise<RetrievalResult[]>;
}

Each RetrievalResult contains chunkId, documentId, the chunk content, a relevance score, source (the document path or URL) and metadata linking back to the source document (document metadata, documentId, offsets). Retrievers throw when the underlying search fails.

The examples below assume an embedding service and a store, e.g.:

import { InMemoryEmbeddingAdapter, OpenAIEmbeddingService } from '@cogitator-ai/memory';

const embeddings = new OpenAIEmbeddingService({ apiKey: process.env.OPENAI_API_KEY! });
const store = new InMemoryEmbeddingAdapter();

Similarity Retriever

Pure cosine similarity search. Embeds the query and returns the closest vectors from the store. Simple and effective for most cases.

import { SimilarityRetriever } from '@cogitator-ai/rag';

const retriever = new SimilarityRetriever({
  embeddingAdapter: store,
  embeddingService: embeddings,
  defaultTopK: 10,
  defaultThreshold: 0.5,
});

const results = await retriever.retrieve('How do agents handle errors?');
OptionTypeDefaultDescription
embeddingAdapterEmbeddingAdapter—Vector store to search
embeddingServiceEmbeddingService—Embedding service for query vectors
defaultTopKnumber10Default number of results
defaultThresholdnumber0.0Minimum similarity score

When to use: Default choice. Works well when your queries are specific and you don't need diversity in results.

MMR Retriever

Maximal Marginal Relevance balances relevance with diversity. It fetches 3x candidates, then iteratively selects results that are relevant to the query but dissimilar to already-selected results. This avoids returning near-duplicate chunks.

import { MMRRetriever } from '@cogitator-ai/rag';

const retriever = new MMRRetriever({
  embeddingAdapter: store,
  embeddingService: embeddings,
  defaultLambda: 0.7,
  defaultTopK: 10,
});

const results = await retriever.retrieve('What tools are available?', { mmrLambda: 0.5 });

Each result's score is its MMR score; the original similarity is kept in metadata.originalScore. Pass mmrLambda per query to override defaultLambda.

Some vector stores (for example Qdrant) do not return stored vectors from a search; in that case the candidates are re-embedded with the embedding service so diversity can still be computed.

The lambda parameter controls the relevance/diversity tradeoff:

  • lambda = 1.0 — pure relevance (same as similarity search)
  • lambda = 0.5 — balanced
  • lambda = 0.0 — maximum diversity
OptionTypeDefaultDescription
embeddingAdapterEmbeddingAdapter—Vector store to search
embeddingServiceEmbeddingService—Embedding service for query vectors
defaultLambdanumber0.7Relevance vs diversity tradeoff (0.0 – 1.0)
defaultTopKnumber10Default number of results
defaultThresholdnumber0.0Minimum similarity score for candidates

When to use: When top results tend to be repetitive (e.g., similar paragraphs from different sections). Great for building diverse context windows.

Hybrid Retriever

Combines vector similarity search with keyword search using Reciprocal Rank Fusion, via a HybridSearch instance from @cogitator-ai/memory.

import { HybridRetriever } from '@cogitator-ai/rag';
import { HybridSearch, PostgresAdapter, unwrap } from '@cogitator-ai/memory';

const db = new PostgresAdapter({
  provider: 'postgres',
  connectionString: process.env.DATABASE_URL!,
});
db.setVectorDimensions(embeddings.dimensions);
unwrap(await db.connect());

const hybridSearch = new HybridSearch({
  embeddingAdapter: db,
  embeddingService: embeddings,
  keywordAdapter: db, // full-text search in Postgres; omit to use the in-process BM25 index
});

const retriever = new HybridRetriever({
  hybridSearch,
  defaultWeights: { bm25: 0.3, vector: 0.7 },
  defaultTopK: 10,
});

const results = await retriever.retrieve('error handling in tool execution');

When used inside a RAGPipeline (via withHybridSearch() or withRetriever()), every ingested chunk is also added to the HybridSearch in-process BM25 index (keyed by embedding id), and keyword-only hits are mapped back to their chunk id, document id and metadata. That index lives in memory and only holds chunks ingested in the current process — for a persistent store, pass a keywordAdapter (Postgres full-text search or InMemoryEmbeddingAdapter) so keyword search covers everything stored. Custom retrievers can receive the same notifications by implementing ChunkIndexer.indexChunk().

Each result includes vectorScore and keywordScore in its metadata, so you can inspect how each signal contributed.

OptionTypeDefaultDescription
hybridSearchHybridSearch—HybridSearch from @cogitator-ai/memory
defaultWeightsobjectHybridSearch's ({ bm25: 0.4, vector: 0.6 }){ bm25: number, vector: number } weights
defaultTopKnumber10Default number of results
defaultThresholdnumber0.0Minimum vector similarity for the vector half

When to use: When queries contain specific terms (error codes, function names, product IDs) that benefit from exact keyword matching alongside semantic understanding.

MultiQuery Retriever

Expands a single query into multiple variants, retrieves results for each in parallel, and merges them by taking the highest score per chunk. You provide the query expansion function — typically an LLM call. The original query is always searched; generated variants are trimmed, de-duplicated (case-insensitive) and capped by defaultMaxQueries or the per-query multiQueryCount. If expansion throws, the original query is used alone; if every retrieval fails, the first error is thrown.

import { Cogitator } from '@cogitator-ai/core';
import { MultiQueryRetriever, SimilarityRetriever } from '@cogitator-ai/rag';

const cog = new Cogitator({
  llm: { providers: { google: { apiKey: process.env.GOOGLE_API_KEY! } } },
});
const llm = cog.getLLMBackend('google/gemini-3.5-flash-lite');

const baseRetriever = new SimilarityRetriever({
  embeddingAdapter: store,
  embeddingService: embeddings,
});

const retriever = new MultiQueryRetriever({
  baseRetriever,
  defaultMaxQueries: 3,
  expandQuery: async (query) => {
    const response = await llm.chat({
      model: 'gemini-3.5-flash-lite',
      messages: [
        {
          role: 'user',
          content:
            `Generate 3 alternative search queries for: "${query}"\n` +
            'Return one query per line, no numbering.',
        },
      ],
    });
    return response.content.split('\n').filter(Boolean);
  },
});

const results = await retriever.retrieve('How do I configure memory?');
OptionTypeDefaultDescription
baseRetrieverRetriever—Underlying retriever for each query
expandQuery(query: string) => Promise<string[]>—Function that generates query variants
defaultTopKnumber10Results returned after merging
defaultMaxQueriesnumberallMax generated variants used

Per-query options are passed to the base retriever for every variant, so topK applies both per variant and to the merged list.

When to use: When user queries are ambiguous or under-specified. The LLM generates variations that cover different angles of the question.

createRetriever Factory

Create a retriever from a config object. Here topK, threshold, lambda, weights and maxQueries set the retriever's defaults:

import { createRetriever } from '@cogitator-ai/rag';

const retriever = createRetriever({
  strategy: 'mmr',
  embeddingAdapter: store,
  embeddingService: embeddings,
  lambda: 0.6,
  topK: 15,
});

Supported strategy configs:

createRetriever({
  strategy: 'similarity',
  embeddingAdapter: store,
  embeddingService: embeddings,
});

createRetriever({
  strategy: 'mmr',
  embeddingAdapter: store,
  embeddingService: embeddings,
  lambda: 0.7,
});

createRetriever({
  strategy: 'hybrid',
  hybridSearch,
  weights: { bm25: 0.3, vector: 0.7 },
});

createRetriever({
  strategy: 'multi-query',
  baseRetriever: similarityRetriever,
  expandQuery: queryExpansionFn,
  maxQueries: 3,
});

Comparison

StrategyDiversityKeyword SupportLLM CallsBest For
similarityLowNoNoSpecific queries, default choice
mmrHighNoNoDiverse context windows
hybridMediumYesNoQueries with exact terms
multi-queryHighDepends on baseYesAmbiguous or broad queries

Per-Query Options

All retrievers accept per-query overrides:

const results = await retriever.retrieve('query', {
  topK: 5,
  threshold: 0.7,
});

On this page