Aller au contenu principal

File Search : indexer et interroger des documents

File Search : indexer et interroger des documents

File Search permet au modèle de chercher dans vos documents privés. Vous uploadez des fichiers dans un vector store, et le modèle les interroge automatiquement quand il a besoin d’une information. C’est la base du RAG intégré à l’API OpenAI.

Créer un vector store

Le vector store est l’unité d’organisation de File Search, et le découpage que vous choisissez ici vous suivra longtemps. Un store unique pour toute l’entreprise est simple à administrer mais mélange des documents dont les vocabulaires n’ont rien à voir, ce qui dégrade la pertinence ; un store par équipe ou par domaine coûte un peu de gestion et rend chaque recherche nettement plus précise. Dans le doute, séparez : fusionner plus tard est plus facile que démêler.

from openai import OpenAI

client = OpenAI()

# Créer le vector store
vector_store = client.vector_stores.create(
    name="documentation-produit"
)
print(f"Vector store cree : {vector_store.id}")

Uploader et indexer des fichiers

L’indexation est asynchrone, et c’est la source d’erreur la plus fréquente au premier essai : on ajoute des fichiers, on interroge dans la foulée, et le modèle répond qu’il ne trouve rien — non parce que la recherche a échoué, mais parce que rien n’était encore indexé. Vérifiez le statut avant d’interroger, comme le montre la section sur le cycle de vie.

Un mot sur les formats : la liste est large, mais tous ne se valent pas. Un PDF issu d’un traitement de texte s’extrait proprement ; un PDF scanné sans couche de texte n’apportera rien du tout, puisqu’il n’y a littéralement aucun mot à indexer.

import os

# Upload d'un seul fichier
with open("guide-utilisateur.pdf", "rb") as f:
    fichier = client.files.create(file=f, purpose="assistants")

# Attacher au vector store
client.vector_stores.files.create(
    vector_store_id=vector_store.id,
    file_id=fichier.id
)

# Upload en lot
dossier = "./documentation/"
for nom_fichier in os.listdir(dossier):
    chemin = os.path.join(dossier, nom_fichier)
    if os.path.isfile(chemin):
        with open(chemin, "rb") as f:
            fichier = client.files.create(file=f, purpose="assistants")
        client.vector_stores.files.create(
            vector_store_id=vector_store.id,
            file_id=fichier.id
        )
        print(f"Indexe : {nom_fichier}")

L’appel est volontairement banal : vous déclarez l’outil, vous posez votre question, et le modèle décide s’il doit chercher. Tout le travail de RAG — découpage, vectorisation, classement, injection dans le contexte — se fait côté serveur. C’est un gain de temps considérable au démarrage, et la leçon suivante montre les réglages à connaître quand les valeurs par défaut ne suffisent plus.

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Quelle est la procédure de remboursement pour les clients premium ?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id]
    }]
)

print(response.output_text)

Le modèle cherche automatiquement dans vos documents et formule une réponse basée sur le contenu trouvé.

Examiner les résultats de recherche

Les annotations sont ce qui distingue une réponse vérifiable d’une affirmation. Exposez-les à vos utilisateurs : sur une base documentaire interne, pouvoir remonter au document d’origine change complètement le niveau de confiance accordé au système — et permet de repérer les cas où le modèle a répondu à côté.

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Quels sont les SLA garantis dans le contrat ?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id]
    }]
)

# Parcourir les annotations
for item in response.output:
    if item.type == "message":
        for block in item.content:
            if hasattr(block, "annotations"):
                for ann in block.annotations:
                    if ann.type == "file_citation":
                        print(f"Fichier : {ann.file_id}")
                        print(f"Position : {ann.start_index}-{ann.end_index}")

Configurer la recherche

Nombre de résultats

Augmenter le nombre de passages n’améliore pas mécaniquement la réponse. Au-delà d’une dizaine, les extraits faiblement pertinents diluent les bons et occupent du contexte facturé. Montez cette valeur quand la réponse est visiblement incomplète, pas par principe.

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Liste toutes les clauses de pénalité du contrat",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id],
        "max_num_results": 20  # Defaut : 5, max : 50
    }]
)

Filtrage par métadonnées

Les métadonnées sont le levier de précision le plus sous-utilisé. Filtrer sur l’année, le service ou le type de document avant la recherche vectorielle élimine d’emblée le bruit que le classement devrait sinon écarter. C’est aussi ce qui permet de répondre « selon la procédure en vigueur » plutôt que de mélanger trois versions successives du même document.

# Upload avec metadonnees
with open("contrat-2026.pdf", "rb") as f:
    fichier = client.files.create(file=f, purpose="assistants")

client.vector_stores.files.create(
    vector_store_id=vector_store.id,
    file_id=fichier.id,
    attributes={
        "type_document": "contrat",
        "annee": 2026,
        "client": "acme-corp"
    }
)

# Recherche filtree
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Quelle est la date d'échéance ?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id],
        "filters": {
            "type": "eq",
            "key": "type_document",
            "value": "contrat"
        }
    }]
)

Filtres composés

Combinez plusieurs critères avec des opérateurs logiques :

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Resume les obligations contractuelles de 2026",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id],
        "filters": {
            "type": "and",
            "filters": [
                {"type": "eq", "key": "type_document", "value": "contrat"},
                {"type": "gte", "key": "annee", "value": 2026}
            ]
        }
    }]
)

Gérer le cycle de vie du vector store

Surveiller l’indexation

# Vérifier le statut du vector store
vs = client.vector_stores.retrieve(vector_store.id)
print(f"Statut : {vs.status}")
print(f"Fichiers : {vs.file_counts.completed} indexes, "
      f"{vs.file_counts.in_progress} en cours, "
      f"{vs.file_counts.failed} en echec")

Supprimer des fichiers obsolètes

# Lister les fichiers du vector store
fichiers = client.vector_stores.files.list(vector_store.id)
for f in fichiers.data:
    print(f"{f.id} - {f.status}")

# Detacher un fichier
client.vector_stores.files.delete(
    vector_store_id=vector_store.id,
    file_id="file_abc123"
)

Exemple complet : assistant documentaire

def assistant_documentaire(question: str, vs_id: str) -> dict:
    """Assistant qui répond en se basant sur la documentation interne."""
    response = client.responses.create(
        model="gpt-5.6-terra",
        instructions=(
            "Tu es un assistant documentaire. Réponds uniquement en te basant "
            "sur les documents fournis. Si l'information n'est pas dans les "
            "documents, dis-le clairement. Cite les sources."
        ),
        input=question,
        tools=[{
            "type": "file_search",
            "vector_store_ids": [vs_id],
            "max_num_results": 10
        }]
    )

    return {
        "reponse": response.output_text,
        "modele": response.model,
        "tokens": response.usage.total_tokens
    }

Points clés à retenir

  • Créez un vector store et uploadez vos fichiers pour activer File Search
  • Le modèle décide seul quand chercher dans les documents
  • max_num_results contrôle la profondeur de recherche
  • Les métadonnées et filtres permettent une recherche ciblée
  • Surveillez le statut d’indexation avant d’interroger