# Retrieval Strategies

URL: https://cogitator.app/docs/rag/retrieval

> 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:

```typescript
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.:

```typescript
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.

```typescript
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?');
```

| Option             | Type             | Default | Description                         |
| ------------------ | ---------------- | ------- | ----------------------------------- |
| `embeddingAdapter` | EmbeddingAdapter | —       | Vector store to search              |
| `embeddingService` | EmbeddingService | —       | Embedding service for query vectors |
| `defaultTopK`      | number           | 10      | Default number of results           |
| `defaultThreshold` | number           | 0.0     | Minimum 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.

```typescript
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

| Option             | Type             | Default | Description                                 |
| ------------------ | ---------------- | ------- | ------------------------------------------- |
| `embeddingAdapter` | EmbeddingAdapter | —       | Vector store to search                      |
| `embeddingService` | EmbeddingService | —       | Embedding service for query vectors         |
| `defaultLambda`    | number           | 0.7     | Relevance vs diversity tradeoff (0.0 – 1.0) |
| `defaultTopK`      | number           | 10      | Default number of results                   |
| `defaultThreshold` | number           | 0.0     | Minimum 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`](/docs/memory/hybrid-search) instance from `@cogitator-ai/memory`.

```typescript
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.

| Option             | Type         | Default                                       | Description                                   |
| ------------------ | ------------ | --------------------------------------------- | --------------------------------------------- |
| `hybridSearch`     | HybridSearch | —                                             | `HybridSearch` from `@cogitator-ai/memory`    |
| `defaultWeights`   | object       | HybridSearch's (`{ bm25: 0.4, vector: 0.6 }`) | `{ bm25: number, vector: number }` weights    |
| `defaultTopK`      | number       | 10                                            | Default number of results                     |
| `defaultThreshold` | number       | 0.0                                           | Minimum 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.

```typescript
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?');
```

| Option              | Type                                   | Default | Description                            |
| ------------------- | -------------------------------------- | ------- | -------------------------------------- |
| `baseRetriever`     | Retriever                              | —       | Underlying retriever for each query    |
| `expandQuery`       | (query: string) => Promise\<string\[]> | —       | Function that generates query variants |
| `defaultTopK`       | number                                 | 10      | Results returned after merging         |
| `defaultMaxQueries` | number                                 | all     | Max 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:

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

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

Supported strategy configs:

```typescript
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

| Strategy      | Diversity | Keyword Support | LLM Calls | Best For                         |
| ------------- | --------- | --------------- | --------- | -------------------------------- |
| `similarity`  | Low       | No              | No        | Specific queries, default choice |
| `mmr`         | High      | No              | No        | Diverse context windows          |
| `hybrid`      | Medium    | Yes             | No        | Queries with exact terms         |
| `multi-query` | High      | Depends on base | Yes       | Ambiguous or broad queries       |

## Per-Query Options

All retrievers accept per-query overrides:

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