Multi-Agent Chatbot with Amazon Bedrock

Build a multi-agent customer support chatbot using the Strands Agents SDK Swarm pattern.

Introduction

30 Second Summary

Think about calling customer support. You get routed to different specialists depending on whether you have a billing question, a technical issue, or need product information.

In this project, you will build a multi-agent customer support chatbot that does exactly this. Specialized AI agents collaborate autonomously to handle different types of customer inquiries, handing off to each other based on expertise.

What You'll Build

A multi-agent customer support chatbot for a fictional e-commerce company, powered by Amazon Bedrock and the Strands Agents SDK Swarm pattern.

By the end of this project, you'll have:

  • Three specialized AI agents with custom tools for billing, technical support, and product expertise.
  • A Swarm orchestrator that coordinates autonomous handoffs between agents based on query content.
  • An interactive chat interface where you can watch agents collaborate on complex questions in real-time.
  • Secret Mission: Add a Quality Assurance agent that reviews other agents' responses before they reach the customer.

Are there any prerequisites?

You'll need basic Python knowledge (functions, classes, and decorators). You also need an AWS account with Bedrock model access enabled.

Step 1 walks you through setting up everything else from scratch.

Set Up Your Project Environment

Your tools are installed and AWS is configured. Now it's time to create a dedicated workspace for your multi-agent system.

In this step, you'll set up a project folder, isolate your dependencies in a Python virtual environment, and install the Strands Agents SDK. You'll also lay down the file structure your agents will live in.

In this step, get ready to:
  • Create a project directory and virtual environment.
  • Install the Strands Agents SDK packages.
  • Set up the project file structure.
Create your project directory and virtual environment

A virtual environment keeps your project's Python packages separate from your system Python. This prevents version conflicts between projects.

  • In VS Code, open the integrated terminal by pressing Ctrl+` (macOS and Windows).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir techgear-support
cd techgear-support
  • Create a Python virtual environment and activate it.

macOS

  • Create and activate your virtual environment by running these commands:
python3 -m venv .venv
source .venv/bin/activate

Windows

  • Create and activate your virtual environment by running these commands:
python -m venv .venv
.venv\Scripts\activate

Your terminal prompt should now show (.venv) at the beginning. This confirms you're working inside the virtual environment.

Don't see (.venv) in your prompt?

Make sure you ran the activate command (not just the python3 -m venv command). Creating the environment and activating it are two separate steps.

On Windows, if you see an error about execution policies, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned first, then try the activate command again.

Help me activate my Python virtual environment.

Install the Strands Agents SDK

The Strands Agents SDK is an open-source Python framework from AWS for building AI agents. It connects directly to Amazon Bedrock for model inference.

  • Install the SDK packages by running this command:
pip install strands-agents strands-agents-tools

What do these packages do?

  • strands-agents is the core SDK. It provides the Agent class, the @tool decorator, and the multi-agent orchestration patterns (Swarm, Graph, Workflow).
  • strands-agents-tools provides pre-built tools like calculators and web search. You won't use these directly, but they are useful as references.

You should see several Successfully installed messages in your terminal once the installation finishes.

Seeing a permissions error or install failure?

Make sure your (.venv) prefix is visible in your terminal prompt. If it isn't, you're installing into your system Python instead of the virtual environment.

If you see a network timeout, check your internet connection and try running the install command again.

Help me fix my pip install error.

  • Lock your dependencies into a requirements.txt file so you can recreate this environment later. Create the file by running:

macOS

cat > requirements.txt << EOF
strands-agents>=1.0.0
strands-agents-tools>=0.2.0
EOF

Windows

@"
strands-agents>=1.0.0
strands-agents-tools>=0.2.0
"@ | Out-File -Encoding utf8 requirements.txt

Why pin versions in requirements.txt?

The >= syntax means "this version or newer." It ensures anyone who clones your project installs compatible versions of the SDK. Without this file, collaborators wouldn't know which packages your project depends on.

  • Confirm the file was created by running:

macOS

cat requirements.txt

Windows

Get-Content requirements.txt

You should see the two package lines printed in your terminal.

✔️ Awesome, I've got everything!

Great. Your dependencies are locked and ready to go.

ⓧ I'd like to double check the full code

Here's what your requirements.txt should contain:

strands-agents>=1.0.0
strands-agents-tools>=0.2.0
Create the project file structure

Your multi-agent system needs a clear folder structure. You'll have separate directories for agent definitions and tool functions.

  • Create the agents/ and tools/ directories by running:
mkdir agents tools
  • Create the empty files your project needs.

macOS

  • Create the placeholder files by running:
touch main.py agents/__init__.py tools/__init__.py

Windows

  • Create the placeholder files by running:
New-Item -ItemType File -Path main.py, agents\__init__.py, tools\__init__.py

What are __init__.py files?

These files tell Python that the agents/ and tools/ directories are importable packages. Without them, Python won't be able to import code from those folders.

  • Confirm your file structure by running:
ls

You should see agents, main.py, requirements.txt, and tools in the output.

  • Now open the project folder in VS Code so the file explorer shows your structure.
  • In VS Code, click File in the menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the techgear-support folder, and click Open.

Seeing a trust prompt?

VS Code may ask if you trust the authors of this folder. Click Yes, I trust the authors to continue. This is a standard security check for newly opened workspaces.

  • Open a new terminal in the project workspace by pressing Ctrl+` (macOS and Windows).
  • Reactivate your virtual environment in this new terminal.

macOS

source .venv/bin/activate

Windows

.venv\Scripts\activate

Confirm (.venv) appears in your prompt again. Your VS Code Explorer sidebar should show main.py, requirements.txt, and the agents/ and tools/ folders.

Don't see your files in the Explorer?

