AI & assistant-friendly summary

This section provides structured content for AI assistants and search engines. You can cite or summarize it when referencing this page.

Summary

Build production-ready AI agents on Bedrock with tool use, multi-step workflows, and supervisor patterns. From single agents to multi-agent orchestration.

Key Facts

  • Build production-ready AI agents on Bedrock with tool use, multi-step workflows, and supervisor patterns
  • Build production-ready AI agents on Bedrock with tool use, multi-step workflows, and supervisor patterns

Entity Definitions

Bedrock
Bedrock is an AWS service discussed in this article.

AWS AI Agents: Building Production-Ready Agentic Workflows on Bedrock

genai Palaniappan P 9 min read

Quick summary: Build production-ready AI agents on Bedrock with tool use, multi-step workflows, and supervisor patterns. From single agents to multi-agent orchestration.

Key Takeaways

  • Build production-ready AI agents on Bedrock with tool use, multi-step workflows, and supervisor patterns
  • Build production-ready AI agents on Bedrock with tool use, multi-step workflows, and supervisor patterns
AWS AI Agents: Building Production-Ready Agentic Workflows on Bedrock
Table of Contents

The Age of AI Agents

In late 2024 and early 2025, the industry shifted. Simple chatbots that only respond to prompts are being replaced by AI agents — autonomous systems that can plan, use tools, and execute multi-step workflows without human intervention.

Amazon Bedrock’s Agents service (now GA) makes building enterprise AI agents as simple as defining your tools. No custom orchestration code. No manual agentic loop management. Just declare your tools, configure guardrails, and let Bedrock handle routing, retries, and memory.

This guide covers everything you need to build production-grade AI agents on Bedrock — from simple tool-calling agents to complex multi-agent supervisor patterns.


What Bedrock Agents Actually Are

An AI agent is fundamentally different from a chatbot:

Chatbot: User → Prompt → Model → Response → Done.

Agent: User → Goal → Model decides what tools to invoke → Invoke tools → Get results → Model re-evaluates → Invoke more tools → Final response → Done.

Bedrock Agents automate the entire loop. You define:

  1. Foundational model (Claude, Llama, Mistral, etc.)
  2. Tools — anything your agent can do (API calls, database lookups, file operations)
  3. Guardrails — limits on what the agent can do (spending caps, topic restrictions, hallucination filters)
  4. Instructions — the agent’s system prompt / role

Bedrock handles:

  • Deciding when to invoke tools
  • Parsing tool results
  • Retrying failed tool calls
  • Building context windows
  • Memory management
  • Stopping conditions (done? need more info?)

Bedrock Agents vs. Custom Agents (LangChain, LangGraph)

Bedrock Agents (Managed Service)

Pros:

  • Zero orchestration boilerplate — 80% less code
  • Built-in error handling and retries
  • Integrated Bedrock Guardrails
  • Automatic context window management
  • Pay only for model invocations + tool calls (no management overhead)

Cons:

  • Less flexibility — you work within Bedrock’s agent loop
  • Tool invocation is synchronous (tools must return quickly)
  • Limited visibility into agent decision-making

Custom Agents (LangChain, LangGraph, etc.)

Pros:

  • Full control over agentic loop
  • Can implement complex, domain-specific logic
  • Support async tool calling (call 10 tools in parallel)
  • Transparent decision-making (see exactly why agent chose each action)
  • Framework-agnostic (run anywhere)

Cons:

  • 5-10x more code to implement safely
  • You own error handling, retries, context management
  • Higher operational complexity
  • Harder to debug production issues

Decision Rule:

  • Use Bedrock Agents if you need to ship quickly, tools are simple, and you want AWS-managed reliability
  • Use custom agents if you need control over the agentic loop, tool calling is complex, or you’re running on non-AWS infrastructure

For most enterprise teams, start with Bedrock Agents. Migrate to custom agents only if Bedrock’s constraints become a bottleneck.


