Mistral Search Index

Utilisez Search Index, le stockage de recherche hébergé par Mistral, pour stocker et rechercher les fragments de vos pipelines Search Toolkit. MistralSearchIndex implémente VectorStoreIndex, NavigableIndex et RetrievableIndex, et utilise donc les mêmes interfaces de pipeline que les backends Vespa et Postgres. Vous n'avez pas à provisionner ni à gérer de base de données : l'index réside dans votre espace de travail Mistral.

Le client est inclus dans le package principal et ne nécessite aucune dépendance optionnelle :

uv add mistralai-search-toolkit
Note

Search Index est en préversion publique. Son API, son comportement et ses outils peuvent évoluer pendant cette phase, et une mise à jour peut nécessiter des modifications de votre code.

Fonctionnement

Fonctionnement

Le client se compose de trois objets, tous situés dans mistralai.search.toolkit.mistral_search_index :

  • MistralSearchIndexCollectionSchema : déclare le nom de l'index, le modèle de représentation vectorielle et les champs personnalisés dérivés de votre sous-classe de Document.
  • MistralSearchIndexService : gère le cycle de vie de l'index (create_index, update_index, delete_index, get_index_info, index_exists) et récupère le store d'un index existant avec get_search_index.
  • MistralSearchIndex : indexe, supprime, recherche, récupère et parcourt des documents. Transmettez-le à Pipeline(stores=...) pour l'ingestion, et interrogez-le avec search et retrieve.

Le serveur fait autorité : get_search_index lit le schéma et le modèle de représentations vectorielles depuis l'index. Un processus qui effectue uniquement des requêtes n'a donc pas besoin de déclarer le schéma. Ne construisez pas vous-même MistralSearchIndex. Récupérez-le plutôt avec get_search_index pour qu'il corresponde à l'index sur le serveur.

Authentification

Authentification

MistralSearchIndexService() construit un client Mistral à partir de la variable d'environnement MISTRAL_API_KEY et lève une exception RuntimeError si elle n'est pas définie. Pour appeler un Point de terminaison API autre que https://api.mistral.ai, définissez MISTRAL_API_URL. Pour réutiliser un client déjà configuré, passez-le en argument :

from mistralai.client import Mistral
from mistralai.search.toolkit.mistral_search_index import MistralSearchIndexService

service = MistralSearchIndexService(client=Mistral(api_key="..."))

Pour créer un index, vous devez disposer de l'autorisation create_search_indexes dans l'espace de travail. Pour comprendre le fonctionnement des autorisations, consultez Créer un index.

Déclarer une collection

Déclarer une collection

Générez le schéma à partir de votre type de document avec from_document_type. Comme pour les autres backends, embedding_model accepte un EmbeddingModel ou un MistralEmbeddingPreset, décrits dans Modèle de Représentations vectorielles :

from typing import Annotated, Sequence

from mistralai.search.toolkit.document import Document, DocumentChunk
from mistralai.search.toolkit.embedding import MistralEmbeddingPreset
from mistralai.search.toolkit.mistral_search_index.schema import (
    MistralSearchIndexCollectionSchema,
    MistralSearchIndexField,
    MistralSearchIndexFieldType,
)


class ReportChunk(DocumentChunk):
    page: Annotated[int | None, MistralSearchIndexField(indexed=True)] = None


class ReportDocument(Document):
    title: str
    category: Annotated[str, MistralSearchIndexField(type=MistralSearchIndexFieldType.KEYWORD, indexed=True)]
    chunks: Sequence[ReportChunk] = []


SCHEMA = MistralSearchIndexCollectionSchema.from_document_type(
    collection_name="reports",
    document_type=ReportDocument,
    embedding_model=MistralEmbeddingPreset.MISTRAL_EMBED_DIM_1024,
)
ChampTypeRôle
collection_namestrNom de l'index. Il doit commencer par une lettre minuscule, ne contenir que des lettres minuscules, des chiffres et des tirets bas isolés, se terminer par une lettre ou un chiffre et compter au maximum 32 caractères. Les noms sont uniques par espace de travail.
document_typetype[Document]Votre sous-classe de Document. Les champs qu'elle et son type de fragment ajoutent aux modèles de base deviennent des champs typés dans l'index.
embedding_modelEmbeddingModel | MistralEmbeddingPresetModèle de Représentations vectorielles, qui définit les dimensions, le type de données et la métrique de distance. Vous ne pouvez plus le modifier une fois l'index créé.

Associer l'embedder à l'index

Le modèle de représentations vectorielles est fixé lors de la création de l'index. Chaque représentation vectorielle que vous envoyez doit provenir du même modèle et avoir les mêmes dimensions, aussi bien lors de l'ingestion que pour les requêtes que vous vectorisez vous-même. En cas de différence, la requête échoue. Associez la configuration prédéfinie au modèle MistralEmbedder correspondant :

