S
STONI
AI
Agentic AI
AWS Bedrock
Design Patterns
ReAct
Reflection
Tool Use
Agent
LLM

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

FeatureGen AIAgentic AI
OperationImmediate response to promptsGoal-oriented multi-step execution
AutonomyDependent on human commandsAutonomous planning and execution
InteractionSingle input → Single outputContinuous interaction with environment
Tool UsageLimitedActive use of external tools/APIs
MemoryOnly maintains conversation contextState tracking and long-term memory
Decision MakingReactiveProactive

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:

  1. Asynchronous

    • Operates in loosely coupled, event-driven environments
    • Can process multiple tasks in parallel
    • Proceeds independently without waiting for real-time responses
  2. Autonomy

    • Acts independently without human or external control
    • Makes judgments and decisions based on situations
    • Adapts and responds to unexpected circumstances
  3. 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:

  1. Reason: Use the Foundation Model's reasoning capabilities to analyze requests and break them into logical steps
  2. Act: Determine necessary tools (Action Groups) or knowledge bases and execute API calls or data queries
  3. Observe: Observe execution results and determine next steps
  4. 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:

  1. 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)
  1. 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:

  1. Tool Discovery: Identify available tools and their functionalities
  2. Tool Selection: Select appropriate tools for the task
  3. Parameter Extraction: Extract required parameters for tool invocation
  4. Tool Invocation: Execute tools and receive results
  5. 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.

References

Clickable cat