Build an Inventory Lookup Agent

Build a Bedrock Agent that queries a DynamoDB inventory table via natural language.

Introduction

30 Second Summary

Imagine walking into a warehouse and asking "Do we have laptop stands in stock?" and getting an instant, accurate answer. AI agents make this possible by connecting natural language questions to real databases.

In this project, you will build an inventory lookup agent using Amazon Bedrock. The agent understands plain English questions and queries a product database to return stock levels and pricing.

What You'll Build

A warehouse inventory assistant powered by Amazon Bedrock that interprets natural language queries and retrieves product data from a DynamoDB table through a Lambda function.

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

  • A DynamoDB table loaded with sample inventory data for 8 products.
  • A Lambda function that queries products by name or lists the full inventory.
  • A Bedrock Agent configured with an action group and OpenAPI schema to orchestrate inventory lookups.
  • A Python script that invokes the agent programmatically from your command line.
  • Secret Mission: extend the agent to handle restock commands that update inventory quantities through natural language.

Are there any prerequisites?

You need an AWS account with Bedrock model access enabled, the AWS CLI configured on your machine, and Python 3 with pip installed. Step 1 walks you through verifying each of these.

Set Up the Inventory Database

Your tools are set up and ready. Now it's time to build the foundation your agent will query: a warehouse inventory database.

Amazon DynamoDB is a serverless NoSQL database that stores items as flexible key-value documents. In this step, you'll create a table and load it with sample product data using a Python script.

In this step, get ready to:
  • Create a DynamoDB table with a Python script.
  • Load 8 sample inventory items into the table.
  • Verify the data is accessible from the CLI.
Create the inventory table and load sample data

Your Bedrock Agent needs data to work with. You'll write a Python script that creates a DynamoDB table and populates it with 8 product records representing a warehouse inventory.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir inventory-agent
cd inventory-agent
  • Create the setup script file by running:
touch setup_inventory.py
  • Confirm the file exists by running:
ls

You should see setup_inventory.py in the output.

  • Open setup_inventory.py in a text editor of your choice.
  • Copy the following code and paste it into setup_inventory.py. This first section sets up the DynamoDB client and defines the table creation logic:
import boto3
import time

REGION = "us-east-1"
TABLE_NAME = "InventoryTable"

dynamodb = boto3.client("dynamodb", region_name=REGION)