Make sure you opened the techgear-support folder itself, not the Desktop folder. The Explorer panel should show TECHGEAR-SUPPORT as the workspace root.

Help me get VS Code showing my project files.

Your project skeleton is ready. Next up, you'll write your first AI agent and verify that it can talk to Amazon Bedrock.

Build Your First Single Agent

Your project structure is in place and the Strands Agents SDK is installed. Now it's time to see it all come alive by building your first working agent.

Before wiring up multiple agents, you need to prove one thing: your local code can talk to Amazon Bedrock and get a response. In this step, you will create a custom tool, attach it to a single agent, and run a test query to confirm the connection works end-to-end.

In this step, get ready to:
  • Define @tool-decorated functions that an agent can call.
  • Create a Strands agent with a system prompt and custom tools.
  • Run the agent to verify Bedrock connectivity.
Define your billing tools

An AI agent is only as useful as the actions it can take. In the Strands Agents SDK, you give an agent capabilities by defining Python functions decorated with @tool.

The model reads each tool's docstring to decide when and how to use it. You will start with an order lookup tool that simulates fetching order data from a database.

  • Open tools/billing_tools.py in your editor and add the following code:
from strands import tool


@tool
def lookup_order(order_id: str) -> dict:
    """Look up an order by its ID and return order details.

    Args:
        order_id: The order ID to look up (e.g., ORD-12345)

    Returns:
        Dictionary containing order status, items, and shipping info
    """
    # Simulated order database
    orders = {
        "ORD-12345": {
            "status": "shipped",
            "items": ["Wireless Headphones", "USB-C Cable"],
            "total": "$89.99",
            "tracking": "1Z999AA10123456784",
            "estimated_delivery": "2026-05-28",
        },
        "ORD-67890": {
            "status": "processing",
            "items": ["Smart Watch Pro"],
            "total": "$299.99",
            "tracking": None,
            "estimated_delivery": "2026-06-02",
        },
    }
    if order_id in orders:
        return orders[order_id]
    return {"error": f"Order {order_id} not found. Please check the order ID."}

What does this code do?

The @tool decorator from Strands registers the function so the AI model knows it exists. The docstring acts as documentation that the model reads to decide when to call this tool.

The function uses a hardcoded dictionary to simulate a database. In a real system, you would replace this with an API call or database query.

Now add the second tool below the first. This one checks whether an order qualifies for a refund.

  • In the same tools/billing_tools.py file, add this function after lookup_order:
@tool
def check_refund_eligibility(order_id: str) -> dict:
    """Check if an order is eligible for a refund.

    Args:
        order_id: The order ID to check refund eligibility for

    Returns:
        Dictionary with eligibility status and reason
    """
    # Simulated refund policy data
    eligible_orders = {
        "ORD-12345": {
            "eligible": True,
            "reason": "Within 30-day return window",
            "refund_amount": "$89.99",
        },
        "ORD-67890": {
            "eligible": False,
            "reason": "Order has not shipped yet. You can cancel instead.",
        },
    }
    if order_id in eligible_orders:
        return eligible_orders[order_id]
    return {"eligible": False, "reason": "Order not found"}

What does this code do?

This tool gives the agent the ability to answer refund questions. The model will call check_refund_eligibility when a customer asks about returns, and lookup_order when they ask about order status.

The agent decides which tool to call based on the customer's question and the tool docstrings. You do not need to write routing logic yourself.

  • Save tools/billing_tools.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved tools/billing_tools.py with both tool functions.

ⓧ I'd like to double check the full code

from strands import tool


@tool
def lookup_order(order_id: str) -> dict:
    """Look up an order by its ID and return order details.

    Args:
        order_id: The order ID to look up (e.g., ORD-12345)

    Returns:
        Dictionary containing order status, items, and shipping info
    """
    orders = {
        "ORD-12345": {
            "status": "shipped",
            "items": ["Wireless Headphones", "USB-C Cable"],
            "total": "$89.99",
            "tracking": "1Z999AA10123456784",
            "estimated_delivery": "2026-05-28",
        },
        "ORD-67890": {
            "status": "processing",
            "items": ["Smart Watch Pro"],
            "total": "$299.99",
            "tracking": None,
            "estimated_delivery": "2026-06-02",
        },
    }
    if order_id in orders:
        return orders[order_id]
    return {"error": f"Order {order_id} not found. Please check the order ID."}


@tool
def check_refund_eligibility(order_id: str) -> dict:
    """Check if an order is eligible for a refund.

    Args:
        order_id: The order ID to check refund eligibility for

    Returns:
        Dictionary with eligibility status and reason
    """
    eligible_orders = {
        "ORD-12345": {
            "eligible": True,
            "reason": "Within 30-day return window",
            "refund_amount": "$89.99",
        },
        "ORD-67890": {
            "eligible": False,
            "reason": "Order has not shipped yet. You can cancel instead.",
        },
    }
    if order_id in eligible_orders:
        return eligible_orders[order_id]
    return {"eligible": False, "reason": "Order not found"}
Create a test agent with a system prompt

A Strands Agent combines a system prompt (personality and instructions) with tools (capabilities). The system prompt tells the model who it is and how to behave.

  • Open main.py and add the following code to create a single billing agent and test it:
from strands import Agent
from tools.billing_tools import lookup_order, check_refund_eligibility

# Create a billing agent with tools and a system prompt
billing_agent = Agent(
    name="billing_agent",
    system_prompt="""You are the Billing Support Agent for TechGear e-commerce.
Your expertise covers:
- Order status inquiries and tracking
- Refund eligibility and processing
- Payment issues and billing questions

Always use your tools to look up real order data. Be concise and helpful.""",
    tools=[lookup_order, check_refund_eligibility],
)