PresetDimensionsEmbedder
MISTRAL_EMBED_DIM_128128MistralEmbedder() (le modèle par défaut)
MISTRAL_EMBED_DIM_256256MistralEmbedder(model_name=MODEL_256_EMBEDDING)
MISTRAL_EMBED_DIM_10241024MistralEmbedder(model_name=MODEL_1024_EMBEDDING)

Les constantes MODEL_*_EMBEDDING proviennent de mistralai.search.toolkit.embedding. Sans nom de modèle, MistralEmbedder() utilise le modèle à 128 dimensions. Passez donc model_name si l'index utilise une autre configuration prédéfinie.

Champs personnalisés

Chaque champ que votre sous-classe de Document ou de DocumentChunk ajoute au modèle de base devient un champ typé dans l'index. Les champs du document décrivent le document entier, et ceux du fragment décrivent un passage. La boîte à outils gère les champs de base (source_id, content, chunks, metadata et les autres).

Le toolkit déduit le type de chaque champ à partir de son annotation :

AnnotationType de champIndexé par défaut
strtextOui
StrEnumkeywordOui
intlongNon
floatfloatNon
boolboolNon
datetimetimestampNon
list[T]T[] (keyword[] pour une liste de StrEnum)Non
dict, un modèle PydanticjsonNon

Un champ est obligatoire, sauf si son annotation autorise None. MistralSearchIndexField(type=..., indexed=..., required=..., default=..., ignore=...) remplace toute valeur déduite, et ignore=True exclut un champ de l'index :

from datetime import datetime
from enum import StrEnum

from mistralai.search.toolkit.mistral_search_index.schema import MistralSearchIndexField, MistralSearchIndexFieldType


class Category(StrEnum):
    BILLING = "billing"
    SHIPPING = "shipping"


class Article(Document):
    title: str                                                   # text, full-text searchable
    category: Category                                           # keyword, exact match
    sku: Annotated[str, MistralSearchIndexField(type=MistralSearchIndexFieldType.KEYWORD)]
    published_at: Annotated[datetime | None, MistralSearchIndexField(indexed=True)] = None
    internal_note: Annotated[str | None, MistralSearchIndexField(ignore=True)] = None
  • Utilisez keyword pour les identifiants comme les SKU, les codes ou les tags : un str correspond par défaut à text, un type tokenisé pour la recherche par mot-clé.
  • Les types non pris en charge provoquent une erreur dès la construction du schéma. Un champ dont le type n'a aucune correspondance lève une exception ValueError lors de cette étape. Pour corriger le problème, ajoutez une déclaration explicite MistralSearchIndexField(type=...) ou définissez ignore=True.
  • Les champs obligatoires sont vérifiés localement. Avant chaque écriture, le schéma vérifie que chaque champ personnalisé obligatoire est défini, de sorte qu'un champ manquant entraîne un échec avant l'envoi de la requête.

Pour les règles de nommage des champs et les champs système, voir Champs personnalisés. Pour filtrer sur les champs personnalisés, voir Filtres.

Gérer l'index

Gérer l'index

Les opérations de création, de mise à jour et de suppression s'exécutent de manière asynchrone sur le serveur. create_index renvoie un résultat alors que le provisionnement de l'index est encore en cours. Attendez donc que l'index soit prêt avant d'y écrire :

import asyncio

from mistralai.search.toolkit.mistral_search_index import MistralSearchIndexService
from mistralai.search.toolkit.mistral_search_index.models import MistralSearchIndexStatus

service = MistralSearchIndexService()

if not await service.index_exists("reports"):
    info = await service.create_index(SCHEMA)
    while info.status == MistralSearchIndexStatus.PROVISIONING:
        await asyncio.sleep(1)
        info = await service.get_index_info("reports")
    if info.status != MistralSearchIndexStatus.READY:
        raise RuntimeError(f"provisioning failed: {info.status_message}")

store = await service.get_search_index("reports")  # -> MistralSearchIndex

update_index(schema) transmet un schéma étendu. Les mises à jour sont additives : vous pouvez ajouter des champs optionnels ou activer indexed. Pour toute autre modification, recréez l'index. delete_index(name) supprime l'index et tous ses documents.

Indexation

Indexation

Utilisez le store dans un pipeline d'ingestion :

from mistralai.search.toolkit.embedding import MODEL_1024_EMBEDDING, MistralEmbedder
from mistralai.search.toolkit.ingestion.extractors import MistralOCRExtractor
from mistralai.search.toolkit.ingestion.loaders import FilesystemFileLoader
from mistralai.search.toolkit.ingestion.pipelines import Pipeline
from mistralai.search.toolkit.ingestion.text_splitters import MarkdownTextSplitter, MarkdownTextSplitterConfig

