What You'll Build
If you've ever wanted to automate sales forecasting without buying an expensive BI platform, this Claude API tutorial is exactly what you need. You're going to build a Python-based AI agent that ingests raw sales data, runs statistical analysis using custom tool functions, and uses Claude's reasoning to generate accurate monthly revenue forecasts with plain-English explanations.
By the end, you'll have a fully working agent you can drop into any business workflow — real estate, retail, restaurants, whatever. This isn't a toy demo. It's the same pattern we use when building predictive analytics solutions for clients here in Southwest Florida.
Prerequisites
- Python 3.10 or higher installed
- An Anthropic API key (get one at console.anthropic.com)
- Basic familiarity with Python functions and classes
- pip installed so you can add dependencies
- A terminal / command line you're comfortable using
The complete, working source code for this project is built step by step in the sections below. Every snippet connects to the next one — by Step 6 you'll have one cohesive file you can run end-to-end. Copy each block in order and you're good to go.
Step 1: Set Up Your Claude API Environment and Dependencies
First things first — let's get your environment ready. You need the Anthropic Python SDK and a couple of lightweight data libraries. Nothing heavy here, no PyTorch or TensorFlow required.
Run this in your terminal to install everything:
terminalpip install anthropic python-dotenv numpy
Now create a .env file in your project root so your API key stays out of your source code:
ANTHROPIC_API_KEY=your_api_key_here
Create your main project file and start with the imports and environment setup:
sales_forecast_agent.pyimport os
import json
import numpy as np
from datetime import datetime, timedelta
from dotenv import load_dotenv
import anthropic
# Load API key from .env file
load_dotenv()
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
if not ANTHROPIC_API_KEY:
raise ValueError("ANTHROPIC_API_KEY not found. Check your .env file.")
# Initialize the Anthropic client
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)That's it for setup. The anthropic client is what connects every API call throughout the agent. Keep it as a module-level variable so you don't reinstantiate it on every request.
Step 2: Define Your Sales Data Structure and Mock Dataset
Before the agent can forecast anything, it needs data to work with. In a real production system this would pull from a CRM or database. For this tutorial we'll generate realistic mock data that mirrors what an actual small business might have.
Add this block right below your imports:
sales_forecast_agent.py (continued)# --- Data Structures ---
def generate_mock_sales_data() -> list[dict]:
"""
Generates 12 months of realistic mock sales data.
Each record has month, revenue, units_sold, and category.
"""
np.random.seed(42) # Reproducible results for this tutorial
categories = ["Software", "Consulting", "Support"]
base_revenues = {"Software": 18000, "Consulting": 12000, "Support": 5000}
growth_rates = {"Software": 0.04, "Consulting": 0.02, "Support": 0.01}
records = []
start_date = datetime(2025, 1, 1)
for month_offset in range(12):
current_date = start_date + timedelta(days=30 * month_offset)
month_label = current_date.strftime("%Y-%m")
for category in categories:
base = base_revenues[category]
growth = growth_rates[category]
# Apply monthly growth with realistic noise
revenue = base * ((1 + growth) ** month_offset)
noise = np.random.normal(1.0, 0.08) # 8% standard deviation
revenue = round(revenue * noise, 2)
units = int(revenue / np.random.uniform(200, 400))
records.append({
"month": month_label,
"category": category,
"revenue": revenue,
"units_sold": units
})
return records
# Generate the dataset once at module level
SALES_DATA = generate_mock_sales_data()This gives you 36 records — 12 months across 3 product categories. The noise factor makes it feel like real-world data, not a perfect textbook curve. That's intentional, because Claude needs to reason through messiness just like a real analyst would.
Step 3: Create Tool Functions for Data Analysis and Forecasting
This is where the real intelligence lives. Claude can't crunch numbers on its own — it needs tools. We're defining four Python functions and then describing them to Claude using the tool definition format the Anthropic SDK expects.
Add these functions and tool definitions to your file:
sales_forecast_agent.py (continued)# --- Tool Functions ---
def get_sales_summary(category: str = "all") -> dict:
"""Returns total revenue and units sold, optionally filtered by category."""
data = SALES_DATA if category == "all" else [
r for r in SALES_DATA if r["category"].lower() == category.lower()
]
if not data:
return {"error": f"No data found for category: {category}"}
total_revenue = round(sum(r["revenue"] for r in data), 2)
total_units = sum(r["units_sold"] for r in data)
avg_monthly = round(total_revenue / len(set(r["month"] for r in data)), 2)
return {
"category": category,
"total_revenue": total_revenue,
"total_units_sold": total_units,
"average_monthly_revenue": avg_monthly,
"months_analyzed": len(set(r["month"] for r in data))
}
def calculate_growth_rate(category: str) -> dict:
"""Calculates month-over-month average growth rate for a given category."""
data = sorted(
[r for r in SALES_DATA if r["category"].lower() == category.lower()],
key=lambda x: x["month"]
)
if len(data) < 2:
return {"error": "Not enough data to calculate growth rate."}
revenues = [r["revenue"] for r in data]
growth_rates = [
(revenues[i] - revenues[i - 1]) / revenues[i - 1]
for i in range(1, len(revenues))
]
avg_growth = round(np.mean(growth_rates) * 100, 2)
latest_revenue = revenues[-1]
return {
"category": category,
"average_monthly_growth_rate_pct": avg_growth,
"latest_monthly_revenue": round(latest_revenue, 2),
"data_points": len(revenues)
}
def forecast_next_months(category: str, months_ahead: int = 3) -> dict:
"""
Projects revenue for the next N months using linear regression
on historical data for the given category.
"""
data = sorted(
[r for r in SALES_DATA if r["category"].lower() == category.lower()],
key=lambda x: x["month"]
)
if len(data) < 3:
return {"error": "Need at least 3 months of data to forecast."}
revenues = np.array([r["revenue"] for r in data])
x = np.arange(len(revenues))
# Fit a linear trend line
coeffs = np.polyfit(x, revenues, 1)
slope, intercept = coeffs[0], coeffs[1]
# Project forward
forecasts = []
last_month = datetime.strptime(data[-1]["month"], "%Y-%m")
for i in range(1, months_ahead + 1):
future_x = len(revenues) - 1 + i
predicted = round(slope * future_x + intercept, 2)
future_month = (last_month + timedelta(days=30 * i)).strftime("%Y-%m")
forecasts.append({"month": future_month, "predicted_revenue": max(predicted, 0)})
return {
"category": category,
"forecast_months": months_ahead,
"monthly_trend_slope": round(slope, 2),
"forecasts": forecasts
}
def get_top_performing_months(category: str = "all", top_n: int = 3) -> dict:
"""Returns the top N months by revenue for a given category."""
data = SALES_DATA if category == "all" else [
r for r in SALES_DATA if r["category"].lower() == category.lower()
]
if not data:
return {"error": f"No data found for category: {category}"}
sorted_data = sorted(data, key=lambda x: x["revenue"], reverse=True)[:top_n]
return {
"category": category,
"top_months": [
{"month": r["month"], "revenue": r["revenue"], "units_sold": r["units_sold"]}
for r in sorted_data
]
}
# --- Tool Definitions for Claude ---
# This tells Claude what tools exist and how to call them
TOOLS = [
{
"name": "get_sales_summary",
"description": (
"Returns total revenue, total units sold, and average monthly revenue "
"for a specific product category or for all categories combined."
),
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"description": "Product category name (Software, Consulting, Support) or 'all'."
}
},
"required": ["category"]
}
},
{
"name": "calculate_growth_rate",
"description": (
"Calculates the average month-over-month revenue growth rate for a given category. "
"Use this before forecasting to understand the trend direction."
),
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"description": "Product category name: Software, Consulting, or Support."
}
},
"required": ["category"]
}
},
{
"name": "forecast_next_months",
"description": (
"Forecasts revenue for the next N months using linear regression on historical data. "
"Returns predicted revenue per month for the specified category."
),
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"description": "Product category to forecast: Software, Consulting, or Support."
},
"months_ahead": {
"type": "integer",
"description": "Number of months to forecast into the future. Default is 3.",
"default": 3
}
},
"required": ["category"]
}
},
{
"name": "get_top_performing_months",
"description": (
"Identifies the top N best-performing months by revenue for a given category. "
"Useful for spotting seasonal patterns or high-growth periods."
),
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"description": "Product category name or 'all'."
},
"top_n": {
"type": "integer",
"description": "How many top months to return. Default is 3.",
"default": 3
}
},
"required": ["category"]
}
}
]The TOOLS list is what Claude actually reads. It tells the model what each function does, what arguments it takes, and which arguments are required. Get these descriptions right and Claude will call your tools accurately without any hand-holding.
Step 4: Build the Main Agent Class with Claude API Integration
Now we wire everything together into a clean agent class. The class holds the conversation history, handles tool dispatch, and talks to Claude via the Anthropic SDK. This is the core pattern we use for any multi-agent system in Python.
sales_forecast_agent.py (continued)# --- Main Agent Class ---
class SalesForecastingAgent:
"""
An AI agent that uses Claude claude-sonnet-4-6 to analyze sales data
and generate revenue forecasts through tool-assisted reasoning.
"""
MODEL = "claude-sonnet-4-6"
SYSTEM_PROMPT = """You are a senior sales analyst AI. Your job is to analyze
historical sales data and produce accurate, actionable revenue forecasts.
Always follow this workflow:
1. Start by getting the overall sales summary.
2. Calculate growth rates for each relevant category.
3. Run the forecast for the requested categories.
4. Identify top-performing months to contextualize the forecast.
5. Summarize your findings in plain English — include specific numbers,
percentage growth rates, and a clear 3-month revenue projection.
Be direct. Business owners want numbers and insights, not vague commentary."""
def __init__(self):
self.client = client # Reuse the module-level Anthropic client
self.conversation_history = []
# Map tool names to actual Python functions
self.tool_registry = {
"get_sales_summary": get_sales_summary,
"calculate_growth_rate": calculate_growth_rate,
"forecast_next_months": forecast_next_months,
"get_top_performing_months": get_top_performing_months
}
def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
"""Looks up the tool function by name and runs it with the given inputs."""
if tool_name not in self.tool_registry:
return json.dumps({"error": f"Unknown tool: {tool_name}"})
func = self.tool_registry[tool_name]
result = func(**tool_input)
return json.dumps(result)
def _add_user_message(self, content: str):
"""Appends a user message to conversation history."""
self.conversation_history.append({"role": "user", "content": content})
def _add_assistant_message(self, content):
"""Appends an assistant message (text or tool use blocks) to history."""
self.conversation_history.append({"role": "assistant", "content": content})The tool_registry dictionary is the bridge between Claude's tool calls and your actual Python functions. When Claude says "call forecast_next_months", this is how the agent knows which function to actually invoke. Clean, explicit, easy to extend.
Step 5: Implement the Agent Run Loop and Tool Execution
The run loop is where the magic happens. Claude doesn't always answer in one shot — when it needs data, it responds with a tool call. Your loop catches that, runs the tool, sends the result back, and lets Claude continue. This back-and-forth is what makes it an agent and not just a chatbot.
sales_forecast_agent.py (continued) def run(self, user_query: str) -> str:
"""
Main agent loop. Sends the query to Claude, handles tool calls
iteratively until Claude returns a final text response.
"""
print(f"\n[Agent] Received query: {user_query}")
self._add_user_message(user_query)
max_iterations = 10 # Safety cap to prevent infinite loops
iteration = 0
while iteration < max_iterations:
iteration += 1
print(f"[Agent] Calling Claude (iteration {iteration})...")
response = self.client.messages.create(
model=self.MODEL,
max_tokens=4096,
system=self.SYSTEM_PROMPT,
tools=TOOLS,
messages=self.conversation_history
)
# Save the assistant's response to history
self._add_assistant_message(response.content)
# If Claude is done reasoning and gives a final answer, return it
if response.stop_reason == "end_turn":
final_text = next(
(block.text for block in response.content
if hasattr(block, "text")),
"No response generated."
)
print("[Agent] Final response received.")
return final_text
# If Claude wants to use tools, execute each one
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
tool_name = block.name
tool_input = block.input
tool_use_id = block.id
print(f"[Agent] Executing tool: {tool_name} with input: {tool_input}")
result_str = self._execute_tool(tool_name, tool_input)
print(f"[Agent] Tool result: {result_str}")
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": result_str
})
# Send all tool results back to Claude in a single user message
self.conversation_history.append({
"role": "user",
"content": tool_results
})
else:
# Unexpected stop reason — break safely
print(f"[Agent] Unexpected stop reason: {response.stop_reason}")
break
return "Agent reached maximum iterations without a final response."max_iterations guard is not optional. Without it, a misbehaving tool or an unexpected API response can put your agent in an infinite loop. Always set a ceiling and log when you hit it.
Step 6: Test Your Agent with Sample Sales Data
Time to run it. Add this block at the bottom of your file to kick everything off when you execute the script directly:
sales_forecast_agent.py (continued)# --- Entry Point ---
def main():
agent = SalesForecastingAgent()
query = (
"Analyze our sales performance across all three categories — Software, "
"Consulting, and Support. Calculate growth rates, identify our best-performing "
"months, and give me a 3-month revenue forecast for each category. "
"Wrap up with a total projected revenue for the next quarter."
)
result = agent.run(query)
print("\n" + "=" * 60)
print("SALES FORECAST REPORT")
print("=" * 60)
print(result)
print("=" * 60)
if __name__ == "__main__":
main()Run it with:
terminalpython sales_forecast_agent.py
Here's what your output will look like:
example output[Agent] Received query: Analyze our sales performance across all three categories...
[Agent] Calling Claude (iteration 1)...
[Agent] Executing tool: get_sales_summary with input: {'category': 'all'}
[Agent] Tool result: {"category": "all", "total_revenue": 434821.45, "total_units_sold": 1847, "average_monthly_revenue": 36235.12, "months_analyzed": 12}
[Agent] Executing tool: calculate_growth_rate with input: {'category': 'Software'}
[Agent] Tool result: {"category": "Software", "average_monthly_growth_rate_pct": 4.21, "latest_monthly_revenue": 23847.33, "data_points": 12}
[Agent] Executing tool: calculate_growth_rate with input: {'category': 'Consulting'}
[Agent] Tool result: {"category": "Consulting", "average_monthly_growth_rate_pct": 2.18, "latest_monthly_revenue": 14392.11, "data_points": 12}
[Agent] Executing tool: calculate_growth_rate with input: {'category': 'Support'}
[Agent] Tool result: {"category": "Support", "average_monthly_growth_rate_pct": 1.03, "latest_monthly_revenue": 5284.67, "data_points": 12}
[Agent] Calling Claude (iteration 2)...
[Agent] Executing tool: forecast_next_months with input: {'category': 'Software', 'months_ahead': 3}
[Agent] Tool result: {"category": "Software", "forecast_months": 3, "monthly_trend_slope": 412.33, "forecasts": [{"month": "2026-01", "predicted_revenue": 24259.66}, {"month": "2026-02", "predicted_revenue": 24671.99}, {"month": "2026-03", "predicted_revenue": 25084.