Module 6: Prompt Composition and Chaining

1. Introduction: One Prompt Is Not Enough

Overview

In previous modules you learned advanced techniques to make a single prompt more powerful: Chain-of-Thought, Self-Consistency, ReAct. But there's a fundamental reality none of those techniques can solve on its own: complex real-world tasks require multiple steps, multiple perspectives, and multiple validations.

This module introduces the concept of Prompt Composition: the art of designing systems where multiple prompts work together, each specialized in one part of the problem, connected in pipelines that handle the flow of information robustly.


Why One Prompt Isn't Enough

Think about how you solve a complex problem as a human. You don't do it in a single step. You think, you look for information, you synthesize, you write, you review, you reformat. Each stage has its own specialization.

The same applies to LLMs:

Reason 1: Context Window Limits

Legal document: 80,000 words (~120K tokens)
gpt-4o-mini context window: 128K tokens
gpt-4o context window: 128K tokens

PROBLEM: 
- You can't fit the full document + instructions + expected answer in a single prompt
- Even if it fits, the model loses attention on distant parts of the context (lost in the middle)
- For documents > 200K tokens: impossible without staged processing

SOLUTION: Split into chunks, process each one, synthesize the results

Reason 2: Specialization per Stage

Task: "Analyze this financial report and generate investment recommendations"

A single prompt tries to do EVERYTHING:
- Extract key metrics
- Understand the sector context
- Compute ratios
- Compare against benchmarks
- Generate recommendations
→ Result: shallow analysis, not deep on any single aspect

Specialized pipeline:
- Prompt 1: Extract numeric data (specialized in extraction)
- Prompt 2: Compute ratios (specialized in financial calculation)
- Prompt 3: Interpret trends (specialized in analysis)
- Prompt 4: Generate recommendations (specialized in synthesis)
→ Result: deeper analysis, each stage optimized

Reason 3: Intermediate Validation

# Without intermediate validation:
result = one_huge_prompt(document)  # If it fails, you don't know where

# With a pipeline and validation:
extraction = extract(document)
assert extraction has_correct_format()

analysis = analyze(extraction)
assert analysis.confidence_score > 0.7

report = generate_report(analysis)
# If analysis fails, the error is clear and you can retry only that stage

Reason 4: Error Handling per Stage

Without a pipeline: If there's an error, the whole process fails and you lose all the work.

With a pipeline: If stage 3 of 5 fails, you retry only that stage,
                 stages 1 and 2 are already completed and cached.
              
This is critical for long documents where each stage can cost
$0.10 in API calls. You don't want to lose the previous work.

Decomposition: The Core Principle

Decomposition is the process of splitting a complex task into manageable sub-tasks, each with:

  • A clear, well-defined input
  • A specialized prompt
  • An output with a validatable schema
  • Explicit dependencies on other sub-tasks
Example: News article analysis system

Complex task:
"Analyze this article and generate: an executive summary, a bias analysis,
 extraction of verifiable facts, and potential impact"

Decomposition into sub-tasks:

Sub-task 1: Entity extraction
  Input: Full article
  Output: {persons, organizations, dates, locations, figures}
  Prompt: Specialized in NER (Named Entity Recognition)
  Independent of: Nothing
  
Sub-task 2: Executive summary (can run in parallel with 3 and 4)
  Input: Full article
  Output: {title, key_points: [str], length: "3 sentences"}
  Prompt: Specialized in summarization
  Independent of: Sub-tasks 3, 4
  
Sub-task 3: Bias analysis
  Input: Article + extracted entities (from Sub-task 1)
  Output: {bias_detected: bool, bias_type: str, evidence: [str]}
  Prompt: Specialized in critical media analysis
  Depends on: Sub-task 1

Sub-task 4: Fact verification
  Input: Article + entities + summary
  Output: {verifiable_facts: [{fact: str, verifiable: bool}]}
  Depends on: Sub-tasks 1, 2
  
Sub-task 5: Final synthesis
  Input: Outputs from all previous sub-tasks
  Output: Structured report
  Depends on: All of them

The Composition Patterns

In this module you'll learn four fundamental patterns:

Pattern 1: Sequential Chaining (A → B → C)

The output of each stage is the input of the next one. It's the simplest and most common pattern.

Input → [Extract] → data → [Analyze] → analysis → [Report] → Output

When to use it: When each stage depends on the output of the previous one and there's no possible parallelization.

Pattern 2: Parallel Processing (A, B, C → synthesis)