pipeline = Pipeline(
    loader=FilesystemFileLoader(),
    extractor=MistralOCRExtractor(),
    text_splitter=MarkdownTextSplitter(MarkdownTextSplitterConfig()),
    processors=[ReportEnricher()],  # your processor that sets title, category, and page
    embedder=MistralEmbedder(model_name=MODEL_1024_EMBEDDING),
    stores=store,
)
await pipeline.run(documents=["docs/report.pdf"])

L'index ne calcule pas les représentations vectorielles lors de l'ingestion. Chaque fragment doit donc en contenir une. Conservez dans le pipeline un composant de vectorisation qui utilise le modèle de l'index (MISTRAL_EMBED_DIM_1024 dans cet exemple). Si un Pipeline écrit dans un index vectoriel sans composant de vectorisation, une erreur est levée lors de sa construction. Le source_id d'un document constitue son identité : indexer à nouveau le même source_id remplace le document et ses fragments.

index_document lève une exception DocumentRejectedError, sous-classe de IndexingError, lorsque le serveur rejette le document pour une raison déterministe, comme un champ manquant ou un texte que l'index ne peut pas stocker. Le message indique chaque champ en échec. Réessayer ne résout pas cette erreur : corrigez les données avant d'envoyer à nouveau le document. Un pipeline signale l'échec du document et poursuit le traitement du reste du batch. delete_document(doc_id) accepte l'identifiant du document, et non celui de la source, et lève une exception DocumentNotFoundError si aucun document ne correspond.

Recherche vectorielle et hybride

search(VectorSearchQuery) construit le retriever pour vous. VectorRetriever et QueryEngine fonctionnent donc sans modification :

  • Avec uniquement un embedding, il exécute un module de recherche des plus proches voisins.
  • Avec à la fois query et embedding, il combine un module de recherche par mots-clés et un module de recherche des plus proches voisins par fusion des rangs réciproques.
from mistralai.search.toolkit.search import VectorSearchQuery

results = await store.search(
    VectorSearchQuery(query="quarterly revenue", embedding=vec, top_k=10)
)

La requête respecte également exclude_ids, exclude_source_ids et approximate_options=ApproximateQueryOptions(max_candidates=...). La recherche par mots-clés porte uniquement sur content, et chaque résultat inclut ses champs personnalisés.

Récupération

Pour un guide complet, consultez Recherche documentaire. Pour filtrer sur vos champs, rechercher des mots-clés dans un champ personnalisé ou ajuster la fusion, appelez retrieve avec un module de recherche de mistralai.search.toolkit.mistral_search_index.retriever :

from mistralai.search.toolkit.mistral_search_index.retriever import (
    In,
    KeywordRetriever,
    NearestNeighbourRetriever,
    RRFRetriever,
)

results = await store.retrieve(
    RRFRetriever(
        top_k=10,
        filter=In(field="category", values=["financial", "legal"]),
        retrievers=[
            KeywordRetriever(query="termination clauses", top_k=10),
            KeywordRetriever(query="termination clauses", field="title", top_k=10),
            NearestNeighbourRetriever(query="termination clauses", top_k=10),
        ],
        weights=[1.0, 2.0, 1.0],
    )
)
RetrieverRôle
NearestNeighbourRetrieverRecherche approximative des plus proches voisins parmi les représentations vectorielles des fragments. Avec un modèle de représentations vectorielles Mistral, passez query : le serveur vectorise la requête. Avec un modèle personnalisé, passez query_embedding.
KeywordRetrieverCorrespondance de texte intégral sur content, ou sur un autre champ text indexé via field. operator est "or" (par défaut) ou "and".
RRFRetrieverCombine les classements de ses modules de recherche enfants par fusion des rangs réciproques. weights et rank_constant permettent de l'ajuster, et son filter s'applique à chaque module enfant.

Chaque retriever prend un filter construit à partir de Equal, In, Range, And, Or et Not. Le top_k de premier niveau est plafonné à 100. Pour les limites et la sémantique des scores, voir Interroger un index.

Navigation

MistralSearchIndex implémente NavigableIndex : navigate, read, grep et get_chunk parcourent un document source dans l'ordre de lecture plutôt que par pertinence. Ces méthodes sont les primitives sur lesquelles repose la Recherche Agentique. Pour connaître chaque méthode et ses cas d'utilisation, consultez Navigation dans les documents.

Limites

Limites

  • Pas de mises à jour partielles. MistralSearchIndex n'implémente ni PatchableIndex ni VersionedIndex. Pour modifier un document, réindexez-le.
  • Vérifications de l'état. Les appels de documents et de requêtes sur un index qui n'est pas ready échouent. Le service lève une IndexNotFoundError lorsqu'un appel de cycle de vie cible un index qui n'existe pas ou auquel vous n'avez pas accès.
Voir aussi

Voir aussi