What Are Agent Patterns?
Agent patterns are reusable architectural blueprints for building AI agent workflows. Just as design patterns in software engineering provide proven solutions to common problems, agent patterns provide structured approaches to common AI agent challenges.
The Problem with Ad-Hoc Agents
When building AI agents from scratch, developers often face:
Inconsistent Results: Single-prompt approaches are unreliable for complex tasks
Hard to Debug: Monolithic prompt chains are difficult to troubleshoot
No Structure: Each agent is built from scratch with unique logic
Limited Reusability: Solutions don’t transfer across projects
Unpredictable Behavior: Without structure, agents behave inconsistently
Example: Ad-Hoc Approach
# Ad-hoc approach - no pattern
def answer_question(question: str) -> str:
prompt = f"Answer this question: {question}"
response = llm.invoke(prompt)
return response
# Problems:
# - No tool use
# - No verification
# - No reasoning structure
# - No error handling
# - No iterative refinement
This works for simple queries but fails for complex tasks requiring:
Multiple reasoning steps
Tool/API interactions
Self-correction
Quality refinement
Exploration of alternatives
What Are Patterns?
Agent patterns are structured workflows that break complex agent behavior into:
Clear Stages: Distinct phases with specific responsibilities
State Management: Tracking progress and intermediate results
Conditional Logic: Decision points for routing and iteration
Tool Integration: Structured approach to external interactions
Quality Control: Built-in verification and refinement
Example: Pattern-Based Approach
# ReAct Pattern - structured workflow
agent = ReActAgent(
llm_configs=configs,
tools={"search": search_tool, "calculator": calc_tool},
max_iterations=5
)
result = agent.run("What is the population of Tokyo times 2?")
# Workflow:
# 1. Thought: "I need to find Tokyo's population"
# 2. Action: search("Tokyo population")
# 3. Observation: "~14 million"
# 4. Thought: "Now I need to multiply by 2"
# 5. Action: calculator("14000000 * 2")
# 6. Observation: "28000000"
# 7. Final Answer: "28 million"
The pattern provides:
Structured reasoning loop
Tool selection logic
Error recovery
Iteration control
State tracking
Why Patterns Matter
1. Reliability
Patterns provide consistent, predictable behavior:
# Without pattern: unpredictable
response = llm.invoke("Write a story")
# Quality varies wildly, no guarantee of structure
# With Reflection pattern: consistent quality
agent = ReflectionAgent(llm_configs=configs)
response = agent.run("Write a story")
# Always generates, critiques, and refines
2. Debuggability
Patterns break workflows into inspectable stages:
# Without pattern: black box
result = complex_prompt(input)
# Can't see intermediate steps
# With Plan & Solve pattern: transparent
agent = PlanAndSolveAgent(llm_configs=configs)
result = agent.run(input)
# Can inspect: plan, step results, aggregation
3. Reusability
Patterns transfer across domains:
# Same ReAct pattern, different domains
# Domain 1: Customer Service
customer_agent = ReActAgent(
tools={"check_order": check_order_status, "cancel_order": cancel},
llm_configs=configs
)
# Domain 2: Data Analysis
data_agent = ReActAgent(
tools={"query_db": run_sql, "plot": create_chart},
llm_configs=configs
)
# Same pattern, different tools!
4. Best Practices
Patterns encode research-backed approaches:
Pattern |
Research Paper |
Key Insight |
|---|---|---|
ReAct |
Interleaving reasoning and acting improves performance |
|
Reflection |
Self-reflection improves output quality |
|
Plan & Solve |
Separating planning from execution increases reliability |
|
STORM |
Multi-perspective synthesis improves comprehensiveness |
Pattern vs Non-Pattern Approaches
Comparison Table
Aspect |
Non-Pattern |
Pattern-Based |
|---|---|---|
Structure |
Ad-hoc prompts |
Defined workflow stages |
State |
None or implicit |
Explicit state tracking |
Tools |
Manual integration |
Structured tool use |
Iteration |
One-shot |
Controlled loops |
Debugging |
Difficult (black box) |
Easy (inspectable stages) |
Quality |
Inconsistent |
Predictable |
Reusability |
Low |
High |
Maintenance |
Brittle |
Modular |
Real-World Example
Task: Generate a research report on quantum computing
Non-Pattern Approach:
prompt = """
Write a comprehensive research report on quantum computing.
Include history, current state, applications, and future outlook.
Use authoritative sources and cite them.
"""
report = llm.invoke(prompt)
# Problems:
# - No source verification
# - May hallucinate facts
# - No structured research process
# - Can't trace reasoning
# - Single attempt, no refinement
Pattern Approach (STORM):
agent = STORMAgent(
llm_configs=configs,
retrieval_tools={"search": wikipedia_search, "scholar": academic_search}
)
report = agent.run("quantum computing")
# Workflow:
# 1. Generate outline (intro, history, applications, future, conclusion)
# 2. Generate perspectives (physicist, engineer, business analyst)
# 3. Generate questions per perspective per section
# 4. Retrieve information for each question
# 5. Synthesize each section from retrieved info
# 6. Compile final report with citations
# Benefits:
# - Systematic research process
# - Multiple viewpoints
# - Source-backed claims
# - Transparent methodology
# - Structured output
Common Pattern Categories
1. Tool-Using Patterns
Agents that interact with external systems:
ReAct: Reason about what to do, act with tools
LLM Compiler: Plan and execute tools in parallel
REWOO: Separate planning from tool execution for efficiency
Use When: Need to search, calculate, query APIs, or access external data
2. Refinement Patterns
Agents that improve output through iteration:
Reflection: Generate, critique, refine
Reflexion: Learn from failures across trials
LATS: Explore multiple solution paths
Use When: Output quality is critical (writing, coding, design)
3. Planning Patterns
Agents that decompose complex tasks:
Plan & Solve: Create plan, execute sequentially
Self-Discovery: Dynamically select reasoning strategies
Use When: Task has multiple logical steps or requires strategic thinking
4. Multi-Perspective Patterns
Agents that synthesize diverse viewpoints:
STORM: Multi-perspective research and synthesis
Use When: Need comprehensive, well-rounded analysis
When to Use Patterns
Use Patterns When:
Task Complexity: Requires multiple steps or reasoning stages
Quality Matters: Output must be reliable and consistent
Need Tools: Must interact with external systems/data
Refinement Required: Initial output needs improvement
Reusability: Will apply same workflow to different inputs
Debugging: Need to understand agent behavior
Collaboration: Others will work on or extend the agent
Use Simple Prompts When:
Task is Trivial: Single, straightforward question
Speed Critical: Can’t afford multi-step workflow
Prototyping: Exploring what’s possible
Budget Constrained: Multiple LLM calls are too expensive
Example Decision:
# Simple task - direct prompt is fine
answer = llm.invoke("What is the capital of France?")
# Complex task - use pattern
agent = ReActAgent(
tools={"search": search, "calculate": calc},
llm_configs=configs
)
answer = agent.run(
"Find the GDP of France and Germany, "
"calculate the ratio, and explain the economic factors"
)
The Agent Patterns Library
This library provides 9 battle-tested patterns:
Pattern |
Category |
Best For |
|---|---|---|
ReAct |
Tool-Using |
Q&A with external data |
Reflection |
Refinement |
High-quality content |
Plan & Solve |
Planning |
Multi-step tasks |
Reflexion |
Refinement |
Learning from failures |
LLM Compiler |
Tool-Using |
Parallel tool execution |
REWOO |
Tool-Using |
Cost-efficient workflows |
LATS |
Refinement |
Exploring solutions |
Self-Discovery |
Planning |
Adaptive reasoning |
STORM |
Multi-Perspective |
Comprehensive research |
Each pattern is:
Fully Implemented: Ready to use out of the box
Customizable: Override prompts and behavior
Type-Safe: Full type hints for IDE support
Well-Tested: Comprehensive test coverage
Documented: Clear API and examples
Design Philosophy
Agent Patterns follows these principles:
1. Synchronous by Design
# Synchronous - simple and debuggable
result = agent.run(input)
# NOT async - complexity removed
# result = await agent.run(input)
Why: Async adds complexity, makes debugging harder, and isn’t needed for most agent workflows.
2. Externalized Configuration
# Prompts in files, not code
agent = ReActAgent(prompt_dir="my_prompts")
# Easy to customize without touching code
Why: Separates prompt engineering from software engineering, enables non-coders to customize.
3. Explicit State
# State is explicit and inspectable
state = {
"input": "query",
"thought": "...",
"action": {...},
"observation": "...",
}
Why: Makes debugging and monitoring straightforward.
4. Composable Tools
# Tools are simple functions
def my_tool(input: str) -> str:
return result
# Easy to test, mock, and reuse
Why: Keep tools simple, focused, and testable.
Next Steps
Understand the Architecture: Learn how patterns work internally
Choose a Pattern: Use the pattern selection guide
See Examples: Explore real-world examples
API Reference: Study the complete API documentation
Build Custom: Learn to extend BaseAgent for custom patterns
Key Takeaways
Patterns provide structure for complex agent workflows
They improve reliability, debuggability, and reusability
Use patterns for complex tasks, simple prompts for trivial queries
Each pattern encodes research-backed best practices
The library provides 9 production-ready patterns
All patterns share a consistent architecture making them easy to learn and use