# Test the agent with a sample query
print("Testing Bedrock connectivity...")
print("=" * 40)
billing_agent("What is the status of order ORD-12345?")

What does this code do?

The Agent() constructor creates an agent backed by Amazon Bedrock with Claude Sonnet 4 (the default model provider). The system_prompt defines the agent's role and behavior rules.

When you call billing_agent("..."), the SDK sends the query to Bedrock along with the tool definitions. The model decides whether to call a tool, reads the result, and generates a natural language response that streams directly to your terminal.

  • Save main.py.

Seeing a red underline on the import?

If your editor shows an import error for tools.billing_tools, make sure you have an __init__.py file inside both the tools/ and agents/ directories. These files can be empty but they tell Python to treat the folders as packages.

help me fix my Python import error for tools.billing_tools

Run the agent to verify Bedrock connectivity

This is the moment of truth. Running the script sends a real request to Amazon Bedrock and proves your AWS credentials, model access, and agent code all work together.

  • In your terminal (with the virtual environment still active), run the test by executing:
python main.py

You should see the agent's response stream into your terminal in real time. The agent will call the lookup_order tool and respond with the order status, items, tracking number, and delivery estimate for ORD-12345.

What should I see?

The Strands SDK streams the agent's reasoning and response directly to your console. You will see the agent's answer appear word by word, describing the order status as "shipped" with a tracking number and estimated delivery date.

The first request may take a few seconds as the connection to Bedrock initializes. Subsequent calls will be faster.

Seeing an access denied or model error?

Check that you enabled model access for Claude Sonnet 4 in the Amazon Bedrock console in the us-west-2 region. Also verify your AWS credentials are configured by running aws sts get-caller-identity in your terminal.

help me fix my Amazon Bedrock access denied error when running a Strands agent

Your first agent is alive and talking to Bedrock. Next up, you will build out three specialized domain agents that each handle a different area of customer support.

Create Specialized Domain Agents

Your single-agent test confirmed that Amazon Bedrock is working. A single agent can answer general questions, but customer support has distinct domains: billing, technical issues, and product details.

In this step, you will build three specialized agents, each with its own tools and expertise. This is the foundation of multi-agent collaboration. Each agent knows its boundaries and can signal when a question belongs to a different specialist.

In this step, get ready to:
  • Build the Billing Agent with order and refund tools.
  • Build the Technical Support Agent with troubleshooting tools.
  • Build the Product Expert Agent with product catalog tools.
Build the Billing Agent with order and refund tools

You already created billing_tools.py with lookup_order and check_refund_eligibility in the previous step. Now you need an Agent that can use those tools.

The Strands Agents SDK Agent class takes three key parameters: a name for identification, a system_prompt that defines the agent's role and boundaries, and a tools list that gives it capabilities.

  • Create a new file called agents/specialists.py inside your techgear-support project folder.
  • Add the following code to define the Billing Agent:
from strands import Agent
from tools.billing_tools import lookup_order, check_refund_eligibility


billing_agent = Agent(
    name="billing_agent",
    system_prompt="""You are the Billing Support Agent for TechGear e-commerce.
Your expertise covers:
- Order status inquiries and tracking
- Refund eligibility and processing
- Payment issues and billing questions

Always use your tools to look up real order data. Be concise and helpful.
If a question is outside your expertise (technical issues, product details),
hand off to the appropriate specialist.""",
    tools=[lookup_order, check_refund_eligibility],
)

What does this code do?

The name parameter identifies this agent within the multi-agent system. Other agents will reference this name during handoffs.

The system_prompt tells the LLM what role to play and when to hand off. Notice the explicit boundary: "If a question is outside your expertise, hand off to the appropriate specialist." This teaches the agent to recognize its limits.

The tools list gives the agent access to the functions you decorated with @tool earlier. The agent can call these tools autonomously to answer questions.

  • Save agents/specialists.py.

You won't see a change yet. The next two substeps create the tools that the Technical Support and Product Expert agents need.

Build the Technical Support Agent with troubleshooting tools

The Technical Support Agent needs a diagnostic tool that can look up known issues and return structured troubleshooting steps.

  • Create a new file called tools/tech_tools.py and add the following diagnostic function:
from strands import tool


@tool
def diagnose_issue(product_name: str, symptom: str) -> dict:
    """Diagnose a technical issue with a product based on reported symptoms.

    Args:
        product_name: Name of the product experiencing issues
        symptom: Description of the problem or symptom

    Returns:
        Dictionary with diagnosis and recommended steps
    """
    knowledge_base = {
        "wireless headphones": {
            "no sound": {
                "diagnosis": "Audio connection issue",
                "steps": [
                    "1. Ensure headphones are charged (LED should be blue)",
                    "2. Forget the device in Bluetooth settings and re-pair",
                    "3. Check volume is not muted on both device and headphones",
                    "4. Try connecting to a different device to isolate the issue",
                ],
            },
            "won't connect": {
                "diagnosis": "Bluetooth pairing issue",
                "steps": [
                    "1. Hold power button for 10 seconds to enter pairing mode",
                    "2. Clear paired devices list on your phone/computer",
                    "3. Ensure headphones are within 3 feet of the device",
                    "4. Restart both the headphones and your device",
                ],
            },
        },
        "smart watch pro": {
            "won't charge": {
                "diagnosis": "Charging contact issue",
                "steps": [
                    "1. Clean charging contacts with a dry cloth",
                    "2. Ensure magnetic charger is properly aligned",
                    "3. Try a different power source (wall outlet vs USB)",
                    "4. Restart the watch by holding the side button for 15 seconds",
                ],
            },
        },
    }
    product_key = product_name.lower()
    symptom_key = symptom.lower()

    if product_key in knowledge_base:
        for known_symptom, solution in knowledge_base[product_key].items():
            if known_symptom in symptom_key:
                return solution
        return {
            "diagnosis": "Issue not in knowledge base",
            "steps": [
                "1. Try restarting the device",
                "2. Check for firmware updates",
                "3. If issue persists, we can arrange a replacement",
            ],
        }
    return {
        "diagnosis": "Product not recognized",
        "steps": ["Please provide the exact product name for accurate troubleshooting."],
    }

