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

9 maart 20268 min. leestijd

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:

  1. Extractie — Haal de kernpunten uit de Slack-berichten
  2. Structurering — Organiseer die punten in RFC-secties
  3. Generatie — Schrijf de daadwerkelijke RFC
  4. 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.

ScenarioAanpakWaarom
E-mail samenvattenEnkele promptEenvoudige taak, weinig foutmarge
Bug report → fix suggestie2-stap chainEerst begrijpen, dan oplossen
Codebase documenterenMulti-step pipelineAnalyse, structurering, schrijven, validatie
Data extractie + rapportMulti-step pipelineExtractie, 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.

Wil je op de hoogte blijven?

Schrijf je in voor mijn nieuwsbrief of neem contact op voor freelance projecten.

Neem Contact Op