Long contexte : travailler avec de gros documents
Une fenêtre de contexte d'un million de tokens ne veut pas dire que tu peux tout balancer dedans. La place des documents dans le prompt change le résultat.
Temps de lecture : 12 min | Niveau : Intermédiaire
Ce que tu sauras faire après
- Placer les documents au bon endroit dans ton prompt, et savoir pourquoi.
- Structurer plusieurs documents avec des balises
<document>propres. - Forcer le modèle à citer avant de répondre, pour pouvoir vérifier.
- Découper un gros document quand le tout-en-un ne marche pas.
- Traiter trois cas réels : 300 pages de specs, un dump de logs, un gros notebook.
1. Grand contexte ne veut pas dire contexte gratuit
Les modèles Claude actuels (Fable 5.1, Opus 5.5, Sonnet 5) annoncent une fenêtre de 1 million de tokens. Haiku 4.5 est à 200 000.
Pour se repérer : d'après la doc, 1 M de tokens correspond à environ 555 000 mots sur le tokenizer actuel, et 200 000 tokens à environ 150 000 mots.
Mais la taille disponible n'est pas la qualité. L'article d'ingénierie d'Anthropic sur le context engineering explique le phénomène de context rot : quand le volume de tokens augmente, la performance du modèle se dégrade. Le modèle a un "budget d'attention" qui s'épuise à chaque token ajouté.
Conséquence pratique : ton objectif n'est pas de remplir la fenêtre. C'est d'y mettre le minimum de tokens qui suffit. Un prompt de 30 000 tokens bien choisis bat un prompt de 400 000 tokens qui contient tout "au cas où".
2. Règle numéro un : les documents en haut
C'est la règle la plus rentable de ce fichier, et elle coûte zéro effort.
La doc le dit sans détour : "Put longform data at the top: Place your long documents and inputs near the top of your prompt, above your query, instructions, and examples. This improves performance across all models."
Et elle chiffre le gain : "Queries at the end can improve response quality by up to 30 percent in tests, especially with complex, multidocument inputs."
Avant (mauvais ordre)
Trouve les incoherences entre la spec fonctionnelle et le modele de donnees.
Reponds en tableau markdown.
<spec>
... 300 pages ...
</spec>
<modele_donnees>
... 80 tables ...
</modele_donnees>Après (bon ordre)
<documents>
<document index="1">
<source>spec_fonctionnelle_v4.pdf</source>
<document_content>... 300 pages ...</document_content>
</document>
<document index="2">
<source>modele_donnees_dwh.sql</source>
<document_content>... 80 tables ...</document_content>
</document>
</documents>
Trouve les incoherences entre la spec fonctionnelle et le modele de donnees.
Reponds en tableau markdown avec les colonnes : entite, incoherence, source (numero
de document), gravite.Pourquoi c'est mieux
L'instruction est la dernière chose lue. Elle reste "fraîche" au moment où le modèle commence à produire. Avec le premier ordre, l'instruction est noyée 300 pages plus haut.
Bonus non négligeable : cet ordre est aussi celui qui permet le cache de prompt. Les documents, qui ne changent pas, sont en haut ; la question, qui change à chaque fois, est en bas. Voir 08-cout-cache-et-performance.md.
3. Règle numéro deux : structurer avec des balises
Quand tu passes plusieurs documents, ne les colle pas les uns derrière les autres. Le modèle ne saura plus où l'un finit et où l'autre commence.
La structure recommandée par la doc :
<documents>
<document index="1">
<source>annual_report_2023.pdf</source>
<document_content>
{{ANNUAL_REPORT}}
</document_content>
</document>
<document index="2">
<source>competitor_analysis_q2.xlsx</source>
<document_content>
{{COMPETITOR_ANALYSIS}}
</document_content>
</document>
</documents>
Analyze the annual report and competitor analysis. Identify strategic advantages and
recommend Q3 focus areas.Tu peux ajouter tes propres sous-balises de métadonnées. En data, celles qui servent vraiment :
<document index="3">
<source>marts/fct_commandes.sql</source>
<type>modele_dbt</type>
<derniere_modif>2026-08-14</derniere_modif>
<proprietaire>equipe-finance</proprietaire>
<document_content>...</document_content>
</document>L'attribut index sert à une chose précise : tu peux exiger que le modèle cite le numéro du document. Ça rend sa réponse vérifiable.
4. Règle numéro trois : citer avant de répondre
Technique la plus efficace contre les réponses inventées sur un gros document.
La doc : "Ground responses in quotes: For long document tasks, ask Claude to quote relevant parts of the documents first before carrying out its task. This helps Claude focus on the relevant content and ignore the rest of the document."
Le principe : tu découpes la réponse en deux temps.
- Extraction : le modèle va chercher les passages pertinents et les recopie dans
<citations>. - Synthèse : il répond en s'appuyant uniquement sur ces citations.
Exemple appliqué aux specs
<documents>
<document index="1">
<source>spec_facturation_v4.md</source>
<document_content>[300 PAGES]</document_content>
</document>
</documents>
Question : quelles sont toutes les regles d'arrondi appliquees aux montants ?
Etape 1. Dans <citations>, recopie chaque passage qui parle d'arrondi, de precision
decimale, de TVA ou de devise. Pour chaque citation, donne le numero de document et le
titre de la section. Ne resume pas : recopie le texte exact.
Etape 2. Dans <reponse>, liste les regles d'arrondi sous forme de tableau
(regle | ou elle s'applique | citation numero). N'ajoute AUCUNE regle qui ne soit pas
adossee a une citation de l'etape 1. Si une zone est ambigue, ecris-le dans une ligne
"ZONE FLOUE".Deux bénéfices immédiats :
- Tu peux vérifier chaque ligne du tableau en 10 secondes, en cherchant la citation dans le document.
- La consigne "aucune règle sans citation" réduit fortement l'invention.
5. Quand découper, et comment
Le tout-en-un ne marche plus dans trois cas : le document dépasse la fenêtre, la qualité se dégrade visiblement, ou le coût devient absurde.
Stratégie A - Découpage par section métier
La meilleure quand le document a une structure. Tu ne coupes pas tous les 10 000 tokens : tu coupes aux frontières logiques (chapitre, module, schéma).
Passe 1 : pour chaque section, un appel -> une fiche JSON structuree
Passe 2 : un appel qui prend les N fiches -> la syntheseC'est du prompt chaining (fichier 03) appliqué à un document.
Stratégie B - Indexation puis récupération à la demande
Tu ne mets pas le document dans le prompt. Tu mets un index, et le modèle demande les morceaux dont il a besoin via un outil.
L'article de context engineering appelle ça la récupération just-in-time : plutôt que tout précharger, les agents utilisent "lightweight identifiers (file paths, stored queries, web links, etc.)" pour récupérer l'information au moment où ils en ont besoin.
Pour toi, ça veut dire : au lieu de coller 80 fichiers dbt, tu donnes la liste des fichiers plus un outil read_file. Le modèle lit les 4 fichiers qui l'intéressent. Tu passes de 400 000 tokens à 15 000.
Stratégie C - Prise de notes structurée
Pour les traitements longs : le modèle écrit ses conclusions au fur et à mesure dans un fichier externe (NOTES.md), et ce fichier devient sa mémoire. L'article décrit ce pattern comme ce qui permet des tâches de plusieurs heures sans saturer la fenêtre.
6. Trois cas concrets
Cas 1 : 300 pages de specs fonctionnelles
Le piège : tu colles tout et tu demandes "résume-moi ça". Tu obtiens un résumé générique, lisse, inutile.
L'approche qui marche :
- Passe d'indexation. Un appel par chapitre, sortie JSON :
{chapitre, objet, entites_citees, regles_metier, dependances, zones_floues}. - Tu obtiens 30 fiches. Total : peut-être 12 000 tokens au lieu de 400 000.
- Toutes tes questions suivantes tapent sur les 30 fiches, pas sur les 300 pages.
- Quand une fiche signale une zone floue, tu rouvres ce chapitre-là en entier.
Le prompt d'indexation :
<document>
<source>[NOM_DU_CHAPITRE]</source>
<document_content>[LE_CHAPITRE]</document_content>
</document>
Produis uniquement ce JSON, sans texte autour :
{
"chapitre": "...",
"objet": "une phrase",
"entites_citees": ["noms d'objets metier ou de tables"],
"regles_metier": [{"regle": "...", "citation": "texte exact du document"}],
"dependances": ["autres chapitres ou systemes cites"],
"zones_floues": ["points ou la spec est ambigue ou contradictoire"],
"impact_data": "quelles tables ou pipelines sont concernes"
}
Chaque regle metier DOIT avoir sa citation exacte. Pas de citation = pas de regle.Cas 2 : un dump de logs
Le piège : 500 Mo de logs, dont 99 % de lignes identiques. Les coller, c'est payer pour du bruit.
L'approche qui marche : tu pré-traites en local avant d'envoyer quoi que ce soit. Le modèle n'est pas un grep.
# 1. garder uniquement les erreurs et leur contexte
grep -n -B 3 -A 10 -E "ERROR|CRITICAL|Traceback" app.log > erreurs.txt
# 2. compter les signatures d'erreur pour trouver les plus frequentes
grep -oE "ERROR [A-Za-z_.]+" app.log | sort | uniq -c | sort -rn | head -20 > top_erreurs.txtPuis tu envoies top_erreurs.txt (20 lignes) plus un échantillon de erreurs.txt, pas tout.
<documents>
<document index="1">
<source>top_erreurs.txt</source>
<document_content>[LES_20_SIGNATURES_AVEC_LEUR_COMPTE]</document_content>
</document>
<document index="2">
<source>echantillon_traces.txt</source>
<document_content>[3_TRACES_COMPLETES_PAR_SIGNATURE]</document_content>
</document>
</documents>
Contexte : pipeline Airflow, DAG [NOM_DU_DAG], echec depuis [DATE].
Dans <citations>, recopie pour chaque signature frequente la ligne de trace la plus
informative.
Dans <analyse>, donne un tableau : signature | cause probable | preuve (citation) |
est-ce une cause ou une consequence ?
Termine par la signature a traiter en premier et pourquoi.La colonne "cause ou conséquence" est celle qui fait gagner du temps : dans un pipeline, 80 % des erreurs sont des conséquences de la première.
Cas 3 : un gros notebook Jupyter
Le piège : un .ipynb contient les sorties (tableaux, images encodées en base64, warnings). Un notebook de 40 cellules peut faire 300 000 tokens dont 95 % de base64 inutile.
L'approche qui marche : tu extrais le code et tu jettes les sorties lourdes.
import json
with open("analyse.ipynb", encoding="utf-8") as f:
nb = json.load(f)
morceaux = []
for i, cell in enumerate(nb["cells"]):
source = "".join(cell["source"]).strip()
if not source:
continue
if cell["cell_type"] == "markdown":
morceaux.append(f"### Cellule {i} (markdown)\n{source}")
else:
# on garde le code, et seulement les sorties texte courtes
sorties = []
for out in cell.get("outputs", []):
txt = "".join(out.get("text", []))
if txt and len(txt) < 500:
sorties.append(txt)
if out.get("output_type") == "error":
sorties.append("ERREUR: " + " ".join(out.get("traceback", []))[:500])
bloc = f"### Cellule {i} (code)\n```python\n{source}\n```"
if sorties:
bloc += "\nSortie:\n" + "\n".join(sorties)
morceaux.append(bloc)
contexte = "\n\n".join(morceaux)
print(f"{len(contexte)} caracteres, soit environ {len(contexte)//4} tokens")Tu passes couramment de 300 000 à 12 000 tokens. Ensuite seulement, tu poses ta question : refactoring en script, extraction en modèle dbt, détection de fuite de données entre train et test.
7. Ton template prêt à copier
Template A - Question sur plusieurs documents
<documents>
<document index="1">
<source>[NOM_FICHIER_1]</source>
<type>[spec | code | log | donnees]</type>
<document_content>
[CONTENU_1]
</document_content>
</document>
<document index="2">
<source>[NOM_FICHIER_2]</source>
<type>[TYPE_2]</type>
<document_content>
[CONTENU_2]
</document_content>
</document>
</documents>
Tu es [TON_ROLE].
Question : [TA_QUESTION_PRECISE].
Etape 1. Dans <citations>, recopie chaque passage pertinent. Pour chaque citation,
indique le numero du document. Recopie le texte exact, ne resume pas.
Etape 2. Dans <reponse>, reponds sous forme de [FORMAT, ex: tableau markdown avec
les colonnes X, Y, Z]. Chaque affirmation doit renvoyer a une citation de l'etape 1.
Si l'information n'est pas dans les documents, ecris "NON TROUVE" au lieu de deduire.Template B - Indexation d'un gros document
<document>
<source>[NOM_DE_LA_SECTION]</source>
<document_content>
[LA_SECTION]
</document_content>
</document>
Produis uniquement ce JSON, sans aucun texte autour :
{
"section": "[NOM]",
"objet": "une phrase",
"[CHAMP_METIER_1, ex: entites_citees]": ["..."],
"[CHAMP_METIER_2, ex: regles_metier]": [{"regle": "...", "citation": "texte exact"}],
"zones_floues": ["..."],
"[CHAMP_IMPACT, ex: tables_concernees]": ["..."]
}
Regle stricte : chaque element de [CHAMP_METIER_2] doit avoir sa citation exacte.
Pas de citation = tu ne l'inclus pas.Template C - Vérifier que le contexte suffit
À lancer avant une analyse coûteuse.
<documents>
[TES_DOCUMENTS]
</documents>
Je veux ensuite te demander : [TA_QUESTION_FINALE].
Avant de repondre, dis-moi uniquement :
1. Les documents fournis suffisent-ils pour repondre avec certitude ? oui / non / partiellement
2. Quelles informations manquent precisement ?
3. Quel document ou quelle table faudrait-il ajouter ?
4. Quelles parties des documents fournis sont inutiles pour cette question ?
Ne reponds PAS encore a la question finale.Le point 4 est celui qu'on oublie. Il te dit quoi retirer au prochain tour, donc ce que tu vas arrêter de payer.
À retenir
- Les documents en haut, l'instruction en bas. Gain annoncé jusqu'à 30 % sur les entrées multi-documents.
- Structure chaque document avec
<document index="n">,<source>et<document_content>. - Fais citer avant de répondre, et interdis toute affirmation sans citation.
- Une grande fenêtre ne protège pas du context rot : vise le minimum de tokens utiles, pas le maximum possible.
- Pré-traite en local (grep, extraction de notebook) avant d'envoyer. Le modèle n'est pas un outil de filtrage.
- Quand le document est trop gros : indexe-le une fois, puis travaille sur l'index.
Sources
- Prompting best practices - Anthropic
- Effective context engineering for AI agents - Anthropic Engineering
- Models overview - Anthropic
- Prompt caching - Anthropic
- Overview of prompting strategies - Google Cloud / Vertex AI : voir les stratégies "Add contextual information" et "Structure prompts". Cette page ne traite pas le long contexte en tant que tel.
- Prompt engineering techniques - Microsoft Foundry : voir "Add clear syntax", "Provide grounding context" et "Space efficiency". Microsoft recommande aussi le Markdown ou le XML pour séparer les sections d'un prompt, exactement comme Anthropic.