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:

RetrieverUse it for
NearestNeighbourRetrieverVector search: chunks whose meaning is closest to the query, even when they use different words.
KeywordRetrieverKeyword search: chunks that share terms with the query, such as names, identifiers, or error codes.
RRFRetrieverHybrid 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_k or a different filter for the keyword search and the vector search
  • Requiring every keyword (operator="and"), or matching a field other than content
  • 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:

OptionTypeDefaultPurpose
top_kint20Number of chunks to return. Capped at 100 on the retriever that you pass to retrieve.
filterFilter | NoneNoneKeep only the chunks whose fields match. See Filters.
NearestNeighbourRetriever

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)
)
OptionTypeDefaultPurpose
querystr | NoneNoneQuery text. With a Mistral embedding model, the index embeds it.
query_embeddinglist[float] | NoneNoneQuery vector. Required with a custom embedding model, which the index can't call.
fieldstr | NoneNoneEmbedding field to rank against. None uses the chunk embedding, content_embedding.
max_candidatesint | NoneNoneSize 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

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)
)
OptionTypeDefaultPurpose
querystr(required)Query text to match.
fieldstr | NoneNoneField 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

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),
        ],
    )
)
OptionTypeDefaultPurpose
retrieverslist(required)One or more child retrievers, each a KeywordRetriever or a NearestNeighbourRetriever. An RRFRetriever can't be a child.
weightslist[float] | NoneNoneOne positive weight per child, in order. None weights every child equally.
rank_constantint60Smoothing constant. Lower values give more weight to the top ranks of each child.
filterFilter | NoneNoneApplies 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

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:

Retrieverscoredistance
NearestNeighbourRetriever1 - distance for cosine; -distance for l2 and inner_productThe raw vector distance
KeywordRetrieverA keyword relevance score: higher when the chunk matches the query terms betterNone
RRFRetrieverThe fused RRF scoreNone

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.

See also

See also

  • Filters: narrow the results of any retriever.
  • Retrieval: an overview of retrieval with a Mistral Search Index.