Prompt chaining : découper une grosse tâche en sous-tâches
Un méga-prompt qui demande dix choses à la fois en rate toujours deux ou trois. Dix prompts qui se passent le relais, tu peux les vérifier un par un.
Temps de lecture : 13 min | Niveau : Intermédiaire
Ce que tu sauras faire après
- Reconnaître qu'un prompt est trop gros et qu'il faut le couper.
- Définir des points de passage propres entre deux étapes (handoff structuré).
- Construire une chaîne de 4 étapes pour migrer un lot de requêtes SQL.
- Ajouter une boucle d'auto-correction sans tomber dans la boucle infinie.
- Debugger une chaîne : savoir quelle étape a cassé.
1. Le problème du méga-prompt
Voici un prompt que tout le monde écrit au début :
Voici 40 requetes Oracle. Migre-les vers BigQuery, corrige les fonctions de date,
remplace NVL par IFNULL, enleve les hints, documente chaque requete, ecris les
tests de non-regression, et fais-moi un tableau recapitulatif des risques.Ce qui se passe en vrai :
- Les 5 premières requêtes sont bien traduites. Les 35 suivantes sont bâclés.
- Les tests de non-régression sont génériques et inutilisables.
- Le tableau récapitulatif est inventé à moitié.
- Tu n'as aucun moyen de savoir où ça a dérapé, parce que tout est sorti d'un seul bloc.
Le problème n'est pas l'intelligence du modèle. C'est que tu lui demandes de tenir sept objectifs en parallèle, et que tu n'as aucun point de contrôle.
2. Le principe du chaining
Prompt chaining = découper la tâche en sous-tâches, et passer la sortie de l'étape N en entrée de l'étape N+1.
Etape 1 -> sortie 1 -> Etape 2 -> sortie 2 -> Etape 3 -> livrable
| | |
controle controle controleChaque étape a un seul objectif. Chaque sortie est vérifiable.
Anthropic décrit le pattern dans Building effective agents comme le premier des workflows : "This workflow is ideal for situations where the task can be easily and cleanly decomposed into fixed subtasks."
Et la page Prompting best practices précise pourquoi on le garde en 2026 : "With adaptive thinking and subagent orchestration, Claude handles most multistep reasoning internally. Explicit prompt chaining (breaking a task into sequential API calls) is still useful when you need to inspect intermediate outputs or enforce a specific pipeline structure."
Retiens cette phrase. Le chaining ne sert plus à aider le modèle à réfléchir. Il sert à te donner des points de contrôle et une structure de pipeline. C'est exactement le réflexe d'un data engineer : tu ne fais pas un seul script de 2000 lignes, tu fais des tâches Airflow avec des sorties intermédiaires que tu peux inspecter.
3. Pourquoi c'est plus fiable qu'un méga-prompt
Cinq raisons concrètes.
| Raison | Ce que ça change pour toi |
|---|---|
| Un objectif par appel | Le modèle ne répartit pas son attention entre sept demandes |
| Sortie intermédiaire inspectable | Tu vois le résultat de l'étape 2 avant de lancer l'étape 3 |
| Reprise partielle | Si l'étape 3 rate sur 4 requêtes, tu rejoues l'étape 3 sur ces 4 là, pas tout |
| Modèle adapté par étape | Étape mécanique = modèle rapide et pas cher. Étape difficile = gros modèle |
| Logs et coûts par étape | Tu sais quelle étape coûte cher et laquelle rate |
Le quatrième point est souvent oublié et c'est souvent le plus rentable. Voir 08-cout-cache-et-performance.md.
4. Comment couper : les trois règles
Règle 1 : un verbe par étape
Si le nom de ton étape contient "et", coupe.
- Mauvais : "traduire et documenter et tester"
- Bon : "traduire" / "documenter" / "tester"
Règle 2 : le handoff doit être structuré
Le passage entre deux étapes ne doit jamais être du texte libre. Utilise du JSON ou des balises XML.
Mauvais handoff (texte libre) :
J'ai traduit la requete, il y avait quelques soucis avec les dates et un NVL.Impossible à parser, impossible à compter, impossible à rejouer.
Bon handoff (JSON) :
{
"requete_id": "rpt_ventes_mensuelles",
"sql_bigquery": "SELECT ...",
"transformations": [
{"type": "fonction_date", "avant": "TRUNC(d, 'MM')", "apres": "DATE_TRUNC(d, MONTH)"},
{"type": "null_handling", "avant": "NVL(x, 0)", "apres": "IFNULL(x, 0)"}
],
"points_a_verifier": ["fuseau horaire sur date_commande"],
"confiance": "moyenne"
}Ce JSON est directement consommable par l'étape suivante, et directement stockable dans une table de suivi.
Règle 3 : chaque étape doit pouvoir être rejouée seule
Si l'étape 3 a besoin d'un contexte qui n'existait qu'à l'étape 1, elle n'est pas autonome. Repasse le contexte nécessaire dans le handoff.
5. Fil rouge : migrer 40 requêtes Oracle vers BigQuery en 4 étapes
C'est le cas concret le plus fréquent en data engineering. On le déroule en entier.
Vue d'ensemble
40 requetes Oracle
|
[ETAPE 1] Inventaire et classement -> 40 fiches JSON
|
[ETAPE 2] Traduction SQL, 1 requete/appel -> 40 SQL BigQuery + journal
|
[ETAPE 3] Revue critique de chaque trad. -> verdict + corrections
|
[ETAPE 4] Tests de non-regression -> 40 requetes de comparaison
|
Rapport finalQuarante requêtes, ça fait environ 121 appels. C'est normal. Chaque appel est petit, rapide, et tu peux le rejouer.
Étape 1 - Inventaire et classement
Objectif unique : trier les 40 requêtes par difficulté, pour ne pas traiter une requête triviale et un PL/SQL de 400 lignes avec le même effort.
Tu es data engineer, specialiste de la migration Oracle vers BigQuery.
Voici une requete Oracle. Ne la traduis PAS. Classe-la seulement.
<requete id="[ID_REQUETE]">
[LE_SQL_ORACLE]
</requete>
Reponds uniquement avec ce JSON :
{
"requete_id": "[ID_REQUETE]",
"difficulte": "simple | moyenne | difficile",
"raison": "une phrase",
"specificites_oracle": ["liste des elements qui n'existent pas en BigQuery"],
"tables_sources": ["liste des tables lues"],
"effort_recommande": "low | medium | high"
}
Criteres de difficulte :
- simple : SELECT, WHERE, GROUP BY, fonctions standards uniquement
- moyenne : fonctions de date Oracle, NVL/DECODE, sous-requetes correlees
- difficile : CONNECT BY, MODEL, fonctions analytiques imbriquees, PL/SQL, MERGE complexeCe que tu gagnes : tu sais tout de suite que 6 requêtes sur 40 sont "difficiles". Tu peux les traiter à effort: high, et les 34 autres à medium. Tu peux aussi décider d'en réécrire 2 à la main plutôt que de les migrer.
Étape 2 - Traduction, une requête par appel
Objectif unique : produire le SQL BigQuery. Rien d'autre.
Tu es data engineer, specialiste BigQuery.
Traduis cette requete Oracle en SQL standard BigQuery (GoogleSQL).
<requete_oracle>
[LE_SQL_ORACLE]
</requete_oracle>
<analyse_etape_1>
[LE_JSON_DE_L_ETAPE_1]
</analyse_etape_1>
<regles_de_migration>
- Dialecte cible : GoogleSQL (BigQuery), pas Legacy SQL.
- NVL(a, b) -> IFNULL(a, b)
- DECODE(...) -> CASE WHEN ...
- SYSDATE -> CURRENT_TIMESTAMP()
- TRUNC(d, 'MM') -> DATE_TRUNC(d, MONTH)
- ROWNUM -> ROW_NUMBER() OVER (...) ou LIMIT selon l'intention
- Les hints Oracle (/*+ ... */) sont supprimes, jamais traduits.
- Si une construction n'a PAS d'equivalent direct, ne l'invente pas :
mets "TODO_MANUEL" dans transformations et explique.
</regles_de_migration>
Reponds uniquement avec ce JSON :
{
"requete_id": "[ID_REQUETE]",
"sql_bigquery": "...",
"transformations": [{"type": "...", "avant": "...", "apres": "..."}],
"points_a_verifier": ["..."],
"confiance": "haute | moyenne | basse"
}Le point clé est la ligne sur TODO_MANUEL. Tu donnes au modèle une porte de sortie honnête. Sans elle, il invente une traduction plausible pour CONNECT BY et tu ne t'en aperçois qu'en production.
Étape 3 - Revue critique
Objectif unique : relire la traduction avec un oeil neuf, sans avoir le SQL d'origine sous les yeux comme une justification.
C'est le pattern d'auto-correction : "generate a draft, have Claude review it against criteria, have Claude refine based on the review."
Tu es relecteur SQL. Tu n'as pas ecrit cette traduction. Sois critique.
<oracle_origine>
[LE_SQL_ORACLE]
</oracle_origine>
<traduction_proposee>
[LE_SQL_BIGQUERY_DE_L_ETAPE_2]
</traduction_proposee>
Verifie ces 6 points, un par un, dans <revue> :
1. Semantique des NULL : IFNULL/COALESCE places exactement comme NVL l'etait ?
2. Dates : meme fuseau, meme troncature, meme bornes (inclusives/exclusives) ?
3. Types : division entiere Oracle vs BigQuery, CAST implicites ?
4. Cardinalite : le grain de sortie est-il identique ? (jointures, DISTINCT)
5. Tri et pagination : ROWNUM traduit selon l'intention reelle ?
6. Restes : hint oublie, syntaxe Oracle residuelle ?
Puis, dans <verdict>, reponds avec ce JSON :
{
"requete_id": "[ID_REQUETE]",
"verdict": "OK | CORRECTIONS | REJET",
"problemes": [{"point": 1, "gravite": "bloquant|mineur", "description": "..."}],
"sql_corrige": "le SQL corrige, ou null si verdict = OK"
}Pourquoi c'est efficace : l'étape 2 avait pour objectif de produire. L'étape 3 a pour objectif de critiquer. Ce sont deux postures opposées, difficiles à tenir dans un seul appel.
Garde-fou anti-boucle infinie : limite à deux tours de correction maximum. Si après deux tours le verdict n'est pas OK, la requête part dans une file "traitement manuel". Écris cette règle dans ton orchestrateur, pas dans le prompt.
Étape 4 - Tests de non-régression
Objectif unique : produire la requête qui prouve que l'ancienne et la nouvelle donnent le même résultat.
Tu es QA data. Ecris le test de non-regression pour cette migration.
<oracle>
[SQL_ORACLE]
</oracle>
<bigquery>
[SQL_BIGQUERY_VALIDE]
</bigquery>
<cle_metier>
[LES_COLONNES_QUI_IDENTIFIENT_UNE_LIGNE, ex: client_id, mois]
</cle_metier>
Produis ce JSON :
{
"requete_id": "[ID_REQUETE]",
"controle_volumetrie": "requete BigQuery qui compte les lignes des deux cotes",
"controle_agregats": "requete qui compare SUM et COUNT sur les colonnes numeriques",
"controle_ligne_a_ligne": "requete FULL OUTER JOIN sur la cle metier qui liste les ecarts",
"tolerance": "ecart accepte sur les montants, ex: 0.01 pour les arrondis",
"limites_du_test": ["ce que ce test ne couvre PAS"]
}La dernière clé, limites_du_test, est celle qui te sauvera. Elle force le modèle à dire ce qu'il ne teste pas.
L'orchestrateur en Python
La chaîne tourne dans une boucle simple. Aucun framework nécessaire.
import json
resultats = {}
for requete in les_40_requetes:
rid = requete["id"]
# Etape 1 : classement (modele rapide, effort bas)
fiche = appel_llm(PROMPT_ETAPE_1, requete, modele="petit", effort="low")
# Etape 2 : traduction (effort selon la difficulte detectee)
effort = {"simple": "low", "moyenne": "medium", "difficile": "high"}[fiche["difficulte"]]
trad = appel_llm(PROMPT_ETAPE_2, requete, fiche, effort=effort)
# Etape 3 : revue, 2 tours maximum
for tour in range(2):
revue = appel_llm(PROMPT_ETAPE_3, requete, trad, effort="high")
if revue["verdict"] == "OK":
break
trad["sql_bigquery"] = revue["sql_corrige"]
else:
# deux tours et toujours pas OK -> file manuelle
resultats[rid] = {"statut": "MANUEL", "revue": revue}
continue
# Etape 4 : tests
tests = appel_llm(PROMPT_ETAPE_4, requete, trad, effort="medium")
resultats[rid] = {"statut": "OK", "sql": trad, "tests": tests}
# la sortie de chaque etape est sauvegardee : tu peux rejouer une etape seule
with open("migration_resultats.json", "w", encoding="utf-8") as f:
json.dump(resultats, f, ensure_ascii=False, indent=2)Tu reconnais la structure : c'est un DAG. Si tu mets ça dans Airflow, chaque étape devient une tâche, et tu obtiens gratuitement les retries, les logs et la reprise sur échec.
6. Debugger une chaîne
Quand le résultat final est mauvais, la question n'est pas "le modèle est nul" mais "quelle étape a cassé".
Méthode en trois temps :
- Ouvre les sorties intermédiaires. Si tu ne les as pas sauvegardées, c'est ton premier bug. Sauvegarde toujours.
- Remonte jusqu'à la première sortie fausse. L'erreur se propage : une étape 2 fausse donne une étape 3 fausse même si l'étape 3 est parfaite.
- Isole cette étape. Rejoue-la seule avec le même input. Si elle rate encore, le problème est dans son prompt. Si elle passe, le problème est dans le handoff (format, champ manquant, troncature).
Trois causes de panne, par fréquence décroissante :
- Handoff incomplet : l'étape N+1 a besoin d'un champ que l'étape N ne produit pas.
- Étape qui fait deux choses : tu as triché sur la règle 1.
- Prompt trop vague sur la sortie : tu n'as pas imposé le JSON exact.
7. Ton template prêt à copier
Template A - Une étape de chaîne
Tu es [ROLE_PRECIS].
Objectif UNIQUE de cette etape : [UN_SEUL_VERBE + COMPLEMENT].
Tu ne fais rien d'autre. Tu ne [CE_QU_IL_NE_DOIT_PAS_FAIRE, ex: ne documentes pas,
ne testes pas].
<entree>
[LA_SORTIE_DE_L_ETAPE_PRECEDENTE]
</entree>
<regles>
- [REGLE_1]
- [REGLE_2]
- Si tu ne peux pas traiter un cas, ecris "TODO_MANUEL" et explique pourquoi.
N'invente jamais.
</regles>
Reponds uniquement avec ce JSON, sans texte avant ni apres :
{
"[CHAMP_IDENTIFIANT]": "...",
"[CHAMP_RESULTAT]": "...",
"[CHAMP_JOURNAL]": ["..."],
"confiance": "haute | moyenne | basse"
}Template B - Étape de revue critique
Tu es relecteur. Tu n'as PAS produit ce qui suit. Sois critique et precis.
<source_originale>
[L_ENTREE_D_ORIGINE]
</source_originale>
<production_a_relire>
[CE_QUE_L_ETAPE_PRECEDENTE_A_PRODUIT]
</production_a_relire>
Verifie ces points un par un dans <revue> :
1. [CRITERE_1]
2. [CRITERE_2]
3. [CRITERE_3]
Dans <verdict>, reponds :
{
"verdict": "OK | CORRECTIONS | REJET",
"problemes": [{"critere": 1, "gravite": "bloquant|mineur", "description": "..."}],
"version_corrigee": "... ou null si OK"
}Template C - Plan de découpage
À utiliser quand tu ne sais pas comment couper ta tâche.
Je dois : [DECRIS_TA_GROSSE_TACHE].
Volume : [COMBIEN_D_ELEMENTS].
Livrable final attendu : [LE_LIVRABLE].
Propose un decoupage en 3 a 5 etapes chainees. Pour chaque etape, donne :
- son objectif en un seul verbe
- son entree exacte
- le schema JSON exact de sa sortie
- le critere qui permet de dire que l'etape a reussi
- le niveau d'effort recommande (low / medium / high)
Ne propose pas de decoupage ou une etape fait deux choses.À retenir
- Le chaining sert surtout à te donner des points de contrôle et une structure de pipeline, pas à aider le modèle à réfléchir.
- Une étape = un seul verbe. Si le nom contient "et", coupe.
- Le handoff entre deux étapes doit être structuré (JSON ou XML), jamais du texte libre.
- Sauvegarde toujours les sorties intermédiaires : sans elles, tu ne peux pas debugger ni rejouer.
- Le pattern le plus rentable est produire puis critiquer en deux appels séparés, avec une limite de tours.
- Donne toujours une porte de sortie honnête (
TODO_MANUEL) plutôt que de laisser le modèle inventer. - Une chaîne, c'est un DAG. Si elle devient sérieuse, mets-la dans Airflow.
Sources
- Building effective agents - Anthropic Engineering
- Prompting best practices - Anthropic
- Effort - Anthropic
- Overview of prompting strategies - Google Cloud / Vertex AI : voir la stratégie "Break down complex tasks", qui est le même principe que le chaining.
- Prompt engineering techniques - Microsoft Foundry : voir la section "Break the task down", avec un exemple en deux étapes (extraire les faits, puis générer les requêtes de vérification).
- Prompting - OpenAI API : cette page ne traite pas le chaining. Elle est citée pour un autre conseil, très utile ici : garder chaque prompt de production dans un fichier versionné avec le code, et non dans un objet "prompt réutilisable" côté fournisseur.