Prompt Chaining: Complexe Taken Opsplitsen in LLM Pipelines
Waarom één prompt zelden genoeg is voor complexe taken, en hoe prompt chaining en multi-step pipelines betrouwbare resultaten opleveren.
Jean-Pierre Broeders
Freelance DevOps Engineer
Prompt Chaining: Complexe Taken Opsplitsen in LLM Pipelines
Eén prompt die alles doet. Dat is wat de meeste teams proberen als ze voor het eerst met LLMs werken. En het werkt — tot het niet meer werkt. Zodra de complexiteit toeneemt, wordt die ene mega-prompt een onbeheersbaar monster dat soms briljante output geeft en soms complete onzin.
De oplossing? Prompt chaining. Dezelfde aanpak die al decennia in software engineering wordt toegepast: verdeel en heers.
Wat is prompt chaining precies?
Bij prompt chaining wordt een complexe taak opgebroken in meerdere stappen, waarbij de output van stap N de input wordt van stap N+1. Elke stap heeft een gerichte prompt die één ding goed doet.
Stel, er moet een technische RFC gegenereerd worden op basis van een Slack-thread. Eén prompt die dat in één keer doet? Dat gaat gegarandeerd mis. Maar splits het op:
- Extractie — Haal de kernpunten uit de Slack-berichten
- Structurering — Organiseer die punten in RFC-secties
- Generatie — Schrijf de daadwerkelijke RFC
- Review — Controleer op consistentie en ontbrekende informatie
Elke stap is klein, testbaar en debugbaar.
Praktijkvoorbeeld: Code Review Pipeline
Een pipeline die werkt in productie:
import openai
def analyze_diff(diff: str) -> dict:
"""Stap 1: Analyseer de code diff."""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": "Analyseer deze git diff. Geef terug: "
"changed_files, complexity_score (1-10), "
"risk_areas. Alleen JSON output."
}, {
"role": "user",
"content": diff
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
def generate_review(analysis: dict, diff: str) -> str:
"""Stap 2: Genereer review op basis van analyse."""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"Schrijf een code review. Focus op deze "
f"risk areas: {analysis['risk_areas']}. "
f"Complexity: {analysis['complexity_score']}/10. "
f"Wees specifiek, verwijs naar regelnummers."
}, {
"role": "user",
"content": diff
}]
)
return response.choices[0].message.content
def prioritize_findings(review: str) -> str:
"""Stap 3: Prioriteer en categoriseer bevindingen."""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": "Categoriseer deze review findings in: "
"MUST_FIX, SHOULD_FIX, NICE_TO_HAVE. "
"Voeg voor elke finding een korte rationale toe."
}, {
"role": "user",
"content": review
}]
)
return response.choices[0].message.content
Drie stappen, elk met een scherpe focus. De analyse-stap hoeft niet te schrijven, de schrijf-stap hoeft niet te analyseren. Resultaat: betere output op elke stap.
Wanneer chaining en wanneer niet
Niet elke taak heeft chaining nodig. Een simpele vertaling of samenvatting? Gewoon één prompt. Maar zodra er meerdere cognitieve stappen nodig zijn, loont het.
| Scenario | Aanpak | Waarom |
|---|---|---|
| E-mail samenvatten | Enkele prompt | Eenvoudige taak, weinig foutmarge |
| Bug report → fix suggestie | 2-stap chain | Eerst begrijpen, dan oplossen |
| Codebase documenteren | Multi-step pipeline | Analyse, structurering, schrijven, validatie |
| Data extractie + rapport | Multi-step pipeline | Extractie, transformatie, presentatie apart houden |
Gate checks tussen stappen
Het echte voordeel van chaining zit in de mogelijkheid om tussen stappen te valideren. Geen blind vertrouwen op de output — check het.
def run_pipeline(diff: str) -> str:
# Stap 1
analysis = analyze_diff(diff)
# Gate check: is de analyse bruikbaar?
if analysis.get("complexity_score", 0) < 1:
raise ValueError("Analyse leverde geen bruikbare score op")
if not analysis.get("risk_areas"):
# Geen risico's gevonden? Skip de dure review
return "Geen significante risico's gedetecteerd."
# Stap 2
review = generate_review(analysis, diff)
# Gate check: bevat de review concrete punten?
if len(review) < 100:
# Te kort, waarschijnlijk hallucinated "looks good"
review = generate_review(analysis, diff) # retry
# Stap 3
return prioritize_findings(review)
Die gate checks voorkomen dat een slechte output doorpropageert naar de volgende stap. In een enkele mega-prompt is dat onmogelijk.
Parallelle chains
Niet alles hoeft sequentieel. Soms kunnen stappen parallel draaien en worden de resultaten achteraf samengevoegd:
import asyncio
async def parallel_analysis(code: str):
security, performance, readability = await asyncio.gather(
analyze_security(code),
analyze_performance(code),
analyze_readability(code)
)
# Merge stap
return await merge_reviews(security, performance, readability)
Drie analyses tegelijk, dan één merge-stap die alles combineert. Sneller dan sequentieel, en elke analyse kan z'n eigen geoptimaliseerde prompt hebben.
Kosten en latency
Prompt chaining kost meer API calls. Dat is een feit. Maar de totale tokenkosten zijn vaak vergelijkbaar of zelfs lager dan één mega-prompt, omdat elke stap minder context nodig heeft.
Qua latency: sequentiële chains zijn trager. Parallelle chains compenseren dat deels. In de praktijk is het een afweging — voor realtime chat is een enkele prompt vaak beter, voor achtergrondprocessen maakt die extra seconde niet uit.
Foutafhandeling
Elke stap kan falen. Plan daar voor.
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
def robust_step(prompt: str, input_data: str) -> str:
"""Elke pipeline-stap met retry logic."""
try:
result = call_llm(prompt, input_data)
validate_output(result) # gooi exception als output niet klopt
return result
except ValidationError:
# Log voor debugging, retry vangt het op
logger.warning(f"Validatie gefaald, retry...")
raise
Retries per stap, niet voor de hele pipeline. Als stap 3 faalt, hoeft stap 1 en 2 niet opnieuw.
Logging en observability
In productie is het essentieel om elke stap te loggen. Niet alleen de final output, maar de tussenresultaten. Als iets misgaat — en dat gaat het — dan wil je kunnen zien waar het fout ging.
Bewaar de input en output van elke stap, inclusief het model, de temperature, en de tokens used. Dat klinkt als overhead, maar het bespaart uren debugging later.
Conclusie zonder conclusie
Prompt chaining is geen rocket science. Het is gewoon goede software architectuur toegepast op LLM-integraties. Kleine, gerichte stappen. Validatie tussenin. Retry bij fouten. Parallellisatie waar mogelijk.
De volgende keer dat een prompt te complex wordt en de resultaten inconsistent zijn: breek het op. Het kost iets meer setup, maar de betrouwbaarheid gaat er enorm op vooruit.
