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
| # | Capsule | Topic | What you'll learn |
|---|---|---|---|
| 01 | Introduction | One prompt isn't enough | Composition principles |
| 02 | Prompt chaining | Output → Input | Sequential, conditional, parallel |
| 03 | Decomposition | Framework for decomposing | Identify sub-tasks, interfaces |
| 04 | Multi-stage pipelines | Orchestration, state, retry | PipelineState, error handling |
| 05 | Context window management | Summarization, sliding window | Process long documents |
| 06 | Multi-turn strategies | Memory, pruning | Long conversations |
| 07 | Routing by complexity | Cheap vs. powerful model | Cost optimization |
| 08 | Project | Multi-Stage Analysis Pipeline | A 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:
- Split it into 4 sub-tasks
- Define the output schema for each one (as a Python dict)
- 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
| Topic | Covered? | Where you'll see it |
|---|---|---|
| Sequential, parallel, conditional chaining | Yes (Capsules 02-03) | This module |
| Multi-stage pipelines with state and retry | Yes (Capsule 04) | This module |
| Context window management | Yes (Capsule 05) | This module |
| Multi-turn memory strategies | Yes (Capsule 06) | This module |
| Routing by complexity | Yes (Capsule 07) | This module |
| Quality evaluation per stage | No | Module 07 (Evaluation) |
| Caching between pipeline stages | No | Module 08 (Production) |
| Frameworks such as LangChain or LlamaIndex | No | We 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
- Least-to-Most Prompting (Zhou et al., 2022) - Systematic decomposition
- LangChain LCEL Documentation - Framework for chaining
- OpenAI Cookbook - Building production systems
- Pydantic Documentation - Output validation
- Python asyncio documentation - For parallel execution
- Prompt Engineering Guide - Chaining