Context Engineering â Section 5 : Compression & Distillation
Réduire le bruit, garder la preuve : extractive vs. abstractive, keyfacts + citations, passage ranking, budget tokens, cache & évaluation.
5. Compression & Distillation
- A. Panorama
- B. Pipeline
- C. Extractive
- D. Abstractive
- E. Keyfacts & Citations
- F. Passage Ranking
- G. Budgets & Cache
- H. Ăvaluation
- I. Pitfalls
- J. Snippets
- K. Schémas
- L. Playbook
Le ProblĂšme : Le Fardeau du Contexte Long
Injecter des documents bruts dans un LLM est simple mais inefficace. Cela génÚre des problÚmes critiques :
- Coût : Les API des LLMs facturent au token (entrée + sortie). Un contexte long est cher.
- Latence : Plus de tokens à traiter signifie un temps de réponse plus long.
- Bruit & "Lost in the Middle" : Les LLMs peuvent perdre de vue les informations cruciales noyées au milieu d'un long contexte.
- Limite de Contexte : Chaque modĂšle a une fenĂȘtre de contexte finie (ex: 8k, 32k, 128k tokens) qui peut ĂȘtre dĂ©passĂ©e.
Pipeline de Distillation Typique
Un pipeline robuste combine plusieurs étapes pour filtrer et raffiner le contexte.
[1. Retrieve] Query -> Top-k (e.g., 50) passages bruts (BM25/Embeddings)
â
[2. Re-rank] Cross-encoder re-rank -> Top-n (e.g., 20) passages pertinents
â
[3. Diversify] MMR (Maximal Marginal Relevance) -> Ăliminer les passages redondants
â
[4. Distill] â [4a. Extractive] Extraire les phrases clĂ©s (†70% du budget token)
â [4b. Abstractive] GĂ©nĂ©rer un rĂ©sumĂ© et des faits synthĂ©tiques (†30% du budget)
â
[5. Assemble] Compiler le "paquet distillé" (JSON) avec faits, citations, résumé, sources
â
[6. Prompt] Injecter le paquet dans un prompt structuré pour le LLM final
Approche Extractive (Lossless)
Consiste à sélectionner et concaténer des phrases ou extraits textuels directement depuis les documents sources. C'est la méthode la plus sûre pour éviter les hallucinations.
- **Traçabilité parfaite** : Chaque extrait pointe vers sa source exacte.
- **Manque de cohérence** : La simple concaténation de phrases peut nuire à la lisibilité.
# Utilisation de TextRank (variante de PageRank) pour extraire les phrases les plus importantes
from summa.summarizer import summarize
def extractive_summary(text, ratio=0.2):
# 'ratio' détermine le pourcentage de phrases à conserver
return summarize(text, ratio=ratio)
# Exemple sur un passage
passage = "Le systÚme de cache est activé par défaut. Le Time-To-Live (TTL) est de 24h. Pour le désactiver, il faut modifier la variable d'environnement `CACHE_ENABLED` à `false`."
print(extractive_summary(passage, ratio=0.7))
# Output probable : Le systÚme de cache est activé par défaut. Le Time-To-Live (TTL) est de 24h.
Approche Abstractive (Lossy)
Utilise un LLM pour réécrire, paraphraser et synthétiser l'information des sources. C'est plus concis, mais introduit un risque de "dérive sémantique" ou d'hallucination.
- **Cohérence** : Le texte généré est fluide et logique.
- **Perte de détails** : La synthÚse peut omettre des nuances importantes.
def abstractive_summarize_with_guardrails(passages, question):
context_str = "\n".join([f"Source {i+1}: {p}" for i, p in enumerate(passages)])
prompt = f"""
Context:
{context_str}
En te basant STRICTEMENT sur les sources fournies, réponds à la question suivante en 2-3 phrases: "{question}".
Ne mentionne AUCUNE information qui ne soit pas explicitement dans les sources.
Commence ta réponse par "Selon les documents,".
"""
# Utiliser une température basse pour limiter la créativité
return call_llm(prompt, temperature=0.0)
Extraction de Faits Clés (Keyfacts) & Citations
Le "Saint Graal" de la compression. Il s'agit d'extraire des unités d'information atomiques (les "faits") et de les lier à leurs sources. C'est le meilleur des deux mondes : la concision de l'abstractif et la fiabilité de l'extractif.
{
"summary": "Le TTL du cache est de 24h par dĂ©faut mais peut ĂȘtre dĂ©sactivĂ© via une variable d'environnement, conformĂ©ment Ă l'article 32 du RGPD sur les mesures techniques.",
"key_facts": [
{
"claim": "Le Time-To-Live (TTL) par défaut du cache est de 24 heures.",
"citations": [
{ "doc_id": "runbook-cache.md", "passage_hash": "a3b4c5", "text_snippet": "Le Time-To-Live (TTL) est de 24h." }
]
},
{
"claim": "La désactivation du cache se fait via la variable d'environnement `CACHE_ENABLED`.",
"citations": [
{ "doc_id": "runbook-cache.md", "passage_hash": "d6e7f8", "text_snippet": "...modifier la variable d'environnement `CACHE_ENABLED` Ă `false`." }
]
},
{
"claim": "La gestion du cache est une mesure technique relevant de l'Article 32 du RGPD.",
"citations": [
{ "doc_id": "compliance-rgpd.pdf", "page": 12, "text_snippet": "L'article 32 impose la mise en Ćuvre de mesures techniques appropriĂ©es..." }
]
}
],
"warnings": [
"Le document runbook-cache.md n'a pas été mis à jour depuis 2021."
]
}Passage Ranking : Trier Avant de Compresser
La qualité de la compression dépend entiÚrement de la qualité des passages en entrée. Un bon ranking est crucial.
- Retrieval Hybride : Combine la recherche par mot-clé (BM25, rapide et précis) et la recherche sémantique (embeddings, pour le sens). La fusion des scores (ex: RRF - Reciprocal Rank Fusion) donne d'excellents résultats.
- Re-ranking avec Cross-Encoder : Un cross-encoder est un modĂšle Transformer qui prend la query ET un document en entrĂ©e pour produire un score de pertinence. C'est lent, mais extrĂȘmement prĂ©cis. On l'applique uniquement sur le top-k du retrieval hybride.
- Boosts par Métadonnées : Augmenter le score des passages provenant de sources fiables, récentes, ou de sections importantes (ex: `Résumé`, `Conclusion`).
score_final = (w1 * score_rrf) + (w2 * score_cross_encoder) + (w3 * boost_recency)
Gestion des Budgets Tokens & du Cache
La compression doit ĂȘtre pilotĂ©e par des contraintes Ă©conomiques.
Ăvaluation de la QualitĂ© de Compression
Comment savoir si on compresse bien ? On mesure sur plusieurs axes.
| Métrique | Description | Méthode de Mesure |
|---|---|---|
| Faithfulness (Fidélité) | Le résumé ou les faits contredisent-ils les sources ? | LLM-as-a-Judge : un GPT-4 ou Claude 3 demande de vérifier chaque fait par rapport à sa source. |
| Answer Relevance (Pertinence) | Le contexte distillé aide-t-il à bien répondre à la question ? | LLM-as-a-Judge : un LLM note de 1 à 5 la qualité de la réponse finale basée sur le contexte. |
| Conciseness (Concision) | Le ratio de compression est-il bon ? | `1 - (tokens_distillés / tokens_bruts)`. Viser > 60-70%. |
| Information Recall | Avons-nous perdu des informations cruciales ? | Ăvaluation humaine sur un "golden set" de questions/rĂ©ponses de rĂ©fĂ©rence. |
PiĂšges Communs Ă Ăviter
- Compression prématurée : Compresser avant d'avoir un bon ranking est inutile, on compresse du bruit.
- Hallucination abstractive non contrÎlée : Utiliser un prompt de résumé trop vague ou une température > 0.2.
- Perte de la traçabilitĂ© : L'erreur la plus grave. Si on ne sait plus d'oĂč vient un fait, la confiance s'effondre.
- Ignorer la redondance : Ne pas utiliser de technique comme MMR peut remplir le contexte de passages quasi-identiques.
- Budget mal calibré : Allouer trop de tokens à l'abstractif augmente les risques ; pas assez, et le résumé est pauvre.
- Cache trop agressif : Une mauvaise stratégie d'invalidation peut servir des informations obsolÚtes aux utilisateurs.
Snippet : Pipeline de Distillation avec Pydantic
Ce code illustre un pipeline qui utilise Pydantic pour structurer la sortie et garantir la présence des citations.
from pydantic import BaseModel, Field
from typing import List
# 1. Définir des schémas de sortie robustes
class Citation(BaseModel):
doc_id: str = Field(description="Identifiant unique du document source.")
snippet: str = Field(description="Extrait exact prouvant le fait.")
class KeyFact(BaseModel):
claim: str = Field(description="Affirmation factuelle et atomique.")
citations: List[Citation] = Field(description="Liste des preuves issues des sources.")
class DistilledContext(BaseModel):
summary: str
key_facts: List[KeyFact]
warnings: List[str]
# 2. Utiliser un outil comme 'Instructor' pour forcer le LLM à répondre au format Pydantic
import instructor
from openai import OpenAI
# Permet de forcer la sortie JSON du LLM selon le schéma Pydantic
client = instructor.patch(OpenAI())
def distill_with_schema(passages: List[str], question: str) -> DistilledContext:
context_str = "\n".join([f"Source DOC{i+1}: {p}" for i, p in enumerate(passages)])
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
response_model=DistilledContext, # On spécifie le schéma de sortie
messages=[
{"role": "system", "content": "Tu es un expert en synthĂšse d'informations. Extrais les faits clĂ©s et gĂ©nĂšre un rĂ©sumĂ© basĂ© UNIQUEMENT sur les sources. Chaque fait doit ĂȘtre prouvĂ© par une citation."},
{"role": "user", "content": f"Contexte:\n{context_str}\n\nQuestion: {question}"}
]
)
return response
# RĂ©sultat : 'response' est un objet Pydantic DistilledContext, validĂ© et prĂȘt Ă l'emploi.
Schéma de l'Objet "Contexte Distillé"
La sortie du pipeline de compression ne doit pas ĂȘtre du texte brut, mais un objet JSON structurĂ©. Cela permet une exploitation fiable par le reste de l'application (construction du prompt final, affichage dans l'interface, mise en cache).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DistilledContext",
"type": "object",
"properties": {
"query_interpretation": {
"type": "string",
"description": "Reformulation de la question de l'utilisateur telle que comprise par le systĂšme."
},
"summary": {
"type": "string",
"description": "Résumé abstractif de 2-3 phrases des informations les plus pertinentes."
},
"key_facts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"claim": {"type": "string"},
"relevance_score": {"type": "number"},
"citations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"doc_id": {"type": "string"},
"doc_title": {"type": "string"},
"passage_hash": {"type": "string"},
"snippet": {"type": "string"}
},
"required": ["doc_id", "snippet"]
}
}
},
"required": ["claim", "citations"]
}
},
"warnings": {
"type": "array",
"items": {"type": "string"},
"description": "Avertissements sur la qualité des sources (ex: obsolescence, conflits)."
},
"metadata": {
"type": "object",
"properties": {
"latency_ms": {"type": "integer"},
"token_budget_used": {"type": "integer"},
"cache_hit": {"type": "boolean"}
}
}
},
"required": ["summary", "key_facts"]
}Playbook de Déploiement
- Baseline (Semaine 1) : Commencer simple. Implémenter un RAG basique avec retrieval hybride (BM25+Embeddings) et re-ranking (Cross-encoder). Concaténer le top 5 des passages de maniÚre extractive. Mesurer la performance (coût, latence, pertinence).
- ImplĂ©menter les Budgets et le Cache (Semaine 2) : Introduire des budgets de tokens stricts. Mettre en place un cache (Redis/Memcached) pour les rĂ©ponses basĂ©es sur les mĂȘmes sources. C'est le gain de performance le plus rapide.
- Introduire les Keyfacts Extractifs (Semaine 3) : Utiliser un LLM pour extraire des `KeyFacts` au format JSON (cf. onglet J) à partir des passages bruts. La consigne est "extrais" et non "résume".
- Ajouter le Résumé Abstractif (Semaine 4) : Une fois les keyfacts stables, ajouter un résumé abstractif généré à partir des keyfacts (et non des passages bruts). Cela limite les hallucinations.
- Mettre en Place l'Ăvaluation Continue (Semaine 5) : Automatiser les mĂ©triques de fidĂ©litĂ© et de pertinence (LLM-as-a-Judge) dans une CI/CD. CrĂ©er un dashboard pour suivre le coĂ»t par query, la latence et les scores de qualitĂ©.
- Itérer et Optimiser (Continu) : Analyser les "mauvaises réponses" pour identifier les points faibles : le retrieval ramÚne-t-il les bons documents ? Le prompt de distillation est-il assez précis ? Le budget est-il adapté ?
