📊 Sales Report Agent

multi-tool planning via Strands + Bedrock

Concept

"Pull last quarter's sales data and email a summary" is three tasks: query, analyse, send. The agent is given three small single-purpose tools and works out the order itself — that's planning. Nobody hard-coded the sequence; the agentic loop figures it out from context.

Theory & Concepts

The Sales Report Agent demo showcases one of the most powerful capabilities of agentic systems: Autonomous Multi-Tool Planning. When the user submits a request like "Pull last quarter's sales data, calculate the metrics, and email a summary to the team," they are asking for a complex, multi-step workflow. Instead of hardcoding this sequence, we give the agent three simple tools.

In sales_agent.py, we define get_sales_data, analyze_sales, and send_email. The agent is responsible for figuring out the order. Using dynamic planning, the Bedrock agent first realizes it needs data, so it calls get_sales_data. Once it observes the returned revenue and deal count, it deduces that the next logical step is to call analyze_sales to calculate the averages. Finally, armed with the analyzed metrics, it invokes send_email to complete the user's request.

This dynamic chaining is revolutionary because the developer never wrote a script that says "do A, then B, then C." The LLM acts as the orchestrator. If get_sales_data had returned an error, the agent could potentially adapt its plan. By providing single-purpose tools and letting the agent reason about the workflow, we can handle complex, ambiguous user requests without writing rigid state machines.

Request flow

Browser request POST /sales
sales_agent.py report() Agent plans: get_sales_data
analyze_sales send_email
Bedrock phrases result Browser

Code flow

flowchart TD A["Browser<br/>sales request"] -->|"POST /sales"| B["app.py<br/>sales_route"] B -->|"request"| C["sales_agent.py<br/>report"] C -->|"request + tools"| D["Bedrock<br/>Nova Pro"] D -->|"tool call: get_sales_data"| E["get_sales_data<br/>quarter"] E -->|"revenue, deals"| D D -->|"tool call: analyze_sales"| F["analyze_sales<br/>revenue, deals"] F -->|"metrics string"| D D -->|"tool call: send_email"| G["send_email<br/>to, subject, body"] G -->|"sent confirmation"| D D -->|"final answer"| C C -->|"final answer"| B B -->|"JSON result"| A

Backend

sales_agent.py
"""Module 2 demo — multi-tool planning: a sales report agent.

"Pull last quarter's sales data and email a summary to the team" is three
tasks: query, analyse, send. The agent is given three small, single-purpose
tools and works out the order itself — that is planning.
"""

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

from config import MODEL_ID, agent_text


@tool
def get_sales_data(quarter: str) -> dict:
    """Retrieve sales data for a specific quarter."""
    # Mock data — swap in a real CRM/warehouse query to take this further.
    return {"revenue": 1250000, "deals": 47, "quarter": quarter}


@tool
def analyze_sales(revenue: int, deals: int, quarter: str) -> str:
    """Calculate key metrics from sales data."""
    avg_deal = revenue / deals
    return f"Q{quarter}: ${revenue:,} revenue, {deals} deals, ${avg_deal:,.0f} avg deal size"


@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email message."""
    # Mock send — no real email is dispatched; wire up an SMTP/SES client here.
    return f"Email sent to {to}"


def report(question: str) -> str:
    """Answer a sales request, letting the agent chain its three tools."""
    agent = Agent(
        model=BedrockModel(model_id=MODEL_ID),
        tools=[get_sales_data, analyze_sales, send_email],
        callback_handler=None,
    )
    return agent_text(agent(question))