What does this code do?

The diagnose_issue function simulates a knowledge base lookup. It takes a product name and symptom, then returns a structured diagnosis with step-by-step troubleshooting instructions.

The @tool decorator registers this function so the agent can call it autonomously. The docstring is critical. It tells the LLM what the function does and how to call it.

If the product or symptom isn't in the knowledge base, the function returns generic troubleshooting steps rather than an error. This keeps the agent helpful even with unfamiliar issues.

  • Save tools/tech_tools.py.
  • Confirm the file exists by running:
ls tools/

You should see billing_tools.py and tech_tools.py listed in the output.

Don't see tech_tools.py?

Make sure you created the file inside the tools/ directory, not in the project root. The file path should be tools/tech_tools.py.

help me create tech_tools.py in the correct location

Now add the Technical Support Agent to agents/specialists.py.

  • Return to agents/specialists.py in your editor.
  • Add the tech_tools import at the top, below the existing import line. Then add the tech_support_agent definition below billing_agent:
from strands import Agent
from tools.billing_tools import lookup_order, check_refund_eligibility
from tools.tech_tools import diagnose_issue


billing_agent = Agent(
    name="billing_agent",
    system_prompt="""You are the Billing Support Agent for TechGear e-commerce.
Your expertise covers:
- Order status inquiries and tracking
- Refund eligibility and processing
- Payment issues and billing questions

Always use your tools to look up real order data. Be concise and helpful.
If a question is outside your expertise (technical issues, product details),
hand off to the appropriate specialist.""",
    tools=[lookup_order, check_refund_eligibility],
)

tech_support_agent = Agent(
    name="tech_support_agent",
    system_prompt="""You are the Technical Support Agent for TechGear e-commerce.
Your expertise covers:
- Troubleshooting product issues
- Setup and configuration guidance
- Firmware and connectivity problems

Always use your diagnostic tools to provide structured troubleshooting steps.
If a question is about billing, orders, or product purchasing,
hand off to the appropriate specialist.""",
    tools=[diagnose_issue],
)

What does this code do?

The tech_support_agent follows the same pattern as the billing agent. Its system_prompt defines a narrow domain (troubleshooting, setup, connectivity) and explicitly states when to hand off.

Each agent's tools list only includes functions relevant to its domain. This prevents agents from overstepping their expertise. The billing agent cannot diagnose hardware issues, and the tech agent cannot look up order status.

  • Save agents/specialists.py.

You won't see a change yet. The next substep adds the Product Expert and completes the full agent team.

Build the Product Expert Agent with product catalog tools

The Product Expert needs tools to look up product specifications and compare items side by side.

  • Create a new file called tools/product_tools.py and add the product information function:
from strands import tool


@tool
def get_product_info(product_name: str) -> dict:
    """Get detailed information about a product including features and specs.

    Args:
        product_name: Name of the product to look up

    Returns:
        Dictionary with product details, features, and pricing
    """
    catalog = {
        "wireless headphones": {
            "full_name": "ProSound Wireless Headphones X1",
            "price": "$79.99",
            "features": [
                "Active Noise Cancellation",
                "40-hour battery life",
                "Bluetooth 5.3",
                "Multi-device connection (up to 3)",
                "Foldable design with carrying case",
            ],
            "compatibility": ["iOS 15+", "Android 10+", "Windows 10+", "macOS 12+"],
            "warranty": "2 years",
        },
        "smart watch pro": {
            "full_name": "FitTrack Smart Watch Pro",
            "price": "$299.99",
            "features": [
                "Heart rate and SpO2 monitoring",
                "GPS tracking",
                "5 ATM water resistance",
                "7-day battery life",
                "AMOLED display",
            ],
            "compatibility": ["iOS 16+", "Android 11+"],
            "warranty": "1 year",
        },
        "usb-c cable": {
            "full_name": "PowerLink USB-C Cable (6ft)",
            "price": "$14.99",
            "features": [
                "100W Power Delivery",
                "USB 3.2 Gen 2 (10Gbps)",
                "Braided nylon construction",
                "Reversible connector",
            ],
            "compatibility": ["All USB-C devices"],
            "warranty": "Lifetime",
        },
    }
    product_key = product_name.lower()
    if product_key in catalog:
        return catalog[product_key]
    return {"error": f"Product '{product_name}' not found in catalog."}

What does this code do?

The get_product_info function simulates a product catalog lookup. It returns detailed specs including features, compatibility, pricing, and warranty information.

The case-insensitive matching (product_name.lower()) means the agent can call this tool regardless of how the customer phrases the product name. This makes the tool more robust in real conversations.

  • Below the get_product_info function in the same file, add the comparison tool:
@tool
def compare_products(product_a: str, product_b: str) -> dict:
    """Compare two products side by side.

    Args:
        product_a: First product name
        product_b: Second product name

    Returns:
        Dictionary with comparison details
    """
    catalog = {
        "wireless headphones": {"price": "$79.99", "category": "Audio", "rating": 4.5},
        "smart watch pro": {"price": "$299.99", "category": "Wearables", "rating": 4.7},
        "usb-c cable": {"price": "$14.99", "category": "Accessories", "rating": 4.8},
    }
    a_key = product_a.lower()
    b_key = product_b.lower()

    if a_key not in catalog or b_key not in catalog:
        return {"error": "One or both products not found in catalog."}

    return {
        product_a: catalog[a_key],
        product_b: catalog[b_key],
    }

