Retrievers
A retriever describes a query: what to match, how to rank it, and how many chunks to return. Pass it to the retrieve method of a Mistral Search Index, which runs it as is.
Import the retrievers from mistralai.search.toolkit.mistral_search_index.retriever:
| Retriever | Use it for |
|---|---|
NearestNeighbourRetriever | Vector search: chunks whose meaning is closest to the query, even when they use different words. |
KeywordRetriever | Keyword search: chunks that share terms with the query, such as names, identifiers, or error codes. |
RRFRetriever | Hybrid search: fuses the rankings of several retrievers into one. |
Use retrievers when search can't express the query that you need, for example in the following cases:
- Keyword-only search, or vector-only search with a tuned candidate pool
- Weighting keyword search against vector search, or changing the fusion's
rank_constant - A different
top_kor a different filter for the keyword search and the vector search - Requiring every keyword (
operator="and"), or matching a field other thancontent - Combining several keyword and vector searches into one ranking
Import the retrievers only from this module, not from the toolkit core modules, because these are the types that the index accepts.
Every retriever takes the following options:
| Option | Type | Default | Purpose |
|---|---|---|---|
top_k | int | 20 | Number of chunks to return. Capped at 100 on the retriever that you pass to retrieve. |
filter | Filter | None | None | Keep only the chunks whose fields match. See Filters. |
NearestNeighbourRetriever
NearestNeighbourRetriever ranks chunks by the distance between their embedding and the query embedding, using the distance metric of the index's embedding model.
from mistralai.search.toolkit.mistral_search_index.retriever import NearestNeighbourRetriever
results = await index.retrieve(
NearestNeighbourRetriever(query="How do I rotate an API key?", top_k=10)
)| Option | Type | Default | Purpose |
|---|---|---|---|
query | str | None | None | Query text. With a Mistral embedding model, the index embeds it. |
query_embedding | list[float] | None | None | Query vector. Required with a custom embedding model, which the index can't call. |
field | str | None | None | Embedding field to rank against. None uses the chunk embedding, content_embedding. |
max_candidates | int | None | None | Size of the approximate nearest-neighbor candidate pool, from 1 to 1000. Must be at least top_k. Raise it to improve recall at the cost of latency. |
Set query, query_embedding, or both. With a custom embedding model, embed the query with the same model that the index uses:
embedding = await embedder.embed_query("How do I rotate an API key?")
results = await index.retrieve(
NearestNeighbourRetriever(query_embedding=embedding, top_k=10)
)KeywordRetriever
KeywordRetriever ranks chunks by how well their text matches the terms of the query. The retriever skips common words, such as "the" or "and".
from mistralai.search.toolkit.mistral_search_index.retriever import KeywordRetriever
results = await index.retrieve(
KeywordRetriever(query="ERR_TOKEN_EXPIRED", top_k=10)
)| Option | Type | Default | Purpose |
|---|---|---|---|
query | str | (required) | Query text to match. |
field | str | None | None | Field to match. None matches the chunk content. Set it to an indexed text custom field, such as title, to match that field instead. |
operator | "or" | "and" | "or" | "or" matches chunks that contain any term of the query. "and" keeps only chunks that contain every term. |
"or" suits natural-language questions, where few chunks contain every word. Use "and" for short, precise queries such as product names or error codes:
results = await index.retrieve(
KeywordRetriever(query="annual report 2024", field="title", operator="and", top_k=10)
)RRFRetriever
RRFRetriever runs its child retrievers and fuses their rankings with reciprocal rank fusion (RRF). For every chunk that a child returns, the child contributes weight / (rank_constant + rank), and the retriever ranks chunks by the sum. Chunks that several children rank highly come first. To get hybrid search, combine a keyword retriever and a vector retriever:
from mistralai.search.toolkit.mistral_search_index.retriever import (
KeywordRetriever,
NearestNeighbourRetriever,
RRFRetriever,
)
question = "termination clauses"
results = await index.retrieve(
RRFRetriever(
top_k=10,
retrievers=[
KeywordRetriever(query=question, top_k=20),
NearestNeighbourRetriever(query=question, top_k=20),
],
)
)| Option | Type | Default | Purpose |
|---|---|---|---|
retrievers | list | (required) | One or more child retrievers, each a KeywordRetriever or a NearestNeighbourRetriever. An RRFRetriever can't be a child. |
weights | list[float] | None | None | One positive weight per child, in order. None weights every child equally. |
rank_constant | int | 60 | Smoothing constant. Lower values give more weight to the top ranks of each child. |
filter | Filter | None | None | Applies to every child, combined with AND with any filter that the child carries. |
The top_k of each child must be at least the top_k of the RRFRetriever, and can go up to 1000. Give the children more candidates than you return. That way, a chunk that one child ranks a little lower can still reach the top fused results.
Weight the children
Only the ratio between weights matters. The following query matches the question against the chunk text and against a custom title field, with twice the weight on title. It fuses both rankings with vector search:
results = await index.retrieve(
RRFRetriever(
top_k=10,
retrievers=[
KeywordRetriever(query=question, top_k=20),
KeywordRetriever(query=question, field="title", top_k=20),
NearestNeighbourRetriever(query=question, top_k=20),
],
weights=[1.0, 2.0, 1.0],
)
)Each KeywordRetriever matches one field. To match several fields, add one child per field.
Tune each child
Each child keeps its own options: its own top_k, its own filter, and its own operator. In the following query, the keyword search requires every keyword in titles and counts those matches twice. The vector search draws from a larger candidate pool and only considers billing documents:
from mistralai.search.toolkit.mistral_search_index.retriever import Equal
results = await index.retrieve(
RRFRetriever(
top_k=10,
weights=[2.0, 1.0],
retrievers=[
KeywordRetriever(query="refund policy", field="title", operator="and", top_k=20),
NearestNeighbourRetriever(
query="refund policy",
top_k=100,
max_candidates=500,
filter=Equal(field="category", value="billing"),
),
],
)
)Results
retrieve returns a list of SearchResult objects, best match first. A higher score means a better match, and its scale depends on the retriever:
| Retriever | score | distance |
|---|---|---|
NearestNeighbourRetriever | 1 - distance for cosine; -distance for l2 and inner_product | The raw vector distance |
KeywordRetriever | A keyword relevance score: higher when the chunk matches the query terms better | None |
RRFRetriever | The fused RRF score | None |
Scores from different retrievers aren't comparable. Each chunk carries the chunk's content, its custom fields, and its identity (id, source_id, locator). Results don't include embeddings.