Multiple prompts run simultaneously on the same input, then get combined.

         ┌→ [Summary] ──────────┐
Input ───┤→ [Bias analysis]     ├→ [Synthesis] → Output
         └→ [NER extraction] ───┘

When to use it: When multiple analyses are independent of each other. Reduces latency significantly.

Pattern 3: Conditional Branching (if/else)

The pipeline's path forks depending on the content of the input.

Input → [Classify] → LONG?  → [Summarize] → [Analyze]
                   → SHORT? → [Analyze directly]

When to use it: When different types of input require different processing.

Pattern 4: Iterative Refinement (loop)

A prompt is applied repeatedly until it reaches a quality criterion.

Input → [Generate] → [Evaluate] → score < 0.8? → [Refine] → [Evaluate] → ...
                                → score ≥ 0.8? → Output

When to use it: When the initial quality is insufficient and can be improved iteratively.


Module 6 Roadmap

#CapsuleTopicWhat you'll learn
01IntroductionOne prompt isn't enoughComposition principles
02Prompt chainingOutput → InputSequential, conditional, parallel
03DecompositionFramework for decomposingIdentify sub-tasks, interfaces
04Multi-stage pipelinesOrchestration, state, retryPipelineState, error handling
05Context window managementSummarization, sliding windowProcess long documents
06Multi-turn strategiesMemory, pruningLong conversations
07Routing by complexityCheap vs. powerful modelCost optimization
08ProjectMulti-Stage Analysis PipelineA complete analysis system

Module Prerequisites

Before continuing, make sure you're comfortable with:

  • Python async/await: For parallel chaining with asyncio
  • Pydantic BaseModel: To validate outputs between stages
  • try/except in Python: For robust error handling
  • OpenAI SDK: from openai import OpenAI, AsyncOpenAI

First Experiment: One Prompt vs. Pipeline

So you can see the difference right away, compare these two approaches to analyzing a text:

from openai import OpenAI

client = OpenAI()

text = """Company XYZ reported revenue of $5.2M in Q3, a 34% increase 
over the previous year. However, operating expenses grew 45%, 
reducing the profit margin to 12%. The CEO mentioned plans to expand 
into LATAM markets in Q1 of next year."""

# Approach 1: A single prompt (everything at once)
single_response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": f"""Analyze this text and generate:
1. Key extracted data
2. Trend analysis
3. Actionable recommendations