What does this code do?

The compare_products function takes two product names and returns their key attributes side by side. This lets the agent help customers decide between products.

Notice that this tool uses a simplified catalog compared to get_product_info. Each tool is designed for a specific purpose. The comparison tool only needs high-level attributes, while the info tool returns full specifications.

  • Save tools/product_tools.py.
  • Confirm all tool files exist by running:
ls tools/

You should see billing_tools.py, tech_tools.py, and product_tools.py listed.

✔️ Awesome, I've got everything!

Great. Double check you've saved tools/product_tools.py with both functions.

ⓧ I'd like to double check the full code

from strands import tool


@tool
def get_product_info(product_name: str) -> dict:
    """Get detailed information about a product including features and specs.

    Args:
        product_name: Name of the product to look up

    Returns:
        Dictionary with product details, features, and pricing
    """
    catalog = {
        "wireless headphones": {
            "full_name": "ProSound Wireless Headphones X1",
            "price": "$79.99",
            "features": [
                "Active Noise Cancellation",
                "40-hour battery life",
                "Bluetooth 5.3",
                "Multi-device connection (up to 3)",
                "Foldable design with carrying case",
            ],
            "compatibility": ["iOS 15+", "Android 10+", "Windows 10+", "macOS 12+"],
            "warranty": "2 years",
        },
        "smart watch pro": {
            "full_name": "FitTrack Smart Watch Pro",
            "price": "$299.99",
            "features": [
                "Heart rate and SpO2 monitoring",
                "GPS tracking",
                "5 ATM water resistance",
                "7-day battery life",
                "AMOLED display",
            ],
            "compatibility": ["iOS 16+", "Android 11+"],
            "warranty": "1 year",
        },
        "usb-c cable": {
            "full_name": "PowerLink USB-C Cable (6ft)",
            "price": "$14.99",
            "features": [
                "100W Power Delivery",
                "USB 3.2 Gen 2 (10Gbps)",
                "Braided nylon construction",
                "Reversible connector",
            ],
            "compatibility": ["All USB-C devices"],
            "warranty": "Lifetime",
        },
    }
    product_key = product_name.lower()
    if product_key in catalog:
        return catalog[product_key]
    return {"error": f"Product '{product_name}' not found in catalog."}


@tool
def compare_products(product_a: str, product_b: str) -> dict:
    """Compare two products side by side.

    Args:
        product_a: First product name
        product_b: Second product name

    Returns:
        Dictionary with comparison details
    """
    catalog = {
        "wireless headphones": {"price": "$79.99", "category": "Audio", "rating": 4.5},
        "smart watch pro": {"price": "$299.99", "category": "Wearables", "rating": 4.7},
        "usb-c cable": {"price": "$14.99", "category": "Accessories", "rating": 4.8},
    }
    a_key = product_a.lower()
    b_key = product_b.lower()

    if a_key not in catalog or b_key not in catalog:
        return {"error": "One or both products not found in catalog."}

    return {
        product_a: catalog[a_key],
        product_b: catalog[b_key],
    }

Now complete the agent team by adding the Product Expert to agents/specialists.py.

  • Return to agents/specialists.py.
  • Add the product_tools import at the top and the product_expert_agent definition at the bottom. Your complete file should now look like this:
from strands import Agent
from tools.billing_tools import lookup_order, check_refund_eligibility
from tools.tech_tools import diagnose_issue
from tools.product_tools import get_product_info, compare_products


billing_agent = Agent(
    name="billing_agent",
    system_prompt="""You are the Billing Support Agent for TechGear e-commerce.
Your expertise covers:
- Order status inquiries and tracking
- Refund eligibility and processing
- Payment issues and billing questions

Always use your tools to look up real order data. Be concise and helpful.
If a question is outside your expertise (technical issues, product details),
hand off to the appropriate specialist.""",
    tools=[lookup_order, check_refund_eligibility],
)

tech_support_agent = Agent(
    name="tech_support_agent",
    system_prompt="""You are the Technical Support Agent for TechGear e-commerce.
Your expertise covers:
- Troubleshooting product issues
- Setup and configuration guidance
- Firmware and connectivity problems

Always use your diagnostic tools to provide structured troubleshooting steps.
If a question is about billing, orders, or product purchasing,
hand off to the appropriate specialist.""",
    tools=[diagnose_issue],
)

product_expert_agent = Agent(
    name="product_expert_agent",
    system_prompt="""You are the Product Expert Agent for TechGear e-commerce.
Your expertise covers:
- Product features, specifications, and pricing
- Product compatibility information
- Product comparisons and recommendations

Always use your tools to provide accurate product information.
If a question is about existing orders, billing, or technical issues,
hand off to the appropriate specialist.""",
    tools=[get_product_info, compare_products],
)

What does this code do?

The product_expert_agent completes the trio. It has two tools for handling product questions: one for detailed lookups and one for comparisons.

All three agents follow the same pattern. Each has a focused domain, explicit handoff instructions in its system prompt, and only the tools it needs. This separation is what makes multi-agent systems powerful. Instead of one agent trying to do everything, each specialist excels in its lane.

  • Save agents/specialists.py.
  • Verify the agents module loads correctly by running:
python -c "from agents.specialists import billing_agent, tech_support_agent, product_expert_agent; print('All 3 agents loaded successfully!')"

You should see All 3 agents loaded successfully! printed in the terminal.

Seeing a ModuleNotFoundError?

Make sure you are running the command from the techgear-support project root directory (not from inside agents/ or tools/). Python needs to find both the agents and tools folders from the current directory.

