Agentic AI Design Patterns (Part 1): Foundational Patterns and Practical Application
Agentic AI Design Patterns (Part 1): Foundational Patterns and Practical Application
GenAI vs Agentic AI: What's the Difference?
While the emergence of Generative AI (GenAI) has revolutionized the AI industry, simply generating text or images is insufficient for solving complex business problems. This is where Agentic AI comes in.
Key Differences
| Feature | Gen AI | Agentic AI |
|---|---|---|
| Operation | Immediate response to prompts | Goal-oriented multi-step execution |
| Autonomy | Dependent on human commands | Autonomous planning and execution |
| Interaction | Single input → Single output | Continuous interaction with environment |
| Tool Usage | Limited | Active use of external tools/APIs |
| Memory | Only maintains conversation context | State tracking and long-term memory |
| Decision Making | Reactive | Proactive |
Understanding Through Real Examples
GenAI Scenario:
User: "Create a quarterly sales report"
GenAI: [Generates report text]
→ Provides only generic templates without actual data
Agentic AI Scenario:
User: "Create a quarterly sales report"
Agentic AI:
1. Query actual sales data from database (Tool Use)
2. Perform year-over-year analysis (Reasoning)
3. Generate trend visualizations (Tool Use)
4. Derive insights and create report (Reasoning + Acting)
5. Final review and improvements (Reflection)
→ Provides specific, actionable report based on real data
Three Core Principles of Agentic AI
According to AWS Prescriptive Guidance, Agentic AI is defined by three characteristics:
-
Asynchronous
- Operates in loosely coupled, event-driven environments
- Can process multiple tasks in parallel
- Proceeds independently without waiting for real-time responses
-
Autonomy
- Acts independently without human or external control
- Makes judgments and decisions based on situations
- Adapts and responds to unexpected circumstances
-
Agency
- Acts with clear goals
- Can have real impact on the environment
- Makes decisions with accountability for results
These characteristics are the key elements that distinguish Agentic AI from traditional deterministic automation.
Why is Agentic AI Important?
Agentic AI goes beyond simply responding to prompts to become an intelligent system that autonomously plans and executes complex tasks. This series will deeply explore production-validated Agentic AI design patterns, examining the characteristics of each pattern and real-world applications with AWS services.
Core Principles of Agentic Patterns
According to AWS Prescriptive Guidance, all Agentic AI systems follow three fundamental principles:
- Asynchronous: Agents operate in loosely coupled, event-driven environments
- Autonomy: Agents act independently without human or external control
- Agency: Agents are goal-oriented and can impact their environment
These principles are the core elements that distinguish Agentic AI from traditional deterministic automation.
1. ReAct Pattern: Combining Reasoning and Acting
Pattern Overview
The ReAct (Reason + Act) pattern is the foundational orchestration strategy for Amazon Bedrock Agents, where agents iteratively perform thinking (Reasoning) and action (Acting) to solve problems.
Mechanism
The ReAct pattern operates in the following cyclic structure:
- Reason: Use the Foundation Model's reasoning capabilities to analyze requests and break them into logical steps
- Act: Determine necessary tools (Action Groups) or knowledge bases and execute API calls or data queries
- Observe: Observe execution results and determine next steps
- Iterate: Repeat until task completion
# Conceptual structure of ReAct pattern
while not task_completed:
# Reasoning Phase
thought = llm.reason(current_state, goal)
# Acting Phase
action = select_tool(thought)
observation = execute_action(action)
# Update State
current_state = update(current_state, observation)
Use Cases
When to Use:
- When multi-step tasks are needed (e.g., data collection → analysis → report generation)
- When interaction with external APIs or databases is required
- When next steps need to be determined dynamically during execution
Real-World Examples:
- Customer support system: Query analysis → Related document search → Answer generation → Ticket update
- Data analysis pipeline: Data source identification → Data extraction → Transformation → Visualization
AWS Implementation
Amazon Bedrock Agents provide ReAct pattern by default:
import boto3
bedrock_agent = boto3.client('bedrock-agent-runtime')
response = bedrock_agent.invoke_agent(
agentId='AGENT_ID',
agentAliasId='AGENT_ALIAS_ID',
sessionId='session-123',
inputText='Generate quarterly sales report'
)
# Agent automatically:
# 1. Query sales data from database (Act)
# 2. Analyze data and identify trends (Reason)
# 3. Call visualization tools (Act)
# 4. Generate report (Reason + Act)
Trade-offs
Advantages:
- Transparency: Each step's reasoning process is traceable
- Adaptability: Can adjust plans based on intermediate results
- Consistency: Predictable workflow
Disadvantages:
- Latency: Increased response time due to LLM calls at each step
- Cost: Higher operational costs due to high token usage
- Sequential nature: Inefficient for tasks requiring parallel processing
2. Reflection Pattern: Self-Improvement Mechanism
Pattern Overview
The Reflection pattern implements a self-feedback loop where agents evaluate and improve their own outputs. This mimics the human process of reviewing and refining work.
Mechanism
def reflection_loop(task, max_iterations=3):
output = generate_initial_output(task)
for i in range(max_iterations):
# Self-Critique
critique = llm.evaluate(output, task)
if critique.is_satisfactory():
break
# Self-Improvement
output = llm.refine(output, critique)
return output
The Reflection pattern separates two roles:
- Generator: Creates initial output
- Reflector: Evaluates output and suggests improvements
Use Cases
When to Use:
- When output quality is critical (e.g., technical documentation, code reviews)
- For tasks requiring complex reasoning
- For tasks with high error probability
Real-World Examples:
- Code Generation and Review
# Initial Generation
code = generate_code(requirements)
# Reflection Loop
for _ in range(3):
issues = analyze_code(code) # Review security, performance, readability
if not issues:
break
code = improve_code(code, issues)
- Technical Documentation Writing
- Draft generation → Accuracy validation → Clarity improvement → Completeness evaluation
AWS Implementation
Implementation using Amazon Bedrock's Converse API:
import boto3
bedrock = boto3.client('bedrock-runtime')
def generate_with_reflection(prompt, model_id):
# Generation Phase
response = bedrock.converse(
modelId=model_id,
messages=[{
'role': 'user',
'content': [{'text': prompt}]
}]
)
initial_output = response['output']['message']['content'][0]['text']
# Reflection Phase
reflection_prompt = f"""
Evaluate the following output and suggest improvements:
Output: {initial_output}
Evaluation criteria:
- Accuracy
- Completeness
- Clarity
"""
critique = bedrock.converse(
modelId=model_id,
messages=[{
'role': 'user',
'content': [{'text': reflection_prompt}]
}]
)
# Refinement Phase
if needs_improvement(critique):
refined_output = refine_output(initial_output, critique)
return refined_output
return initial_output
Trade-offs
Advantages:
- Quality improvement: Significant output quality enhancement through iterative refinement
- Error reduction: Minimized mistakes through self-verification
- Reduced human intervention: Automated quality management
Disadvantages:
- Computational cost: Increased costs due to multiple LLM calls
- Latency: Response time proportional to number of iterations
- No convergence guarantee: Not always guaranteed to improve
3. Tool Use Pattern: External Capability Integration
Pattern Overview
The Tool Use pattern enables agents to call external tools and APIs to perform tasks beyond their training data scope. This is an essential pattern in almost all production agents.
Mechanism
Agents use tools in the following manner:
- Tool Discovery: Identify available tools and their functionalities
- Tool Selection: Select appropriate tools for the task
- Parameter Extraction: Extract required parameters for tool invocation
- Tool Invocation: Execute tools and receive results
- Result Integration: Integrate results into context to determine next steps
Use Cases
Essential Use Cases:
- Real-time data queries (weather, stocks, news)
- Database operations (CRUD operations)
- External API integration (payments, email, SMS)
- Code execution (calculations, data analysis)
Real-World Examples:
# Tool Definition
tools = [
{
"name": "get_weather",
"description": "Query current weather information",
"parameters": {
"location": "string",
"unit": "celsius|fahrenheit"
}
},
{
"name": "search_database",
"description": "Search information in database",
"parameters": {
"query": "string",
"table": "string"
}
}
]
# Agent decides which tool to use
user_query = "What's the current weather in Seoul?"
selected_tool = agent.select_tool(user_query, tools)
# -> "get_weather"
result = execute_tool(selected_tool, {"location": "Seoul", "unit": "celsius"})
AWS Implementation
Implementation through Amazon Bedrock Agents' Action Groups:
# Lambda Function for Tool Execution
def lambda_handler(event, context):
action_group = event['actionGroup']
function = event['function']
parameters = event.get('parameters', [])
if function == 'get_weather':
location = next(p['value'] for p in parameters if p['name'] == 'location')
weather_data = fetch_weather(location)
return {
'response': {
'actionGroup': action_group,
'function': function,
'functionResponse': {
'responseBody': {
'TEXT': {
'body': json.dumps(weather_data)
}
}
}
}
}
Action Group Definition:
{
"actionGroupName": "WeatherTools",
"actionGroupExecutor": {
"lambda": "arn:aws:lambda:region:account:function:weather-tool"
},
"apiSchema": {
"payload": {
"openapi": "3.0.0",
"info": {
"title": "Weather API",
"version": "1.0.0"
},
"paths": {
"/weather": {
"get": {
"description": "Get current weather",
"parameters": [
{
"name": "location",
"in": "query",
"required": true,
"schema": {"type": "string"}
}
]
}
}
}
}
}
}
Tool Selection Strategy
Single Tool vs Multiple Tools:
# Single tool invocation
result = agent.use_tool("calculator", {"expression": "123 * 456"})
# Tool chaining
search_results = agent.use_tool("web_search", {"query": "AWS Lambda pricing"})
summary = agent.use_tool("summarizer", {"text": search_results})
translation = agent.use_tool("translator", {"text": summary, "target": "en"})
Trade-offs
Advantages:
- Scalability: Easy to extend functionality by adding new tools
- Accuracy: Utilize real-time data and specialized systems
- Flexibility: Can integrate various external services
Disadvantages:
- Complexity: Requires tool management and error handling
- Dependencies: Impact from external service failures
- Security: Need for API key management and access control
Pattern Selection Guide
Framework for deciding which pattern to use in real projects:
Decision Tree
1. Is the workflow predictable?
YES → Sequential Pattern (covered in Part 2)
NO → Go to 2
2. Is quality more important than speed?
YES → Add Reflection Pattern
NO → Optimize direct execution
3. Do you need external data or tools?
YES → Tool Use Pattern (essential)
NO → Basic Reasoning Pattern
4. Is the task truly complex?
YES → Multi-Agent Pattern (covered in Part 3)
NO → Start with Single Agent + Tool Use
Pattern Combination Examples
Real production systems combine multiple patterns:
Customer Support Agent:
- ReAct (basic orchestration)
- Tool Use (ticketing system, knowledge base)
- Reflection (answer quality verification)
Code Generation Agent:
- ReAct (requirements analysis → design → implementation)
- Tool Use (code execution, test execution)
- Reflection (code review and improvement)
Performance Optimization Strategies
1. Caching Strategy
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_tool_call(tool_name, params_hash):
return execute_tool(tool_name, params_hash)
2. Parallel Tool Invocation
import asyncio
async def parallel_tool_execution(tools):
tasks = [execute_tool_async(tool) for tool in tools]
results = await asyncio.gather(*tasks)
return results
3. Early Termination Conditions
def reflection_with_early_exit(output, quality_threshold=0.9):
for i in range(max_iterations):
quality_score = evaluate_quality(output)
if quality_score >= quality_threshold:
return output # Early termination
output = refine(output)
return output
Monitoring and Observability
In production environments, track the following metrics:
metrics = {
"pattern_type": "ReAct",
"iterations": 3,
"tools_used": ["database", "calculator"],
"total_tokens": 1500,
"latency_ms": 2300,
"success": True,
"quality_score": 0.92
}
# Send metrics to CloudWatch
cloudwatch.put_metric_data(
Namespace='AgenticAI',
MetricData=[
{
'MetricName': 'PatternLatency',
'Value': metrics['latency_ms'],
'Unit': 'Milliseconds',
'Dimensions': [
{'Name': 'Pattern', 'Value': metrics['pattern_type']}
]
}
]
)
Coming Up Next
Part 2 will cover more complex orchestration patterns:
- Planning Pattern: Breaking down complex tasks into subtasks
- Routing Pattern: Routing tasks to specialized agents
- Human-in-the-Loop Pattern: Integrating human review and approval
These patterns are essential for enterprise-grade applications, and we'll examine implementation methods in detail using AWS Step Functions and Amazon EventBridge.
