🗄️ Inventory Manager

class-based stateful tools via Strands + Bedrock

Concept

Grouping related tools in a class lets them share ONE resource — a single self.products store — instead of each tool opening its own. A module-level instance keeps the state alive across requests, so an update made in one call is visible to the next.

Theory & Concepts

In this Inventory Manager demo, the user can ask the agent to check and update stock levels. When a request hits the /stock endpoint, the agent uses tools wrapped inside an InventoryTools class.

Unlike standalone functions, these class-based tools share a single persistent state—the self.products dictionary (simulating a database). This allows the agent to update a product's stock using the update_stock tool and immediately verify the new quantity using the check_stock tool within the same conversation turn.

Because the _inventory instance is created at the module level in stock_agent.py, this state persists across different browser requests. You can ask to update the stock in one request, and verify it in another, demonstrating how stateful tools allow agents to manage shared resources efficiently without reconnecting to a database on every tool call.

Request flow

Browser request POST /stock
stock_agent.py manage() Agent
Bedrock check_stock or update_stock on shared InventoryTools instance
answer Browser

Code flow

flowchart TD A["Browser<br/>stock request"] -->|"POST /stock"| B["app.py<br/>stock_route"] B -->|"request"| C["stock_agent.py<br/>manage"] C -->|"request + tools"| D["Bedrock<br/>Nova Pro"] D -->|"tool call: check_stock"| E["InventoryTools<br/>check_stock"] E -->|"qty + price"| D D -->|"tool call: update_stock"| F["InventoryTools<br/>update_stock"] F -->|"updated confirmation"| D E & F -->|"shared self.products"| G["Module-level<br/>_inventory instance"] D -->|"final answer"| C C -->|"final answer"| B B -->|"JSON result"| A

Backend

stock_agent.py
"""Module 2 demo — class-based tools (the shared-resource pattern).

Grouping related tools in a class lets them share ONE resource (here, a single
``self.products`` store) instead of each tool opening its own. A module-level
instance keeps the state alive across requests, so an update made in one call
is visible to the next — check, update, then re-check and watch it persist.
"""

from strands import Agent, tool
from strands.models.bedrock import BedrockModel

from config import MODEL_ID, agent_text


class InventoryTools:
    def __init__(self):
        # Shared resource: all tools access the same data store.
        # In production: self.db = connect_to_database()   <-- opened ONCE
        self.products = {
            "PROD-123": {"name": "Wireless Mouse", "quantity": 15, "price": 29.99},
            "PROD-456": {"name": "USB-C Hub", "quantity": 0, "price": 49.99},
            "PROD-789": {"name": "Mechanical Keyboard", "quantity": 8, "price": 89.99},
        }

    @tool
    def check_stock(self, product_id: str) -> str:
        """Check product stock level.

        Args:
            product_id: The product ID to check
        """
        product = self.products.get(product_id)
        if not product:
            return f"Product {product_id} not found"
        return f"{product['name']}: {product['quantity']} units at ${product['price']}"

    @tool
    def update_stock(self, product_id: str, quantity: int) -> str:
        """Update product stock quantity.

        Args:
            product_id: The product ID to update
            quantity: New quantity to set
        """
        if product_id in self.products:
            self.products[product_id]["quantity"] = quantity
            return f"Updated {product_id} to {quantity} units"
        return f"Product {product_id} not found"


# One shared instance — state persists across web requests.
_inventory = InventoryTools()


def manage(question: str) -> str:
    """Answer a stock request with the class-based, stateful inventory tools."""
    agent = Agent(
        model=BedrockModel(model_id=MODEL_ID),
        tools=[_inventory.check_stock, _inventory.update_stock],
        callback_handler=None,
    )
    return agent_text(agent(question))