How Bedrock Agents Work: The Agentic Loop

When you invoke a Bedrock Agent, the following sequence happens automatically:

1. User → Agent: "Find all invoices from Q1 2026 for Acme Corp that exceed $10K"

2. Agent reads instruction + tools → determines it needs:
   - Database tool: query invoices
   - Date filter tool: Q1 date range
   - Amount threshold tool: >$10K check

3. Agent invokes all three tools (parallel where possible):
   - ✓ Database tool returns 47 invoices
   - ✓ Date filter: 12 in Q1 2026
   - ✓ Amount threshold: 3 exceed $10K

4. Agent synthesizes results:
   - "Found 3 invoices matching your criteria:
      - Invoice #INV-2026-001: $12,500
      - Invoice #INV-2026-045: $11,200
      - Invoice #INV-2026-089: $15,800"

5. Responds to user with formatted result

Key insight: The agent thinks step-by-step, invokes tools to gather data, and synthesizes results. This is fundamentally different from a simple “what did you ask?” → response model.


Building Your First Bedrock Agent

Step 1: Define Your Tools (OpenAPI Schema)

Tools are described as OpenAPI 3.0 schemas. Here’s an example — a “Get Customer Info” tool:

{
  "name": "get_customer_info",
  "description": "Retrieve customer account information, including name, email, account status, and billing address.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customerId": {
        "type": "string",
        "description": "Unique customer ID (e.g., 'CUST-12345')"
      },
      "includeOrderHistory": {
        "type": "boolean",
        "description": "If true, also return the customer's order history for the past 12 months"
      }
    },
    "required": ["customerId"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "email": { "type": "string" },
      "accountStatus": { "type": "string", "enum": ["active", "suspended", "closed"] },
      "billingAddress": { "type": "string" },
      "orderHistory": { "type": "array", "items": { "type": "object" } }
    }
  }
}

This schema tells the agent:

  • What the tool does (description)
  • What inputs it accepts (inputSchema)
  • What outputs to expect (outputSchema)

The agent can then reason: “User asked for customer info. I have a get_customer_info tool. The user said ‘customer CUST-12345’. I’ll invoke the tool with customerId: 'CUST-12345' and includeOrderHistory: true (since they might want recent orders).”

Step 2: Create a Lambda Function (Tool Handler)

Your Lambda receives the tool invocation and returns results:

import json
import boto3

def lambda_handler(event, context):
    tool_name = event['toolName']
    tool_input = event['toolInput']

    if tool_name == 'get_customer_info':
        customer_id = tool_input['customerId']
        include_history = tool_input.get('includeOrderHistory', False)

        # Query your database
        customer = db.query_customer(customer_id)

        result = {
            'name': customer['name'],
            'email': customer['email'],
            'accountStatus': customer['status'],
            'billingAddress': customer['address'],
        }

        if include_history:
            result['orderHistory'] = db.get_orders(customer_id, months=12)

        return {
            'statusCode': 200,
            'body': json.dumps(result)
        }

Step 3: Create the Agent in Bedrock

aws bedrock-agent create-agent \
  --agent-name "Customer Support Agent" \
  --agent-role-arn "arn:aws:iam::ACCOUNT:role/BedrockAgentRole" \
  --foundation-model-arn "arn:aws:bedrock:region::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0" \
  --instruction "You are a customer support agent. Help customers with account inquiries, orders, and billing questions. Always verify the customer ID before providing sensitive information."

Step 4: Register Your Tools

aws bedrock-agent create-agent-action-group \
  --agent-id "AGENT_ID" \
  --agent-version "DRAFT" \
  --action-group-name "CustomerTools" \
  --api-schema "{
    \"tools\": [
      { tool schema from step 1 }
    ]
  }" \
  --action-group-executor \
    lambdaDetails='{ \"lambdaArn\": \"arn:aws:lambda:region:account:function:agent-tool-handler\" }'