Also confirm your virtual environment is still active. You should see (.venv) at the start of your terminal prompt.

help me fix ModuleNotFoundError when importing my agents

✔️ Awesome, I've got everything!

Great. Double check you've saved agents/specialists.py with all three agents defined.

ⓧ I'd like to double check the full code

from strands import Agent
from tools.billing_tools import lookup_order, check_refund_eligibility
from tools.tech_tools import diagnose_issue
from tools.product_tools import get_product_info, compare_products


billing_agent = Agent(
    name="billing_agent",
    system_prompt="""You are the Billing Support Agent for TechGear e-commerce.
Your expertise covers:
- Order status inquiries and tracking
- Refund eligibility and processing
- Payment issues and billing questions

Always use your tools to look up real order data. Be concise and helpful.
If a question is outside your expertise (technical issues, product details),
hand off to the appropriate specialist.""",
    tools=[lookup_order, check_refund_eligibility],
)

tech_support_agent = Agent(
    name="tech_support_agent",
    system_prompt="""You are the Technical Support Agent for TechGear e-commerce.
Your expertise covers:
- Troubleshooting product issues
- Setup and configuration guidance
- Firmware and connectivity problems

Always use your diagnostic tools to provide structured troubleshooting steps.
If a question is about billing, orders, or product purchasing,
hand off to the appropriate specialist.""",
    tools=[diagnose_issue],
)

product_expert_agent = Agent(
    name="product_expert_agent",
    system_prompt="""You are the Product Expert Agent for TechGear e-commerce.
Your expertise covers:
- Product features, specifications, and pricing
- Product compatibility information
- Product comparisons and recommendations

Always use your tools to provide accurate product information.
If a question is about existing orders, billing, or technical issues,
hand off to the appropriate specialist.""",
    tools=[get_product_info, compare_products],
)

Your three specialist agents are ready. Next up, you will wire them together using the Swarm pattern so they can autonomously hand off queries to each other.

Orchestrate with the Swarm Pattern

In the previous step, you built three specialized agents with their own tools and system prompts. Each one can handle a slice of customer support on its own.

The real power of multi-agent systems comes from agents working together. When a customer asks about a refund AND wants to know product specs, a single agent struggles. The Swarm pattern solves this by letting agents autonomously hand off tasks to whichever specialist is best suited.

In this step, get ready to:
  • Import and configure the Swarm orchestrator in main.py.
  • Wire all three agents into the Swarm with an entry point and handoff limits.
  • Run the Swarm and observe autonomous agent handoffs in the terminal.
Import and configure the Swarm

The Strands Agents SDK provides a Swarm class that handles all orchestration logic. You give it a list of agents, and it automatically injects a handoff tool into each one so they can transfer control to a more suitable specialist.

  • Switch back to main.py in your editor.
  • Replace the entire file contents with the following imports:
import sys
from strands.multiagent import Swarm
from agents.specialists import billing_agent, tech_support_agent, product_expert_agent

What do these imports do?

  • sys is a standard library module used later for system-level operations.
  • Swarm is the multi-agent orchestrator from strands.multiagent that coordinates handoffs between agents.
  • The three agent objects you created in agents/specialists.py are imported so the Swarm can manage them.
  • Save main.py.

Seeing an import error?

Make sure your virtual environment is activated. The strands-agents package must be installed in the active environment.

help me fix my Strands import error

Wire all three agents into the Swarm

The Swarm needs to know which agents to coordinate, where to start routing queries, and when to stop if agents keep handing off endlessly.

  • Add the create_support_swarm() function below the imports in main.py:
def create_support_swarm():
    """Create the multi-agent customer support swarm."""
    swarm = Swarm(
        [billing_agent, tech_support_agent, product_expert_agent],
        entry_point=billing_agent,
        max_handoffs=10,
        max_iterations=10,
        execution_timeout=120.0,
        node_timeout=60.0,
    )
    return swarm

What does this code do?

  • Swarm([...]) takes a list of all agents the swarm can route queries to.
  • entry_point=billing_agent means the billing agent receives every incoming query first and decides whether to handle it or hand off.
  • max_handoffs=10 caps how many times agents can pass control to each other, preventing infinite loops.
  • max_iterations=10 limits total node executions across the swarm.
  • execution_timeout=120.0 gives the entire swarm 2 minutes to finish before timing out.
  • node_timeout=60.0 gives each individual agent 60 seconds per turn.

Why Swarm instead of Graph or Workflow?

The Strands SDK offers three multi-agent patterns. Graph uses a developer-defined DAG with fixed edges. Workflow runs a deterministic task pipeline in parallel.

Swarm is different because agents decide the routing themselves. For customer support, this is ideal. A billing question that turns into a product question gets routed automatically, without you coding every possible path.

  • Save main.py.

Seeing a name error for one of the agents?

Check that your import line matches exactly: from agents.specialists import billing_agent, tech_support_agent, product_expert_agent. The names must match the variables defined in agents/specialists.py.

help me debug my agent import issue

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py before moving on.

ⓧ I'd like to double check the full code

import sys
from strands.multiagent import Swarm
from agents.specialists import billing_agent, tech_support_agent, product_expert_agent


def create_support_swarm():
    """Create the multi-agent customer support swarm."""
    swarm = Swarm(
        [billing_agent, tech_support_agent, product_expert_agent],
        entry_point=billing_agent,
        max_handoffs=10,
        max_iterations=10,
        execution_timeout=120.0,
        node_timeout=60.0,
    )
    return swarm
Run the Swarm and observe autonomous handoffs

Your Swarm is configured. Now you will send it a query and watch it route the request to the correct agent.

  • Test the Swarm by running this command in your terminal:
