Retrieval : chunking et ranking
Retrieval : chunking et ranking
Derriere File Search se cache un pipeline de retrieval complet : vos documents sont decoupes en chunks, vectorises, indexes, puis classes par pertinence au moment de la requete. Comprendre ce pipeline vous permet d’optimiser la qualite des reponses.
Le pipeline de retrieval
Quand vous uploadez un fichier dans un vector store, OpenAI execute automatiquement :
- Parsing : extraction du texte brut depuis le format source
- Chunking : decoupage en segments de taille controlee
- Embedding : vectorisation de chaque chunk
- Indexation : stockage dans l’index vectoriel
Au moment de la requete :
- Embedding de la requete : la question est vectorisee
- Recherche vectorielle : les chunks les plus proches sont identifies
- Ranking : les resultats sont re-classes par pertinence
- Injection : les meilleurs chunks sont injectes dans le contexte du modele
Configurer la strategie de chunking
Vous pouvez controler la taille et le recouvrement des chunks :
from openai import OpenAI
client = OpenAI()
# Strategie par defaut : auto
vector_store = client.vector_stores.create(
name="base-juridique",
chunking_strategy={"type": "auto"}
)
# Strategie personnalisee : chunks plus petits pour plus de precision
vector_store = client.vector_stores.create(
name="faq-support",
chunking_strategy={
"type": "static",
"static": {
"max_chunk_size_tokens": 400,
"chunk_overlap_tokens": 100
}
}
)
Choisir la bonne taille de chunk
| Type de contenu | max_chunk_size | overlap | Pourquoi |
|---|---|---|---|
| FAQ, Q&R | 200-400 | 50 | Reponses courtes et autonomes |
| Documentation technique | 600-800 | 200 | Paragraphes avec contexte |
| Contrats, textes juridiques | 800-1200 | 300 | Clauses longues et interconnectees |
| Code source | 400-600 | 100 | Fonctions et classes |
Impact du recouvrement
Le chunk_overlap_tokens determine combien de tokens sont partages entre deux chunks consecutifs :
# Texte original (simplifie)
# "La clause 5.1 stipule que le fournisseur doit livrer sous 30 jours.
# En cas de retard, des penalites de 1% par jour s'appliquent.
# La clause 5.2 precise que les penalites sont plafonnees a 15%."
# Sans overlap : la clause 5.1 et les penalites peuvent etre dans des chunks differents
# Avec overlap : le lien entre clause et penalites est preserve
Seuil de pertinence et ranking
Le ranking determine quels chunks sont retenus. Vous pouvez configurer un filtre :
response = client.responses.create(
model="gpt-5.3",
input="Quelle est la politique de retour ?",
tools=[{
"type": "file_search",
"vector_store_ids": [vector_store.id],
"max_num_results": 10,
"ranking_options": {
"ranker": "auto",
"score_threshold": 0.5
}
}]
)
Le score_threshold filtre les resultats en dessous d’un seuil de pertinence. Une valeur de 0.0 retourne tout, 1.0 serait trop restrictif. En pratique, 0.3 a 0.6 donne de bons resultats.
Optimiser la qualite du retrieval
Preparer les documents
La qualite du retrieval depend enormement de la preparation des documents :
# Mauvais : PDF scanne avec OCR approximatif
# -> Chunks incoherents, embeddings de mauvaise qualite
# Bon : document structure avec titres et sections
# -> Chunks alignes sur les sections logiques
# Pattern : pre-traiter avant upload
def preparer_document(texte_brut: str) -> str:
"""Nettoie et structure le texte avant indexation."""
# Supprimer les en-tetes/pieds de page repetitifs
lignes = texte_brut.split("\n")
lignes = [l for l in lignes if not est_entete_pied(l)]
# Normaliser les espaces
texte = "\n".join(lignes)
texte = re.sub(r"\n{3,}", "\n\n", texte)
return texte
Enrichir avec des metadonnees
Les metadonnees permettent de pre-filtrer avant la recherche vectorielle, ce qui ameliore la precision :
# Upload avec metadonnees riches
documents = [
{"fichier": "politique-rh-2026.pdf", "departement": "rh", "type": "politique", "annee": 2026},
{"fichier": "contrat-fournisseur-a.pdf", "departement": "achats", "type": "contrat", "annee": 2026},
{"fichier": "guide-securite-it.pdf", "departement": "dsi", "type": "guide", "annee": 2025},
]
for doc in documents:
with open(doc["fichier"], "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={
"departement": doc["departement"],
"type_document": doc["type"],
"annee": doc["annee"]
}
)
Mesurer la qualite du retrieval
Pour evaluer si vos chunks retournent les bonnes informations :
def evaluer_retrieval(questions_test: list[dict], vs_id: str) -> dict:
"""Evalue la qualite du retrieval sur un jeu de test.
Chaque element de questions_test contient :
- question: la question a poser
- reponse_attendue: mots-cles ou phrases attendus dans la reponse
"""
resultats = {"total": 0, "pertinent": 0, "non_pertinent": 0}
for test in questions_test:
response = client.responses.create(
model="gpt-5.3",
input=test["question"],
tools=[{
"type": "file_search",
"vector_store_ids": [vs_id],
"max_num_results": 5
}]
)
reponse = response.output_text.lower()
mots_cles_trouves = sum(
1 for mot in test["reponse_attendue"]
if mot.lower() in reponse
)
resultats["total"] += 1
if mots_cles_trouves >= len(test["reponse_attendue"]) * 0.5:
resultats["pertinent"] += 1
else:
resultats["non_pertinent"] += 1
resultats["taux_pertinence"] = resultats["pertinent"] / resultats["total"]
return resultats
Strategies avancees
Multi-vector-store
Interrogez plusieurs bases documentaires en parallele :
response = client.responses.create(
model="gpt-5.3",
input="Compare notre politique RH avec les obligations legales",
tools=[{
"type": "file_search",
"vector_store_ids": [
vs_politiques_internes,
vs_textes_juridiques
],
"max_num_results": 15
}]
)
Requetes decomposees
Pour des questions complexes, decomposez en sous-requetes :
def recherche_decomposee(question_complexe: str, vs_id: str) -> str:
"""Decompose une question complexe en sous-recherches."""
# Etape 1 : decomposer la question
decomposition = client.responses.create(
model="gpt-5.3",
input=f"Decompose cette question en 2-3 sous-questions simples : {question_complexe}",
)
# Etape 2 : rechercher pour chaque sous-question
sous_resultats = []
for sous_q in decomposition.output_text.split("\n"):
if sous_q.strip():
r = client.responses.create(
model="gpt-5.3",
input=sous_q.strip(),
tools=[{"type": "file_search", "vector_store_ids": [vs_id]}]
)
sous_resultats.append(r.output_text)
# Etape 3 : synthetiser
synthese = client.responses.create(
model="gpt-5.3",
input=f"Question originale : {question_complexe}\n\n"
f"Resultats intermediaires :\n" + "\n---\n".join(sous_resultats) +
"\n\nSynthetise une reponse complete et coherente."
)
return synthese.output_text
Points cles a retenir
- Le chunking
staticavec taille et overlap personnalises ameliore la precision - Petits chunks pour les FAQ, grands chunks pour les documents juridiques
- Le
score_thresholdfiltre les resultats non pertinents - Nettoyez et structurez vos documents avant indexation
- Evaluez la qualite du retrieval avec un jeu de test