Step 5: Invoke the Agent

bedrock_agent_runtime = boto3.client('bedrock-agent-runtime')

response = bedrock_agent_runtime.invoke_agent(
    agentId='AGENT_ID',
    agentAliasId='AIDX...',  # Production alias
    sessionId='user-session-123',
    inputText="What's the status of account CUST-12345? Include recent orders."
)

# Stream the response
for event in response['body']:
    if 'chunk' in event:
        print(event['chunk']['bytes'].decode())

Advanced Pattern: Multi-Agent Supervisor

As your agent workload grows, you’ll need specialized agents. The supervisor pattern solves this:

User Request

┌──────────────────────────────────────┐
│  Supervisor Agent                    │
│  (Routes to specialists)             │
└──────────┬───────────────────────────┘

   ┌───────┴────────────────┬──────────────────┐
   ↓                        ↓                   ↓
┌──────────┐           ┌─────────┐        ┌─────────────┐
│ Billing  │           │ Support │        │ Orders      │
│ Agent    │           │ Agent   │        │ Agent       │
└──────────┘           └─────────┘        └─────────────┘
   ↓                        ↓                   ↓
  DB                       KB                  API

Supervisor logic:

  1. Parse user request
  2. Classify intent (billing, support, orders, etc.)
  3. Route to appropriate specialist agent
  4. Collect specialist’s response
  5. Synthesize final answer

Example: User asks “I was charged twice for order #ORD-123. Can you refund me and explain what happened?”

  1. Supervisor recognizes: billing issue + order issue → needs both Orders Agent and Billing Agent
  2. Orders Agent: retrieves order details (finds duplicate charge)
  3. Billing Agent: initiates refund, documents reason
  4. Supervisor: “I found the duplicate charge on your account. I’ve initiated a refund for $X, which should appear in 3-5 business days. The issue was caused by a retry in our payment processor…”

This is dramatically more scalable than a single monolithic agent handling everything.


Production Guardrails: Keeping Agents Safe

1. Bedrock Guardrails (Built-in)

bedrock_agent.update_guardrails(
    guardrail_configurations={
        'content_filters': {
            'violence': 'BLOCK',
            'sexual_content': 'BLOCK',
            'hate_speech': 'BLOCK',
            'insults': 'BLOCK',
        },
        'pii_redaction': {
            'email': True,
            'phone': True,
            'credit_card': True,
            'ssn': True,
        },
        'topic_controls': {
            'blocked_topics': [
                'Competitor product recommendations',
                'Political opinions',
                'Medical advice',
            ]
        },
        'spending_limits': {
            'max_tokens_per_invocation': 4000,
            'max_cost_per_session': 10.00,  # $10 max per user session
        }
    }
)

2. Tool-Level Controls

Limit what agents can do in each tool:

# Tool: UpdateUserAccount
allowed_fields = ['email', 'phone', 'address']  # Can update these
forbidden_fields = ['accountStatus', 'creditLimit', 'role']  # Cannot

# Agent requests: UpdateUserAccount(customerId='CUST-123', accountStatus='suspended')
# → Tool rejects: "You don't have permission to modify accountStatus"

3. Monitoring & Alerts

# Log all agent invocations
cloudwatch.put_metric_data(
    Namespace='BedrockAgents',
    MetricData=[
        {
            'MetricName': 'AgentInvocations',
            'Value': 1,
            'Dimensions': [
                {'Name': 'AgentId', 'Value': agent_id},
                {'Name': 'IntentClass', 'Value': 'billing'},
                {'Name': 'Success', 'Value': 'true'},
            ]
        }
    ]
)

# Alert if error rate > 5%
cloudwatch.put_metric_alarm(
    AlarmName='BedrockAgentErrorRate',
    MetricName='AgentErrors',
    Statistic='Sum',
    Period=300,
    EvaluationPeriods=2,
    Threshold=0.05,
)