Text: {text}"""}],
    temperature=0
)
print("=== SINGLE PROMPT ===")
print(single_response.choices[0].message.content[:300])

# Approach 2: A 3-stage pipeline
# Stage 1: Extraction
extraction = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": f"Extract ONLY the numeric data and concrete facts:\n{text}"}],
    temperature=0
).choices[0].message.content

# Stage 2: Analysis (receives only the data, not the original text)
analysis = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": f"Analyze trends and risks in this data:\n{extraction}"}],
    temperature=0
).choices[0].message.content

# Stage 3: Recommendations (receives the analysis, not the raw data)
recommendations = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": f"Generate 3 actionable recommendations:\n{analysis}"}],
    temperature=0
).choices[0].message.content

print("\n=== 3-STAGE PIPELINE ===")
print(f"Extract: {extraction[:100]}...")
print(f"Analysis: {analysis[:100]}...")
print(f"Recommendations: {recommendations[:100]}...")

With a short text, both approaches produce similar results. The difference becomes obvious with long documents, complex tasks, or when you need intermediate validation.


Real Use Case: A Content Intelligence Platform

To give you concrete context, here's a real system that uses every pattern in the module:

Content analysis system for a media company:

Ingestion module:
  Input: Article, video transcript, or PDF
  
Processing pipeline:
  1. Classify (conditional): What kind of content is this?
  2. Normalize (sequential): Convert to a standard format
  3. Parallel analysis:
     a. Extract entities (NER)
     b. Generate summary (3 length levels)
     c. Detect sentiment & tone
     d. Calculate readability scores
  4. Context enrichment (ReAct with search): Add external context
  5. Quality check (Self-Refine): Verify and improve if needed
  6. Format output (sequential): Structured JSON for the API

Context management:
  - Articles < 2K tokens: Process directly
  - Articles 2K-30K tokens: Sliding window
  - Articles > 30K tokens: Hierarchical summarization

Warm-up Exercises

Exercise 1: Identify the right pattern

For each of the following systems, identify which composition pattern you'd use:

a) Transcribe an audio file, translate it, and generate subtitles b) Simultaneously analyze the tone, the entities, and the summary of an email c) Process a document that may be in English or Spanish with different flows d) Improve the quality of some code until it passes every test

See solution

a) Sequential chaining: Each stage depends on the previous one (first transcribe, then translate, then generate subtitles).

b) Parallel processing: Tone, entities, and summary are independent analyses of the same input. They can run simultaneously with asyncio.

c) Conditional branching: Detect the language first, then fork toward the English or Spanish flow.

d) Iterative refinement: Generate code → run tests → if they fail, refine → repeat until they pass.

Exercise 2: Design the interfaces

For a resume analysis pipeline that produces: role classification, fit score, strengths/weaknesses, and suggested interview questions:

  1. Split it into 4 sub-tasks
  2. Define the output schema for each one (as a Python dict)
  3. Specify the dependencies between sub-tasks
See solution
# Sub-task 1: Information extraction
extraction_schema = {
    "name": str,
    "years_of_experience": int,
    "skills": list[str],
    "education": list[dict],  # [{institution, degree, year}]
    "previous_companies": list[dict]  # [{company, role, duration}]
}
# Dependencies: none

# Sub-task 2: Role classification (can run in parallel with sub-task 1)
classification_schema = {
    "main_role": str,   # "backend_dev", "data_scientist", etc.
    "level": str,       # "junior", "mid", "senior"
    "confidence": float # 0-1
}
# Dependencies: none (operates on the raw resume)

# Sub-task 3: Fit score
score_schema = {
    "total_score": float,   # 0-100
    "technical_score": float,
    "experience_score": float,
    "strengths": list[str],
    "weaknesses": list[str]
}
# Dependencies: Sub-task 1 (extraction), Sub-task 2 (classification)

# Sub-task 4: Interview questions
questions_schema = {
    "technical_questions": list[str],
    "experience_questions": list[str],
    "red_flag_questions": list[str]  # To probe the weak points
}
# Dependencies: Sub-task 1, Sub-task 3

Summary

In this module you'll learn that the most powerful AI systems aren't the ones with the smartest prompt, but the ones that orchestrate multiple specialized prompts robustly.

The key concepts:

  • Decomposition: Split complex tasks into sub-tasks with clear inputs/outputs
  • Sequential chaining: The output of A is the input of B
  • Parallel processing: A, B, C run simultaneously on the same input
  • Conditional branching: The path varies depending on the content
  • Iterative refinement: Loop until you reach a minimum quality
  • Context management: Strategies for documents that exceed the context window
  • Error handling: Retry per stage, fallback, validation with Pydantic

What This Module Does NOT Cover

TopicCovered?Where you'll see it
Sequential, parallel, conditional chainingYes (Capsules 02-03)This module
Multi-stage pipelines with state and retryYes (Capsule 04)This module
Context window managementYes (Capsule 05)This module
Multi-turn memory strategiesYes (Capsule 06)This module
Routing by complexityYes (Capsule 07)This module
Quality evaluation per stageNoModule 07 (Evaluation)
Caching between pipeline stagesNoModule 08 (Production)
Frameworks such as LangChain or LlamaIndexNoWe use the APIs directly

This module teaches you the fundamentals of composition using plain Python and the OpenAI API. Understanding these fundamentals is what lets you later use any framework with deep comprehension, instead of depending on abstractions you don't understand.


Frequently Asked Questions

Why not use LangChain directly? Because LangChain abstracts away the patterns you need to understand. If you know how to build a pipeline manually with state, retry and context management, you can use LangChain (or any framework) in an informed way. If you don't understand it, you're tied to an abstraction that can change or break.

When is a pipeline worth it vs. a single prompt? When the task has at least one of these characteristics: (1) the input exceeds 50% of the context window, (2) the task has clearly distinct sub-tasks, (3) you need intermediate validation, or (4) different parts benefit from different models or temperatures.

Are pipelines slower than a single prompt? Yes in total latency, but they're more reliable. A 4-stage pipeline takes longer than a single prompt, but if one stage fails, you only retry that stage. With a single prompt, if it fails, you lose all the computation.

Can I use async to speed up pipelines? Yes. Independent stages (the parallel pattern) can run with asyncio.gather(). Capsule 02 shows how to implement this.


Additional resources

  1. Least-to-Most Prompting (Zhou et al., 2022) - Systematic decomposition
  2. LangChain LCEL Documentation - Framework for chaining
  3. OpenAI Cookbook - Building production systems
  4. Pydantic Documentation - Output validation
  5. Python asyncio documentation - For parallel execution
  6. Prompt Engineering Guide - Chaining