Retrieval-augmented prompting
Retrieval-augmented prompting
Le Retrieval-Augmented Prompting (RAP) combine la recherche documentaire avec le prompting pour fournir au modèle un contexte pertinent et à jour. C’est l’évolution naturelle du grounding : au lieu d’injecter manuellement le contexte, vous automatisez la recherche des passages pertinents avant chaque appel au LLM.
Architecture d’un système RAP
Un pipeline RAP se décompose en trois étapes :
- Indexation : transformer vos documents en embeddings vectoriels
- Retrieval : pour chaque question, chercher les passages les plus pertinents
- Augmentation : injecter ces passages dans le prompt avant de générer la réponse
from openai import OpenAI
import numpy as np
client = OpenAI()
# Étape 1 : Générer les embeddings des documents
def embed_texts(texts: list[str]) -> list[list[float]]:
"""Génère les embeddings pour une liste de textes."""
response = client.embeddings.create(
model="text-embedding-3-large",
input=texts
)
return [item.embedding for item in response.data]
# Étape 2 : Recherche par similarité cosinus
def cosine_similarity(a: list[float], b: list[float]) -> float:
"""Calcule la similarité cosinus entre deux vecteurs."""
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def retrieve(query: str, documents: list[str],
doc_embeddings: list[list[float]],
top_k: int = 3) -> list[str]:
"""Retourne les top_k documents les plus pertinents."""
query_emb = embed_texts([query])[0]
scores = [
(i, cosine_similarity(query_emb, doc_emb))
for i, doc_emb in enumerate(doc_embeddings)
]
scores.sort(key=lambda x: x[1], reverse=True)
return [documents[i] for i, _ in scores[:top_k]]
Pipeline complet
class RAGPipeline:
"""Pipeline RAP minimaliste."""
def __init__(self, documents: list[str]):
self.documents = documents
self.embeddings = embed_texts(documents)
def query(self, question: str, top_k: int = 3) -> str:
"""Répond à une question en s'appuyant sur les documents."""
# Retrieval
relevant_docs = retrieve(
question, self.documents,
self.embeddings, top_k
)
# Augmentation du prompt
context = "\n\n---\n\n".join(
f"[Passage {i+1}]\n{doc}"
for i, doc in enumerate(relevant_docs)
)
system = f"""Tu es un assistant technique.
Réponds UNIQUEMENT à partir des passages fournis.
Si les passages ne contiennent pas la réponse, dis-le.
Cite le numéro du passage pour chaque affirmation.
PASSAGES PERTINENTS :
{context}"""
# Génération
response = client.responses.create(
model="gpt-5.3",
instructions=system,
input=question,
temperature=0.1
)
return response.output_text
Chunking : découper les documents
Le découpage des documents en chunks est crucial pour la qualité du retrieval. Trop grands, les chunks noient l’information pertinente. Trop petits, ils perdent le contexte.
def chunk_document(text: str, chunk_size: int = 500,
overlap: int = 50) -> list[str]:
"""Découpe un texte en chunks avec chevauchement."""
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = " ".join(words[i:i + chunk_size])
if chunk.strip():
chunks.append(chunk)
return chunks
def chunk_by_sections(text: str) -> list[str]:
"""Découpe par sections (titres Markdown)."""
sections = []
current = []
for line in text.split("\n"):
if line.startswith("## ") and current:
sections.append("\n".join(current))
current = [line]
else:
current.append(line)
if current:
sections.append("\n".join(current))
return sections
Stratégies de retrieval avancées
Reranking
Après le retrieval initial, utilisez le LLM pour réordonner les résultats :
def rerank(question: str, passages: list[str]) -> list[str]:
"""Réordonne les passages par pertinence avec le LLM."""
prompt = f"""Question : {question}
Voici des passages candidats. Classe-les du plus pertinent
au moins pertinent pour répondre à la question.
Retourne uniquement les numéros dans l'ordre (ex: 3, 1, 2).
{chr(10).join(f"[{i+1}] {p[:200]}" for i, p in enumerate(passages))}"""
response = client.responses.create(
model="gpt-5.3",
input=prompt,
temperature=0.0
)
# Parser l'ordre retourné et réorganiser
return response.output_text
Hypothetical Document Embedding (HyDE)
Générez un document hypothétique idéal, puis cherchez des documents similaires :
def hyde_retrieve(question: str, documents: list[str],
doc_embeddings: list[list[float]]) -> list[str]:
"""Retrieval via HyDE."""
# Générer un passage hypothétique
hypo_response = client.responses.create(
model="gpt-5.3",
input=f"Écris un court paragraphe technique qui répondrait "
f"parfaitement à cette question : {question}",
temperature=0.5
)
hypothetical_doc = hypo_response.output_text
# Chercher des documents similaires au passage hypothétique
return retrieve(hypothetical_doc, documents,
doc_embeddings, top_k=3)
Utiliser le File Search natif d’OpenAI
Pour un déploiement rapide, l’API propose un retrieval intégré :
# Upload du fichier
file = client.files.create(
file=open("documentation.pdf", "rb"),
purpose="assistants"
)
# Créer un vector store
vector_store = client.vector_stores.create(
name="Documentation technique"
)
client.vector_stores.files.create(
vector_store_id=vector_store.id,
file_id=file.id
)
# Query avec File Search
response = client.responses.create(
model="gpt-5.3",
instructions="Réponds à partir de la documentation fournie.",
input="Comment configurer l'authentification ?",
tools=[{
"type": "file_search",
"vector_store_ids": [vector_store.id]
}]
)
Mise en pratique
- Prenez 5 à 10 pages de documentation technique de votre choix
- Implémentez le pipeline RAP complet (chunking, embedding, retrieval, génération)
- Testez avec 10 questions dont la réponse se trouve dans les documents
- Comparez la qualité avec et sans reranking
- Mesurez le pourcentage de réponses correctement ancrées
Points clés à retenir
- Le RAP automatise l’injection de contexte pertinent dans le prompt
- Le chunking est critique : expérimentez avec différentes tailles
- Le reranking avec le LLM améliore significativement la pertinence
- HyDE est utile quand la question est très différente du vocabulaire des documents
- Le File Search d’OpenAI simplifie le déploiement pour les cas standards