Aller au contenu principal

File Search vs RAG custom : quand utiliser quoi

Mis à jour le 29 juillet 2026

Objectifs

  • Comprendre le RAG managé d’OpenAI (File Search)
  • Comparer File Search avec un pipeline RAG custom
  • Choisir la bonne approche selon votre cas d’usage

File Search : le RAG managé d’OpenAI

Vous venez de construire, leçon après leçon, chaque étage d’un pipeline RAG. File Search fait tout cela pour vous. C’est un outil intégré à la Responses API d’OpenAI qui gère automatiquement le chunking, l’embedding, le stockage vectoriel et la recherche : vous uploadez vos fichiers, et vous interrogez. Cette leçon n’est donc pas une remise en cause de ce qui précède, mais la question honnête qu’il faut se poser avant de coder quoi que ce soit — celle du bon niveau d’abstraction pour votre projet.

Tout part d’un vector store, le conteneur qui héberge vos documents indexés. On le crée, on y pousse les fichiers un à un, puis on attend la fin de l’indexation avant d’interroger. Cette boucle d’attente n’est pas cosmétique : l’indexation est asynchrone, et une requête lancée trop tôt ne verra qu’une partie du corpus.

from openai import OpenAI

client = OpenAI()

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

# 2. Uploader des fichiers
fichiers = ["guide_utilisateur.pdf", "faq.md", "changelog.txt"]

for fichier in fichiers:
    with open(fichier, "rb") as f:
        file_obj = client.files.create(file=f, purpose="assistants")
        client.vector_stores.files.create(
            vector_store_id=vector_store.id,
            file_id=file_obj.id
        )
        print(f"  Uploadé : {fichier}")

# Attendre que l'indexation soit terminée
import time
while True:
    vs = client.vector_stores.retrieve(vector_store.id)
    if vs.file_counts.completed == vs.file_counts.total:
        break
    time.sleep(2)
print("Indexation terminée")

L’interrogation tient ensuite en un appel : on déclare l’outil file_search avec l’identifiant du store, et le modèle décide lui-même d’aller chercher. La partie intéressante est la lecture des annotations, qui contiennent les citations de fichiers. C’est ce qui vous permet d’afficher les sources à l’utilisateur — le comportement que vous aviez dû implémenter à la main dans le pipeline custom.

# Requête avec File Search
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Quelles sont les nouvelles fonctionnalités de la v3.2 ?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id]
    }]
)

print(response.output_text)

# Accéder aux sources citées
for annotation in response.output:
    if hasattr(annotation, "annotations"):
        for ann in annotation.annotations:
            if ann.type == "file_citation":
                print(f"  Source : fichier {ann.file_citation.file_id}")

Deux réglages permettent d’affiner le comportement sans quitter l’outil managé. max_num_results fixe le nombre de chunks récupérés, et score_threshold filtre les résultats trop faibles, exactement comme le seuil de pertinence que vous appliquiez après le reranking.

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Comment configurer le SSO ?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [vector_store.id],
        "max_num_results": 10,         # Nombre de chunks à récupérer
        "ranking_options": {
            "ranker": "auto",           # Ranker automatique
            "score_threshold": 0.3      # Seuil de pertinence
        }
    }]
)

Ce que File Search vous donne, ce qu’il vous retire

L’argument décisif de File Search est l’absence totale d’infrastructure : pas de base vectorielle à provisionner, à sauvegarder ni à surveiller. Le chunking est fait pour vous et optimisé par OpenAI, la mise à jour du corpus se réduit à ajouter ou supprimer un fichier, les citations sont générées automatiquement, et les formats courants sont pris en charge nativement — PDF, DOCX, MD, TXT, HTML, JSON entre autres. Une équipe qui n’a pas de profil infrastructure met une base de connaissances en ligne dans l’après-midi.

Le revers de ce confort est la perte de contrôle sur chaque maillon. Vous ne choisissez pas la taille des chunks, donc le compromis précision/contexte de la leçon 12 vous échappe. La recherche est purement sémantique : pas d’hybride, donc les références produit et les codes d’erreur exacts restent fragiles. Impossible non plus de brancher Cohere ou un cross-encoder maison en reranking. À cela s’ajoutent trois considérations plus stratégiques : le stockage vectoriel est facturé en plus des appels API, vos données résident sur les serveurs d’OpenAI, et l’indexation ne couvre que le texte.

Un pipeline custom inverse terme à terme cette liste. Vous maîtrisez le chunking, l’embedding, la recherche, le reranking et le prompt ; vous combinez sémantique et lexicale ; vous mélangez les fournisseurs de modèles, ou vous gardez tout on-premise si vos données l’exigent ; et surtout, vous optimisez chaque composant indépendamment en mesurant son effet. Ce contrôle se paie en code à écrire et à maintenir, en base vectorielle à administrer, et en expertise — le chunking, l’évaluation et le reranking ne s’improvisent pas, c’est précisément l’objet de ce cours.

Matrice de décision

CritèreFile SearchRAG custom
Prototype rapide✅ Idéal⚠️ Plus lent
Contrôle du chunking
Recherche hybride
Données sensibles⚠️ Chez OpenAI✅ On-premise
Coût à grande échelle⚠️ Peut être élevé✅ Prévisible
Maintenance✅ Zéro⚠️ À gérer
Performance maximale⚠️ Bonne✅ Optimisable

Architecture hybride : le meilleur des deux

Le choix n’a pas à être définitif, et il ne devrait pas l’être. La démarche la plus rentable consiste à démarrer avec File Search pour valider que votre cas d’usage tient debout, puis à basculer vers un pipeline custom le jour où vous butez sur une limite identifiée. Encapsuler les deux derrière une même méthode repondre rend ce basculement indolore pour le reste de votre application.

class RAGHybride:
    """Commence avec File Search, bascule vers custom si nécessaire."""

    def __init__(self, mode: str = "file_search"):
        self.client = OpenAI()
        self.mode = mode

    def repondre_file_search(self, question: str, vs_id: str) -> str:
        response = self.client.responses.create(
            model="gpt-5.6-terra",
            input=question,
            tools=[{
                "type": "file_search",
                "vector_store_ids": [vs_id]
            }]
        )
        return response.output_text

    def repondre_custom(self, question: str, pipeline) -> str:
        resultat = pipeline.repondre(question)
        return resultat["reponse"]

    def repondre(self, question: str, **kwargs) -> str:
        if self.mode == "file_search":
            return self.repondre_file_search(
                question, kwargs["vs_id"]
            )
        else:
            return self.repondre_custom(
                question, kwargs["pipeline"]
            )

Le jour où la migration s’impose, elle suit un ordre précis :

  1. Exportez vos fichiers du vector store
  2. Implémentez le chunking adapté à votre contenu
  3. Générez les embeddings avec text-embedding-3-large
  4. Indexez dans une base vectorielle (Qdrant, Pinecone, etc.)
  5. Ajoutez le reranking et la recherche hybride
  6. Évaluez avec votre dataset de test
  7. Comparez les métriques avec File Search avant de basculer

La dernière étape est celle qu’on saute le plus souvent, et c’est la seule qui justifie tout le reste : sans comparaison chiffrée, vous aurez peut-être remplacé un système managé correct par un système maison moins bon, avec la maintenance en supplément.

Résumé

  • File Search est idéal pour prototyper rapidement un RAG sans infrastructure
  • Un pipeline RAG custom offre un contrôle total et des performances optimisables
  • Commencez par File Search, migrez vers custom quand vous atteignez les limites
  • Les critères de décision : contrôle, données sensibles, coût, performance