python -c "from main import create_support_swarm; swarm = create_support_swarm(); result = swarm('What is the status of order ORD-12345?'); print(result.output); print([node.node_id for node in result.node_history])"
  • Wait for the response. This may take 10-30 seconds as the Swarm connects to Amazon Bedrock and processes the query.

What should I see?

You should see two things printed in your terminal. First, a natural language response about order ORD-12345 (status shipped, tracking number, estimated delivery). Second, a list of agent names showing which agents handled the query.

For a billing-only question like order status, you will likely see ['billing_agent'] in the node history. The billing agent handled it directly because it matched its expertise.

  • Try a query that triggers a handoff by running:
python -c "from main import create_support_swarm; swarm = create_support_swarm(); result = swarm('I ordered wireless headphones but they have no sound. Also what is my order status for ORD-12345?'); print(result.output); print([node.node_id for node in result.node_history])"

This query spans two domains. The node history should show multiple agents collaborating, such as ['billing_agent', 'tech_support_agent'] or a similar handoff chain.

What is a handoff?

When the Swarm starts, it gives the entry point agent (billing) the query plus a hidden handoff tool. If the billing agent decides the question is outside its expertise, it calls that tool to transfer control to a more suitable agent.

The receiving agent gets the full conversation context and can either answer or hand off again. This continues until an agent produces a final answer or the handoff limit is reached.

Getting a timeout or credential error?

  • Verify your AWS credentials are configured by running aws sts get-caller-identity in your terminal.
  • Confirm Claude Sonnet 4 model access is enabled in the Amazon Bedrock console for the us-west-2 region.
  • If the command takes longer than 2 minutes, the execution_timeout will trigger. Check your internet connection and try again.

help me fix my Swarm timeout or credential error

Your multi-agent swarm is alive. Next up, you will wrap it in an interactive chat interface so you can have a real conversation with the agent team.

Build the Interactive Chat Interface

Your Swarm is configured and the agents can hand off tasks to each other autonomously. The missing piece is a way for you to actually talk to the system.

In this step, you'll build a command-line chat interface that accepts your questions, routes them through the Swarm, and shows you which agents collaborated on each response.

In this step, get ready to:
  • Create a welcome banner and initialize the Swarm.
  • Build a chat loop that accepts input and displays agent responses.
  • Show handoff chains as agents collaborate on complex queries.
Add the welcome banner and Swarm initialization

The first thing a user sees when they launch your chatbot is the welcome banner. This prints instructions and tells them which agents are available.

  • In main.py, find the create_support_swarm() function you wrote in the previous step.
  • Below it, add the main() function that prints a welcome banner and initializes the Swarm:
def main():
    """Run the interactive multi-agent chatbot."""
    print("=" * 60)
    print("  TechGear Customer Support - Multi-Agent Chatbot")
    print("  Powered by Amazon Bedrock + Strands Agents SDK")
    print("=" * 60)
    print()
    print("You're connected to our AI support team:")
    print("  - Billing Agent: Orders, refunds, payments")
    print("  - Tech Support Agent: Troubleshooting, setup help")
    print("  - Product Expert Agent: Features, specs, comparisons")
    print()
    print("Type 'quit' to exit.")
    print("-" * 60)

    # Initialize the Swarm once so it persists across all queries
    swarm = create_support_swarm()

What does this code do?

The banner orients the user by listing the three specialist agents and their domains. This is important because the user's phrasing determines which agent the Swarm routes to.

Calling create_support_swarm() once and storing it in swarm means the same Swarm instance handles every query in the session.

Build the chat loop with handoff display

The chat loop is where the real interaction happens. It reads user input, passes it to the Swarm, and prints which agents collaborated on the answer.

  • Still inside the main() function, add the chat loop directly below the swarm = create_support_swarm() line:
    while True:
        print()
        user_input = input("You: ").strip()

        # Skip empty input, exit on quit commands
        if not user_input:
            continue
        if user_input.lower() in ("quit", "exit", "q"):
            print("\nThank you for contacting TechGear support. Goodbye!")
            break

        print("\n[Swarm processing your request...]")

        try:
            # Send the user's message to the Swarm for routing
            result = swarm(user_input)
            print(f"\nAgent: {result.output}")

            # Show which agents were consulted if handoffs occurred
            if result.node_history:
                agents_used = [node.node_id for node in result.node_history]
                if len(agents_used) > 1:
                    print(f"\n  [Agents consulted: {' -> '.join(agents_used)}]")
        except Exception as e:
            print(f"\n[Error]: {e}")
            print("Please try rephrasing your question.")

What does this code do?

  • input("You: ") pauses the loop and waits for the user to type a query.
  • swarm(user_input) sends the message to the Swarm, which autonomously decides which agent should handle it.
  • result.node_history tracks every agent the Swarm consulted. When more than one agent participated, the handoff chain prints so you can see the collaboration path.
  • The try/except block catches network errors or model timeouts without crashing the whole session.
  • Below the main() function (outside of it, at the top level), add the entry point guard:
if __name__ == "__main__":
    main()

Why use if __name__ == "__main__"?

This guard ensures main() only runs when you execute the script directly. If another file imports from main.py, the chatbot won't launch unexpectedly.

  • Save main.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before running the chatbot.

ⓧ I'd like to double check the full code

import sys
from strands.multiagent import Swarm
from agents.specialists import billing_agent, tech_support_agent, product_expert_agent


def create_support_swarm():
    """Create the multi-agent customer support swarm."""
    swarm = Swarm(
        [billing_agent, tech_support_agent, product_expert_agent],
        entry_point=billing_agent,
        max_handoffs=10,
        max_iterations=10,
        execution_timeout=120.0,
        node_timeout=60.0,
    )
    return swarm


