📊 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
Code flow
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))