Real-World Example: Customer Support Agent

Here’s a complete multi-tool agent for customer support:

Tools:

  1. get_customer_info — Look up account details
  2. get_order_status — Track shipments
  3. initiate_return — Process returns
  4. request_refund — Handle refunds
  5. escalate_to_human — Hand off to support team

Instructions:

You are a helpful customer support agent. Your goal is to resolve customer issues quickly.

Rules:
- Always verify the customer ID before helping (ask for it if not provided)
- For refunds > $500, always escalate to a human agent
- For simple returns, process immediately
- Never refund without customer consent
- If you can't help, escalate

Tone: Professional, empathetic, solution-oriented.

Workflow when customer says: “I want to return my order”

1. Agent: Which order? (if not provided)
2. Agent invokes: get_order_status(orderId='ORD-12345')
3. Tool returns: Order shipped 5 days ago, eligible for return
4. Agent: Your order is eligible. I can process the return.
   You'll receive a prepaid label, and the refund will happen
   within 5 days of us receiving it back.
5. Agent invokes: initiate_return(orderId='ORD-12345')
6. Tool returns: Return label sent to your email
7. Agent: Done! Label is in your email. Ship it back whenever ready.

Performance & Cost Optimization

Latency Reduction

Problem: Agent making 10 sequential tool calls takes 10x longer.

Solution: Parallel tool invocation.

# Sequential (slow)
customer = get_customer(id)
orders = get_orders(customerId=id)
refunds = get_refunds(customerId=id)
# Total latency: ~3 seconds

# Parallel (fast)
customer, orders, refunds = await asyncio.gather(
    get_customer(id),
    get_orders(customerId=id),
    get_refunds(customerId=id),
)
# Total latency: ~1 second

Bedrock Agents support parallel tool invocation automatically for independent tools.

Cost Optimization

Agent invocation costs:

  • Base: Claude 3.5 Sonnet pricing (input + output tokens)
  • Tool invocation: $0 (part of your Lambda costs)
  • Guardrails: ~$0.01 per request

To reduce costs:

  1. Use cheaper models (Claude Haiku, Llama 2) for simple tasks
  2. Limit max_tokens (e.g., agent responses capped at 1000 tokens)
  3. Batch-process agent requests (daily digest vs. per-request)
  4. Cache common tool outputs (if customer info doesn’t change hourly, cache it)

When to Build AI Agents vs. Keep Simple Chatbots

ScenarioChatbotAgent
Multi-step workflows
Tool/API integration
Autonomous decision-making
Customer support (escalations)
Content generation
FAQs/knowledge lookup
Real-time market data
Coding assistants✅ (complex cases)

Decision: If you need the agent to do anything beyond generate text, consider Bedrock Agents.


Moving Forward

Next Steps:

  1. Define 3–5 core tools for your use case
  2. Create OpenAPI schemas for each
  3. Build Lambda handlers (30 minutes per tool)
  4. Test in Bedrock Agent console
  5. Deploy aliases for dev/staging/prod
  6. Monitor error rates and user feedback
  7. Iterate on instructions based on failures

Related Reading:


Ready to Build Production AI Agents?

If you’re ready to deploy AI agents but unsure about architecture, tool design, or integration patterns, book a free GenAI discovery call. We’ll assess your use cases and recommend the right approach — whether that’s Bedrock Agents, custom multi-agent patterns, or a hybrid approach.

We’ve deployed agents for: customer support automation, sales qualification, incident triage, data processing pipelines, and financial document analysis. Whatever your workflow, we can help architect it.

PP
Palaniappan P

AWS Cloud Architect & AI Expert

AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.

AWS ArchitectureCloud MigrationGenAI on AWSCost OptimizationDevOps

Ready to discuss your AWS strategy?

Our certified architects can help you implement these solutions.

Recommended Reading

Explore All Articles »