def main():
    """Run the interactive multi-agent chatbot."""
    print("=" * 60)
    print("  TechGear Customer Support - Multi-Agent Chatbot")
    print("  Powered by Amazon Bedrock + Strands Agents SDK")
    print("=" * 60)
    print()
    print("You're connected to our AI support team:")
    print("  - Billing Agent: Orders, refunds, payments")
    print("  - Tech Support Agent: Troubleshooting, setup help")
    print("  - Product Expert Agent: Features, specs, comparisons")
    print()
    print("Type 'quit' to exit.")
    print("-" * 60)

    swarm = create_support_swarm()

    while True:
        print()
        user_input = input("You: ").strip()

        if not user_input:
            continue
        if user_input.lower() in ("quit", "exit", "q"):
            print("\nThank you for contacting TechGear support. Goodbye!")
            break

        print("\n[Swarm processing your request...]")

        try:
            result = swarm(user_input)
            print(f"\nAgent: {result.output}")

            if result.node_history:
                agents_used = [node.node_id for node in result.node_history]
                if len(agents_used) > 1:
                    print(f"\n  [Agents consulted: {' -> '.join(agents_used)}]")
        except Exception as e:
            print(f"\n[Error]: {e}")
            print("Please try rephrasing your question.")


if __name__ == "__main__":
    main()
Run the chatbot and test agent handoffs

Time to see the multi-agent system in action. You'll send queries that trigger different agents and observe how the Swarm routes between specialists.

  • Make sure your virtual environment is still active in the terminal.
  • Launch the chatbot by running:
python main.py

You should see the TechGear welcome banner with the three agents listed and a You: prompt waiting for input.

  • Try a billing query by typing:
What's the status of order ORD-12345?

The Billing Agent should respond with the order status, items, and tracking number from your mock data.

  • Try a query that spans multiple domains to trigger a handoff:
I ordered wireless headphones but they won't connect to my phone. Can I get a refund?

This query touches both technical support and billing. You should see an [Agents consulted: ...] line showing the handoff chain between agents.

  • Type quit to exit the chatbot when you're done testing.

Seeing a timeout or connection error?

  • Confirm your AWS credentials are still valid by running aws sts get-caller-identity in a separate terminal.
  • Make sure Claude Sonnet 4 model access is enabled in the Amazon Bedrock console for the us-west-2 region.
  • Check that your virtual environment is active. You should see (.venv) at the start of your terminal prompt.

My chatbot throws an error when I type a query. Help me debug.

What is happening behind the scenes?

When you type a message, the Swarm sends it to the entry point agent (Billing). That agent either handles the query directly using its tools, or decides to hand off to a better-suited specialist.

The Swarm coordinates these handoffs autonomously. No routing rules are hardcoded. Each agent's system prompt tells it what it's good at and when to defer. The LLM reasoning handles the rest.

Your multi-agent chatbot is fully operational. You've built a team of AI specialists that collaborate autonomously to handle customer questions across billing, tech support, and product expertise. Next, take on the Secret Mission to add a Quality Assurance agent that reviews responses before they reach the customer.

Secret mission

Add a Quality Assurance Review Agent

Your multi-agent system routes queries to the right specialist. But how do you know the responses are actually good? In production, teams add quality gates that review agent output before it reaches the customer. Add a QA agent and hook system that intercepts every response for review.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and does not create any persistent AWS resources. Amazon Bedrock charges stop the moment you stop running the script, so there are no ongoing costs to worry about.

Decide whether to keep your project files for future reference, or delete them entirely.

Resources you used:

  • techgear-support project directory (all Python files and virtual environment).
  • AWS CLI credentials stored in ~/.aws/ (configured during setup).

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your multi-agent chatbot or want to reference the code later.

  • Your code lives in the techgear-support folder on your Desktop. You can re-run the chatbot anytime with python main.py.
  • Amazon Bedrock only charges when you actively run the script and send queries. No charges accumulate while the script is stopped.
  • Your AWS credentials remain configured in ~/.aws/ for future AWS projects.

Pause - I'll come back to this later

Shut down any running processes to free up terminal space. Your files stay on disk for when you return.

  • If the chatbot is still running, type quit to exit the chat loop.
  • Deactivate your virtual environment by running:
deactivate
  • Your project files and virtual environment remain in the techgear-support folder. To resume later, navigate back to the folder, activate the virtual environment, and run the script again.

Delete - I don't want to use this again

Remove all project files and optionally clear AWS credentials if you no longer need them.

  • If the chatbot is still running, type quit to exit.
  • Deactivate the virtual environment by running:
deactivate
  • Delete the project directory by running:

macOS

cd ~/Desktop
rm -rf techgear-support

Windows

cd ~/Desktop
Remove-Item -Recurse -Force techgear-support
  • If you created AWS credentials specifically for this project and don't plan to use AWS again, remove them by running:

macOS

rm -rf ~/.aws/credentials ~/.aws/config

Windows

Remove-Item -Recurse -Force ~\.aws\credentials, ~\.aws\config

Keep credentials if you use AWS elsewhere

Only delete the ~/.aws/ files if these credentials were created solely for this project. If you use AWS for other work, leave them in place.

Nice Work!

Nice Work!

You just built a multi-agent customer support chatbot powered by Amazon Bedrock and the Strands Agents SDK. Your AI support team autonomously routes queries to domain specialists and hands off between agents in real time.

You've learned how to:

  • Built three specialized AI agents with custom @tool-decorated functions and focused system prompts.
  • Orchestrated autonomous collaboration using the Swarm pattern where agents decide routing and share context without central control.
  • Created an interactive chat interface that displays real-time agent handoffs as the system routes queries to domain experts.
  • Added a Quality Assurance agent with hooks for response validation using HookProvider and AfterNodeCallEvent.

Ready to quiz yourself?