def create_table():
    try:
        dynamodb.create_table(
            TableName=TABLE_NAME,
            KeySchema=[{"AttributeName": "product_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "product_id", "AttributeType": "S"}
            ],
            BillingMode="PROVISIONED",
            ProvisionedThroughput={
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5,
            },
        )
        print(f"Creating table {TABLE_NAME}...")
        waiter = dynamodb.get_waiter("table_exists")
        waiter.wait(TableName=TABLE_NAME)
        print(f"Table {TABLE_NAME} is ready.")
    except dynamodb.exceptions.ResourceInUseException:
        print(f"Table {TABLE_NAME} already exists.")

What does this code do?

The create_table() function calls the DynamoDB API to create a table named InventoryTable with a partition key of product_id (String type).

The waiter pauses the script until AWS confirms the table is active. If the table already exists, the except block catches the error and moves on gracefully.

  • Add the following code below the create_table() function. This defines the sample data and the main entry point:

def load_sample_data():
    items = [
        {"product_id": "001", "product_name": "Laptop Stand", "quantity": 45, "price": 29.99, "category": "Accessories"},
        {"product_id": "002", "product_name": "Wireless Headphones", "quantity": 120, "price": 79.99, "category": "Audio"},
        {"product_id": "003", "product_name": "Mechanical Keyboard", "quantity": 30, "price": 149.99, "category": "Peripherals"},
        {"product_id": "004", "product_name": "USB-C Hub", "quantity": 75, "price": 49.99, "category": "Accessories"},
        {"product_id": "005", "product_name": "Monitor Arm", "quantity": 20, "price": 89.99, "category": "Accessories"},
        {"product_id": "006", "product_name": "Webcam HD", "quantity": 60, "price": 59.99, "category": "Peripherals"},
        {"product_id": "007", "product_name": "Desk Lamp", "quantity": 0, "price": 34.99, "category": "Office"},
        {"product_id": "008", "product_name": "Ergonomic Mouse", "quantity": 90, "price": 39.99, "category": "Peripherals"},
    ]

    table = boto3.resource("dynamodb", region_name=REGION).Table(TABLE_NAME)
    for item in items:
        table.put_item(Item=item)
        print(f"  Loaded: {item['product_name']} (qty: {item['quantity']})")

    print(f"\nLoaded {len(items)} items into {TABLE_NAME}.")


if __name__ == "__main__":
    create_table()
    load_sample_data()

What does this code do?

The load_sample_data() function inserts 8 products into the table using put_item(). Each product has a name, quantity, price, and category.

The if __name__ == "__main__" block runs both functions in sequence when you execute the script directly.

  • Save setup_inventory.py.

✔️ Awesome, I've got everything!

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

ⓧ I'd like to double check the full code

import boto3
import time

REGION = "us-east-1"
TABLE_NAME = "InventoryTable"

dynamodb = boto3.client("dynamodb", region_name=REGION)


def create_table():
    try:
        dynamodb.create_table(
            TableName=TABLE_NAME,
            KeySchema=[{"AttributeName": "product_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "product_id", "AttributeType": "S"}
            ],
            BillingMode="PROVISIONED",
            ProvisionedThroughput={
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5,
            },
        )
        print(f"Creating table {TABLE_NAME}...")
        waiter = dynamodb.get_waiter("table_exists")
        waiter.wait(TableName=TABLE_NAME)
        print(f"Table {TABLE_NAME} is ready.")
    except dynamodb.exceptions.ResourceInUseException:
        print(f"Table {TABLE_NAME} already exists.")


def load_sample_data():
    items = [
        {"product_id": "001", "product_name": "Laptop Stand", "quantity": 45, "price": 29.99, "category": "Accessories"},
        {"product_id": "002", "product_name": "Wireless Headphones", "quantity": 120, "price": 79.99, "category": "Audio"},
        {"product_id": "003", "product_name": "Mechanical Keyboard", "quantity": 30, "price": 149.99, "category": "Peripherals"},
        {"product_id": "004", "product_name": "USB-C Hub", "quantity": 75, "price": 49.99, "category": "Accessories"},
        {"product_id": "005", "product_name": "Monitor Arm", "quantity": 20, "price": 89.99, "category": "Accessories"},
        {"product_id": "006", "product_name": "Webcam HD", "quantity": 60, "price": 59.99, "category": "Peripherals"},
        {"product_id": "007", "product_name": "Desk Lamp", "quantity": 0, "price": 34.99, "category": "Office"},
        {"product_id": "008", "product_name": "Ergonomic Mouse", "quantity": 90, "price": 39.99, "category": "Peripherals"},
    ]

    table = boto3.resource("dynamodb", region_name=REGION).Table(TABLE_NAME)
    for item in items:
        table.put_item(Item=item)
        print(f"  Loaded: {item['product_name']} (qty: {item['quantity']})")

    print(f"\nLoaded {len(items)} items into {TABLE_NAME}.")


if __name__ == "__main__":
    create_table()
    load_sample_data()
  • Run the script by executing this command in your terminal:
python3 setup_inventory.py

You should see output confirming the table was created and each product was loaded, ending with "Loaded 8 items into InventoryTable."

Seeing an error?

  • If you see a credentials error, confirm your AWS CLI is configured by running aws sts get-caller-identity.
  • If you see ModuleNotFoundError: No module named 'boto3', install it with pip install boto3.
  • If you see a region error, make sure you are using us-east-1 and your AWS CLI default region is set correctly.

Ask for help: Why is my setup_inventory.py script failing?

Verify the table data

Now that the script has run, you can confirm the data is in DynamoDB by scanning the table directly from the CLI.

  • Scan the table to view all items by running:
aws dynamodb scan --table-name InventoryTable --region us-east-1

You should see JSON output containing all 8 items with their product names, quantities, prices, and categories. The output includes a Count field showing 8 and a ScannedCount of 8.

What is a table scan?

A scan reads every item in a DynamoDB table and returns them all. It's useful for verifying data, but in production you'd use queries with filters for efficiency.

The JSON output uses DynamoDB's type notation. Strings appear as {"S": "value"} and numbers as {"N": "value"}.

Not seeing 8 items?

  • If you see 0 items, your script may not have run successfully. Check the terminal output from the previous step for errors.
  • If the table doesn't exist, run python3 setup_inventory.py again and check for error messages.
  • Make sure the --region flag matches the region in your script (us-east-1).

Ask for help: Why is my DynamoDB scan not returning data?

Your inventory database is loaded and ready to query. Next, you'll build the Lambda function that your agent calls to look up products from this table.

Create the Lambda Function

Your DynamoDB table is loaded with sample inventory data. Now you need a way for the agent to actually query that data.

In this step, you will create an AWS Lambda function that accepts requests from the Bedrock Agent, queries the InventoryTable, and returns structured results. The function handles two operations: looking up a product by name and listing all products.

In this step, get ready to:
  • Create a Lambda function with the inventory lookup code.
  • Grant the function permission to read from DynamoDB.
  • Test the function with a sample event.
Create the Lambda function in the console

Lambda functions are small pieces of code that run on demand without managing servers. Your Bedrock Agent will call this function whenever it needs inventory data.

  • In the AWS Management Console, search for Lambda in the top search bar and select it.
  • Make sure your region is set to us-east-1 in the top-right dropdown.
  • Click Create function.
  • Select Author from scratch.
  • For Function name, enter InventoryLookupFunction.
  • For Runtime, choose Python 3.12.
  • Leave the Permissions section at its default (Create a new role with basic Lambda permissions).
  • Click Create function at the bottom of the page.

Lambda creates a new function with a default lambda_function.py file open in the code editor.

  • In the Code source pane, select all the existing code in lambda_function.py and delete it.
  • Replace it with the following code by pasting it into the editor:
import json
import boto3
from boto3.dynamodb.conditions import Attr

TABLE_NAME = "InventoryTable"
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(TABLE_NAME)


def lambda_handler(event, context):
    api_path = event.get("apiPath", "")
    parameters = event.get("parameters", [])

    if api_path == "/get-product":
        product_name = None
        for param in parameters:
            if param["name"] == "product_name":
                product_name = param["value"]
                break

        if not product_name:
            body = json.dumps({"error": "product_name parameter is required"})
        else:
            response = table.scan(
                FilterExpression=Attr("product_name").contains(product_name)
            )
            items = response.get("Items", [])
            if items:
                results = []
                for item in items:
                    results.append({
                        "product_name": item["product_name"],
                        "quantity": int(item["quantity"]),
                        "price": float(item["price"]),
                        "category": item["category"],
                    })
                body = json.dumps({"products": results})
            else:
                body = json.dumps({"message": f"No products found matching '{product_name}'"})

    elif api_path == "/list-products":
        response = table.scan()
        items = response.get("Items", [])
        results = []
        for item in items:
            results.append({
                "product_name": item["product_name"],
                "quantity": int(item["quantity"]),
                "price": float(item["price"]),
                "category": item["category"],
            })
        body = json.dumps({"products": results, "total_count": len(results)})

    else:
        body = json.dumps({"error": f"Unknown API path: {api_path}"})

    return {
        "messageVersion": "1.0",
        "response": {
            "actionGroup": event.get("actionGroup", ""),
            "apiPath": api_path,
            "httpMethod": event.get("httpMethod", "GET"),
            "httpStatusCode": 200,
            "responseBody": {"application/json": {"body": body}},
        },
    }

What does this code do?

This Lambda function handles two API paths that the Bedrock Agent will call:

  • /get-product scans the DynamoDB table for items whose product_name contains the search term. This enables partial matching, so searching for "laptop" finds "Laptop Stand".
  • /list-products performs a full table scan and returns every item with its name, quantity, price, and category.
  • The response format (messageVersion, responseBody) is the structure that Bedrock Agents expects from action group Lambda functions.
  • Click Deploy in the code editor to save your changes.
  • You should see a green banner confirming the function was updated successfully.

Don't see the Deploy button?

Look for the Deploy button directly above the code editor. If you still cannot find it, make sure you are on the Code tab (not the Configuration or Test tab).

Help me find the Deploy button in the Lambda console code editor.

Add DynamoDB read permissions to the Lambda role

The Lambda function needs permission to read from your InventoryTable. By default, the execution role only has CloudWatch Logs permissions.

  • In your Lambda function's page, click the Configuration tab.
  • Click Permissions in the left sidebar.
  • Under Execution role, click the role name link. This opens the IAM console in a new tab.
  • On the role's page, click Add permissions.
  • Choose Attach policies.
  • In the search bar, type AmazonDynamoDBReadOnlyAccess and press Enter.
  • Select the checkbox next to AmazonDynamoDBReadOnlyAccess.
  • Click Add permissions.

The policy should now appear under Permissions policies for the role.

Why AmazonDynamoDBReadOnlyAccess?

This AWS managed policy grants permission to read from any DynamoDB table in your account. For the base project, your function only needs to scan and read items.

In a production environment, you would scope this down to only the specific table ARN. For learning purposes, the managed policy keeps things simple.

Can't find the role name link?

Make sure you are on the Configuration tab, then Permissions in the left sidebar. The role name is a clickable blue link under Execution role. It opens the IAM console in a new browser tab.

Help me find the execution role link in my Lambda function.

Test the Lambda function

Before connecting the function to the Bedrock Agent, verify it returns inventory data on its own.

  • Return to the Lambda console tab with your InventoryLookupFunction.
  • Click the Test tab.
  • Under Test event, choose Create new event.
  • For Event name, enter TestGetProduct.
  • Replace the default JSON in the Event JSON section with the following:
{
  "apiPath": "/get-product",
  "httpMethod": "GET",
  "parameters": [
    {
      "name": "product_name",
      "value": "Laptop Stand"
    }
  ],
  "actionGroup": "InventoryActions"
}
  • Click Test.

What should I see?

Under Execution result, expand Details. You should see a response containing Laptop Stand with a quantity of 45 and a price of 29.99.

The response is wrapped in the Bedrock Agent response format with messageVersion set to 1.0 and httpStatusCode set to 200.

Seeing an AccessDeniedException?

If the error mentions DynamoDB access denied, the AmazonDynamoDBReadOnlyAccess policy may not have attached correctly. Go back to the IAM console, open the role, and verify the policy appears under Permissions policies. IAM changes take effect immediately, so you can re-run the test right away.

Help me fix this DynamoDB access denied error in my Lambda function.

✔️ Awesome, I've got everything!

Great. Double check you have saved and deployed your function code.

ⓧ I'd like to double check the full code

Here is the complete lambda_function.py code for comparison:

import json
import boto3
from boto3.dynamodb.conditions import Attr

TABLE_NAME = "InventoryTable"
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(TABLE_NAME)


def lambda_handler(event, context):
    api_path = event.get("apiPath", "")
    parameters = event.get("parameters", [])

    if api_path == "/get-product":
        product_name = None
        for param in parameters:
            if param["name"] == "product_name":
                product_name = param["value"]
                break

        if not product_name:
            body = json.dumps({"error": "product_name parameter is required"})
        else:
            response = table.scan(
                FilterExpression=Attr("product_name").contains(product_name)
            )
            items = response.get("Items", [])
            if items:
                results = []
                for item in items:
                    results.append({
                        "product_name": item["product_name"],
                        "quantity": int(item["quantity"]),
                        "price": float(item["price"]),
                        "category": item["category"],
                    })
                body = json.dumps({"products": results})
            else:
                body = json.dumps({"message": f"No products found matching '{product_name}'"})

    elif api_path == "/list-products":
        response = table.scan()
        items = response.get("Items", [])
        results = []
        for item in items:
            results.append({
                "product_name": item["product_name"],
                "quantity": int(item["quantity"]),
                "price": float(item["price"]),
                "category": item["category"],
            })
        body = json.dumps({"products": results, "total_count": len(results)})

    else:
        body = json.dumps({"error": f"Unknown API path: {api_path}"})

    return {
        "messageVersion": "1.0",
        "response": {
            "actionGroup": event.get("actionGroup", ""),
            "apiPath": api_path,
            "httpMethod": event.get("httpMethod", "GET"),
            "httpStatusCode": 200,
            "responseBody": {"application/json": {"body": body}},
        },
    }

Your Lambda function is deployed and verified. Next up, you will create a Bedrock Agent that uses this function to answer natural language inventory questions.

Create the Bedrock Agent

You have a Lambda function that can query your inventory database. Now you need something that can understand natural language questions and figure out which function to call.

That's what an Amazon Bedrock Agent does. It takes a user's question, interprets the intent using a foundation model, and routes the request to the correct backend function. In this step, you'll create the agent, connect it to your Lambda function, and give it permission to call that function.

In this step, get ready to:
  • Create a Bedrock Agent with a foundation model and natural language instructions.
  • Add an action group with an OpenAPI schema that describes the agent's available operations.
  • Grant the agent permission to invoke your Lambda function.
Create the agent in the Bedrock console

The agent needs a foundation model to understand language and a set of instructions that define its personality and purpose. You'll configure both during creation.

  • Return to the AWS Management Console in your browser.
  • In the search bar at the top, type Bedrock and select Amazon Bedrock.
  • In the left navigation pane, under Builder tools, choose Agents.
  • Choose Create agent.
  • For Name, enter InventoryAgent.
  • Choose Create. The Agent builder pane opens.

Now configure the agent's model and instructions in the Agent builder.

  • In the Agent details section, for Agent resource role, select Create and use a new service role.
  • For Select model, choose Claude 3 Haiku (or any available agent-optimized model in the dropdown).
  • In the Instructions for the Agent section, paste the following text:
You are a warehouse inventory assistant. You help users check product availability and look up inventory information. When a user asks about a product, use the inventory lookup function to get current stock levels. Always provide clear, helpful responses with product names and quantities.
  • Choose Save.

What do these instructions do?

The instructions tell the foundation model how to behave. The model reads them on every invocation to understand its role, tone, and what tools it has available.

Without clear instructions, the model might try to answer inventory questions from its training data instead of calling your Lambda function.

What is a service role?

A service role is an IAM role that AWS creates on your behalf. It gives the Bedrock Agent permission to call the foundation model. You'll add more permissions to this role later in this step.

Add an action group with an OpenAPI schema

An action group tells the agent what operations it can perform. You define these operations using an OpenAPI schema, which is a standard format for describing API endpoints, parameters, and responses.

  • Choose the Action groups tab.
  • In Action groups, choose Add.
  • For Enter Action group name, enter InventoryActions.
  • In Action group type, select Define with API schemas.
  • In Action group invocation, choose Select an existing Lambda function.
  • In Select Lambda function, select InventoryLookupFunction.
  • In Action group schema, select Define via in-line schema editor.
  • In the In-line OpenAPI schema text box, replace the existing schema with the following YAML:
openapi: 3.0.0
info:
  title: Inventory API
  version: 1.0.0
  description: API to look up warehouse inventory.
paths:
  /get-product:
    get:
      summary: Gets product details by name.
      description: Looks up a product in the inventory by name and returns stock level, price, and category.
      operationId: getProduct
      parameters:
        - name: product_name
          in: query
          description: The name of the product to look up.
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Product details retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  products:
                    type: array
                    items:
                      type: object
                      properties:
                        product_name:
                          type: string
                        quantity:
                          type: integer
                        price:
                          type: number
                        category:
                          type: string
  /list-products:
    get:
      summary: Lists all products in inventory.
      description: Returns all products currently in the warehouse inventory.
      operationId: listProducts
      responses:
        '200':
          description: All products retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  products:
                    type: array
                    items:
                      type: object
                      properties:
                        product_name:
                          type: string
                        quantity:
                          type: integer
                        price:
                          type: number
                        category:
                          type: string
                  total_count:
                    type: integer

What does this schema do?

The OpenAPI schema is a contract between the agent and your Lambda function. It tells the agent exactly which endpoints exist, what parameters they accept, and what responses they return.

When a user says "Do we have laptop stands?", the agent reads this schema, decides /get-product is the right endpoint, extracts "laptop stands" as the product_name parameter, and calls your Lambda function with that structured request.

  • Choose Create to save your action group.
  • Choose Save to save your changes.
  • Choose Prepare to prepare the agent.

You should see a success banner confirming the agent was prepared.

Don't see the Prepare button?

Make sure you clicked Save first. The Prepare button only becomes active after saving the agent configuration.

help me prepare my Bedrock agent

Add Lambda invoke permission to the agent's service role

The agent's service role currently only has permission to call the foundation model. You need to add an inline policy that allows it to invoke your Lambda function.

  • In the Agent overview section, under Permissions, click the IAM service role link. This opens the role in the IAM console.
  • In the IAM console, choose the Permissions tab.
  • Choose Add permissions, then select Create inline policy.
  • Choose JSON and paste the following policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:[[REGION=\"us-east-1\"]]:[[ACCOUNT_ID=\"your AWS account ID\"]]:function:InventoryLookupFunction"
        }
    ]
}

Where do I find my account ID?

Your 12-digit AWS account ID appears in the top-right corner of the console when you click your username. You can also find it in the Lambda function's ARN from the previous step.

  • Choose Next.
  • For the policy name, enter BedrockAgentLambdaInvoke.
  • Choose Create policy.

You should see the new BedrockAgentLambdaInvoke policy listed under the role's permissions.

Why does the agent need this permission?

AWS services operate on a least-privilege model. The agent's service role was only given permission to call the foundation model when it was auto-created. Without this explicit policy, the agent would understand the user's question but fail when it tries to call your Lambda function to get the actual answer.

Seeing an access denied error later?

Double check that the Resource ARN in your policy matches your Lambda function exactly. The region, account ID, and function name must all be correct.

help me fix my Bedrock agent Lambda permission

Your agent is fully configured and prepared. It has a foundation model for understanding language, an action group that maps to your Lambda function, and the IAM permissions to invoke it. Next up, you'll test it with real queries and invoke it from Python.

Test and Invoke the Agent

Your agent is fully configured with its instructions, action group, and permissions. Now it's time to see it in action.

In this step, you'll chat with the agent directly in the Bedrock console, deploy it with a version and alias, then invoke it programmatically from a Python script.

In this step, get ready to:
  • Test the agent with natural language queries in the Bedrock console.
  • Create a version and alias to deploy the agent.
  • Invoke the agent programmatically using a Python script.
Test the agent in the Bedrock console

The Bedrock console has a built-in Test window where you can send natural language messages to your agent and see its responses in real time.

  • In the Bedrock console, open your InventoryAgent from the Agents list.
  • Choose Test at the top of the page to open the Test panel on the right.
  • In the Alias dropdown, select TestAlias: Working draft.
  • Type the following message and choose Run:
What products do you have?
  • Wait for the agent to respond. You should see a list of inventory items with product names and quantities.
  • Try a second query to test the product-specific lookup:
Do we have laptop stands in stock?
  • Confirm the agent responds with details about Laptop Stands, including a quantity of 45.

What is happening behind the scenes?

When you send a message, the agent's foundation model interprets your intent. It decides which action group operation to call (/list-products or /get-product), invokes the Lambda function, then formats the database results into a natural language answer.

Choose Show trace to see each reasoning step the model takes, including which API path it selected and the parameters it passed.

Agent not returning inventory data?

If the agent responds with a generic message instead of inventory data, make sure you chose Prepare after saving the action group in the previous step. The agent must be prepared before testing.

If you see a permissions error in the trace, confirm that the BedrockAgentLambdaInvoke inline policy is attached to the agent's service role and that the Lambda ARN matches your function.

Help me debug why my Bedrock Agent isn't calling my Lambda function.

Deploy the agent with a version and alias

To invoke the agent programmatically, you need to deploy it. Deploying creates an immutable version snapshot and an alias that points to it.

  • On your InventoryAgent page, choose Create Alias.
  • For Alias name, enter v1.
  • Under Associate a version, select Create a new version and associate it to this alias.
  • Choose Create alias.

What are versions and aliases?

A version is an immutable snapshot of your agent's configuration at a point in time. An alias is a pointer to a specific version. Your application calls the alias, so you can update the underlying version without changing your code.

Bedrock automatically creates a new version when you create an alias. The alias then points to that version.

  • After the alias is created, note the Agent ID shown in the Agent overview section of your agent's page.
  • Click into the v1 alias you just created and note the Alias ID displayed there.

Copy both IDs now

You'll paste the Agent ID and Alias ID into your Python script in the next substep. Keep this browser tab open or paste them into a temporary note.

Invoke the agent from a Python script

With the agent deployed, you can call it from any application using the boto3 SDK. You'll create a script that sends a prompt and prints the streamed response.

  • In your terminal, create a new file called invoke_agent.py in the same folder as your other project files. Start by adding the imports and configuration:
import boto3
import logging

from botocore.exceptions import ClientError

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

AGENT_ID = "[[AGENT_ID="your-agent-id-here"]]"
ALIAS_ID = "[[ALIAS_ID="your-alias-id-here"]]"
REGION = "us-east-1"

What does this code do?

The script imports boto3 (the AWS SDK) and sets up logging so you can see the agent's reasoning trace in your terminal.

The three constants at the top configure which agent to call. Replace AGENT_ID and ALIAS_ID with the values you copied from the console.

  • Below the configuration, add the function that communicates with the agent:
def invoke_agent(client, agent_id, alias_id, prompt, session_id):
    response = client.invoke_agent(
        agentId=agent_id,
        agentAliasId=alias_id,
        enableTrace=True,
        sessionId=session_id,
        inputText=prompt,
        streamingConfigurations={
            "applyGuardrailInterval": 20,
            "streamFinalResponse": False,
        },
    )
    completion = ""
    for event in response.get("completion"):
        if "chunk" in event:
            chunk = event["chunk"]
            completion += chunk["bytes"].decode()
        if "trace" in event:
            trace_event = event.get("trace")
            trace = trace_event["trace"]
            for key, value in trace.items():
                logging.info("%s: %s", key, value)

    return completion

What does this function do?

The invoke_agent function sends your prompt to the Bedrock Agent and collects the streamed response. Each chunk of text is decoded and appended to the completion string.

The enableTrace=True parameter logs the agent's reasoning steps (which action group it chose, what parameters it passed) so you can debug if needed.

  • At the bottom of invoke_agent.py, add the main execution block:
if __name__ == "__main__":
    client = boto3.client(
        service_name="bedrock-agent-runtime",
        region_name=REGION,
    )

    session_id = "inventory-session-001"
    prompt = "What products do you have in stock?"

    try:
        result = invoke_agent(client, AGENT_ID, ALIAS_ID, prompt, session_id)
        print(f"\nAgent response:\n{result}")
    except ClientError as e:
        print(f"Client error: {str(e)}")
        logger.error("Client error: %s", str(e))

What does this code do?

The main block creates a bedrock-agent-runtime client, which is the specific AWS service for invoking agents. It then calls invoke_agent with a test prompt and prints the result.

The session_id groups related messages into a conversation. The agent can use session context to maintain continuity across multiple prompts in the same session.

  • Save invoke_agent.py.
  • Run the script by executing this command:
python invoke_agent.py
  • You should see logging output showing the agent's trace, followed by a natural language response listing your inventory products with names and quantities.

Seeing a client error or empty response?

If you see a ResourceNotFoundException, double-check that your AGENT_ID and ALIAS_ID values match the ones in the Bedrock console. These are alphanumeric strings, not the agent name or alias name.

If you see an access denied error, confirm that the AWS CLI credentials you're using have permission to call bedrock:InvokeAgent on the agent resource.

Help me debug why my invoke_agent.py script is failing.

✔️ Awesome, I've got everything!

Great. Double check you've saved invoke_agent.py and confirmed the agent returned inventory data in your terminal.

ⓧ I'd like to double check the full code

Here is the complete invoke_agent.py file for comparison:

import boto3
import logging

from botocore.exceptions import ClientError

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

AGENT_ID = "your-agent-id-here"
ALIAS_ID = "your-alias-id-here"
REGION = "us-east-1"


def invoke_agent(client, agent_id, alias_id, prompt, session_id):
    response = client.invoke_agent(
        agentId=agent_id,
        agentAliasId=alias_id,
        enableTrace=True,
        sessionId=session_id,
        inputText=prompt,
        streamingConfigurations={
            "applyGuardrailInterval": 20,
            "streamFinalResponse": False,
        },
    )
    completion = ""
    for event in response.get("completion"):
        if "chunk" in event:
            chunk = event["chunk"]
            completion += chunk["bytes"].decode()
        if "trace" in event:
            trace_event = event.get("trace")
            trace = trace_event["trace"]
            for key, value in trace.items():
                logging.info("%s: %s", key, value)

    return completion


if __name__ == "__main__":
    client = boto3.client(
        service_name="bedrock-agent-runtime",
        region_name=REGION,
    )

    session_id = "inventory-session-001"
    prompt = "What products do you have in stock?"

    try:
        result = invoke_agent(client, AGENT_ID, ALIAS_ID, prompt, session_id)
        print(f"\nAgent response:\n{result}")
    except ClientError as e:
        print(f"Client error: {str(e)}")
        logger.error("Client error: %s", str(e))

Your agent is working end-to-end. It understands natural language, calls a Lambda function to query DynamoDB, and returns structured inventory data. Next up, you'll extend the agent with a brand-new capability in the Secret Mission.

Secret mission

Add a Restock Action

Your agent can read inventory data, but a real warehouse assistant needs to update stock levels too. In this secret mission, you will extend the agent to handle write operations. You will modify the Lambda function, update the OpenAPI schema, and adjust IAM permissions so the agent can restock products through natural language commands.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses DynamoDB and Lambda within the AWS Free Tier, and the Bedrock Agent has no charge to keep. You only pay for model tokens when you invoke the agent.

Resources you used:

  • DynamoDB table InventoryTable (free tier, no ongoing cost).
  • Lambda function InventoryLookupFunction (free tier, no ongoing cost).
  • Bedrock Agent InventoryAgent (no charge unless invoked).
  • IAM roles (agent service role and Lambda execution role, no cost).
  • Local Python files (setup_inventory.py, lambda_function.py, invoke_agent.py).

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your agent or building on this project.

  • DynamoDB and Lambda are within AWS Free Tier limits. There are no ongoing charges for these resources.
  • The Bedrock Agent only incurs model token costs when you invoke it.
  • Your local Python files remain on your computer for future reference.

Pause - I'll come back to this later

Since all resources are within free tier or have no idle charges, there is nothing to pause. Your setup stays dormant until you invoke the agent again.

  • Leave all cloud resources as they are. No costs accumulate while they sit idle.
  • When you return, you can immediately invoke the agent from the console or your Python script.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below to fully clean up.

Delete the Bedrock Agent
  • Open the Amazon Bedrock console.
  • Select Agents from the left navigation pane.
  • Select the option button next to InventoryAgent.
  • A dialog box appears. Type delete in the input field and choose Delete.
Delete the Lambda function
Delete the DynamoDB table
  • Delete the table by running this command:
aws dynamodb delete-table --table-name InventoryTable --region us-east-1
Delete the IAM roles
  • Open the IAM console at https://console.aws.amazon.com/iam/.
  • In the navigation pane, choose Roles.
  • Select the agent service role (it starts with AmazonBedrockExecutionRoleForAgents_).
  • Choose Delete and confirm the deletion.
  • Repeat for the Lambda execution role (it starts with InventoryLookupFunction-role-).
Delete local project files
  • Remove your local Python files by running:
rm setup_inventory.py lambda_function.py invoke_agent.py

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully functional AI agent that bridges natural language and structured database queries using Amazon Bedrock.

You've learned how to:

  • Built a DynamoDB table and loaded it with sample inventory data using a Python script.
  • Created a Lambda function and connected it to a Bedrock Agent through an action group defined by an OpenAPI schema.
  • Tested the agent in the console and invoked it programmatically from Python using boto3.
  • Secret Mission: Extended the agent to perform write operations by adding a restock action that updates inventory quantities through natural language commands.

Ready to quiz yourself?