What You'll Build
If you're a real estate agent or broker, you already know the pain: you get 40 leads from Zillow on a Monday morning and have no idea who to call first. This tutorial shows you how to build a Claude API-powered AI agent in Python that automatically scores each lead by buyer intent, budget fit, and purchase urgency — so you always know exactly who deserves your attention right now.
By the end of this walkthrough, you'll have a fully working lead scoring agent that reads raw lead data, calls three specialized tool functions, and outputs a clean score with an explanation for each lead. The whole thing runs in a single Python script using the Anthropic SDK.
The complete, working code for this project is built up step-by-step in the sections below. Every snippet is production-ready and uses the
claude-sonnet-4-6 model. If you want to jump straight to a specific part, use the section headings as your guide.
Prerequisites
- Python 3.10 or higher installed on your machine
- An Anthropic API key — get one at console.anthropic.com
- The
anthropicPython package (pip install anthropic) - Basic familiarity with Python classes and dictionaries
- A
.envfile or environment variable set forANTHROPIC_API_KEY
Full Source Code
All the code you need lives in two files: lead_scoring_agent.py (the agent class and tool logic) and run_scoring.py (the entry point). Both files are built up completely in the steps below — nothing is left as pseudocode.
Step 1: Set Up the Claude API and Anthropic SDK
First, install the Anthropic SDK if you haven't already. Open your terminal and run pip install anthropic python-dotenv. Then create a .env file in your project root and add your key.
ANTHROPIC_API_KEY=sk-ant-your-key-here
Now let's set up the base of the agent file. This handles the client initialization and imports everything we'll need throughout the project.
lead_scoring_agent.py (imports and setup)import os
import json
from dotenv import load_dotenv
import anthropic
load_dotenv()
# Initialize the Anthropic client using the API key from your .env file
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
MODEL = "claude-sonnet-4-6"
That's all the setup you need. The anthropic.Anthropic() call reads your key and gives you a client object you'll use to talk to Claude throughout the rest of the script.
Step 2: Define the Lead Scoring Tool Functions
Claude's tool use feature lets the AI call Python functions you define — it decides when to use them and what arguments to pass. We're going to create three tools: a budget analyzer, an intent classifier, and an urgency detector.
Each tool has two parts: a Python function that does the actual work, and a JSON schema definition that tells Claude what the tool does and what inputs it accepts. Let's write both.
lead_scoring_agent.py (tool functions)def analyze_budget(annual_income: float, pre_approved: bool,
target_price: float, down_payment_pct: float) -> dict:
"""
Scores a lead's budget fit on a 0-100 scale.
Checks if income, pre-approval status, and down payment align
with the target property price.
"""
score = 0
# Pre-approval is the strongest signal of budget seriousness
if pre_approved:
score += 40
# Rough affordability check: target price should be <= 5x annual income
affordability_ratio = target_price / annual_income if annual_income > 0 else 999
if affordability_ratio <= 3:
score += 35
elif affordability_ratio <= 4.5:
score += 25
elif affordability_ratio <= 5:
score += 10
# Down payment percentage signals financial readiness
if down_payment_pct >= 20:
score += 25
elif down_payment_pct >= 10:
score += 15
elif down_payment_pct >= 5:
score += 5
return {
"budget_score": min(score, 100),
"affordability_ratio": round(affordability_ratio, 2),
"pre_approved": pre_approved,
"assessment": "strong" if score >= 70 else "moderate" if score >= 40 else "weak"
}
def classify_intent(inquiry_text: str, pages_visited: int,
contact_attempts: int) -> dict:
"""
Scores buyer intent based on how they've engaged with your listings.
Returns a 0-100 score and an intent label.
"""
score = 0
# More pages visited = more serious research behavior
if pages_visited >= 10:
score += 35
elif pages_visited >= 5:
score += 20
elif pages_visited >= 2:
score += 10
# Multiple contact attempts = high intent signal
if contact_attempts >= 3:
score += 30
elif contact_attempts == 2:
score += 20
elif contact_attempts == 1:
score += 10
# Look for high-intent keywords in the inquiry text
high_intent_keywords = [
"ready to buy", "pre-approved", "move in", "closing",
"make an offer", "schedule a showing", "serious buyer"
]
medium_intent_keywords = [
"interested", "looking", "considering", "want to see",
"can you tell me more", "availability"
]
inquiry_lower = inquiry_text.lower()
if any(kw in inquiry_lower for kw in high_intent_keywords):
score += 35
elif any(kw in inquiry_lower for kw in medium_intent_keywords):
score += 20
intent_label = "hot" if score >= 70 else "warm" if score >= 40 else "cold"
return {
"intent_score": min(score, 100),
"intent_label": intent_label,
"pages_visited": pages_visited,
"contact_attempts": contact_attempts
}
def detect_urgency(move_in_timeline: str, life_event: str,
days_since_first_contact: int) -> dict:
"""
Scores how urgently a lead needs to move.
Life events (relocation, divorce, new job) and short timelines
push this score up fast.
"""
score = 0
# Map stated timelines to urgency points
timeline_scores = {
"immediate": 50,
"1 month": 45,
"1-3 months": 35,
"3-6 months": 20,
"6-12 months": 10,
"just browsing": 0
}
score += timeline_scores.get(move_in_timeline.lower(), 15)
# Life events dramatically increase purchase urgency
high_urgency_events = ["relocation", "new job", "divorce", "lease ending", "foreclosure"]
medium_urgency_events = ["growing family", "retirement", "downsizing"]
life_event_lower = life_event.lower()
if any(event in life_event_lower for event in high_urgency_events):
score += 35
elif any(event in life_event_lower for event in medium_urgency_events):
score += 20
# Leads who reached out recently are still engaged
if days_since_first_contact <= 2:
score += 15
elif days_since_first_contact <= 7:
score += 8
urgency_label = "urgent" if score >= 70 else "moderate" if score >= 40 else "low"
return {
"urgency_score": min(score, 100),
"urgency_label": urgency_label,
"move_in_timeline": move_in_timeline,
"life_event": life_event
}
Now we need to define the JSON schemas that tell Claude exactly how to call these functions. This is how Claude's tool use knows what arguments to pass and what data types to expect.
lead_scoring_agent.py (tool definitions)TOOLS = [
{
"name": "analyze_budget",
"description": (
"Analyzes a real estate lead's financial profile to determine "
"budget fit and financial readiness. Returns a budget score from "
"0-100 and an affordability assessment."
),
"input_schema": {
"type": "object",
"properties": {
"annual_income": {
"type": "number",
"description": "The lead's stated annual household income in USD."
},
"pre_approved": {
"type": "boolean",
"description": "Whether the lead has mortgage pre-approval."
},
"target_price": {
"type": "number",
"description": "The target property price the lead is looking for in USD."
},
"down_payment_pct": {
"type": "number",
"description": "The percentage of the purchase price the lead can put down (0-100)."
}
},
"required": ["annual_income", "pre_approved", "target_price", "down_payment_pct"]
}
},
{
"name": "classify_intent",
"description": (
"Classifies a lead's buyer intent based on their engagement behavior "
"and the language they used in their inquiry. Returns an intent score "
"from 0-100 and a label of hot, warm, or cold."
),
"input_schema": {
"type": "object",
"properties": {
"inquiry_text": {
"type": "string",
"description": "The raw text of the lead's inquiry message."
},
"pages_visited": {
"type": "integer",
"description": "Number of listing pages the lead visited on the website."
},
"contact_attempts": {
"type": "integer",
"description": "Number of times the lead has reached out or submitted a form."
}
},
"required": ["inquiry_text", "pages_visited", "contact_attempts"]
}
},
{
"name": "detect_urgency",
"description": (
"Detects how urgently a lead needs to make a purchase based on their "
"stated timeline, life events, and recency of contact. Returns an "
"urgency score from 0-100 and a label of urgent, moderate, or low."
),
"input_schema": {
"type": "object",
"properties": {
"move_in_timeline": {
"type": "string",
"description": "The lead's stated move-in timeline (e.g., 'immediate', '1-3 months', '6-12 months')."
},
"life_event": {
"type": "string",
"description": "Any life event driving the purchase (e.g., 'relocation', 'growing family', 'none')."
},
"days_since_first_contact": {
"type": "integer",
"description": "Number of days since the lead first made contact."
}
},
"required": ["move_in_timeline", "life_event", "days_since_first_contact"]
}
}
]
description fields in your tool schemas are not just documentation — Claude actually reads them to decide when and how to use each tool. Write them clearly, as if you're explaining the tool to a junior analyst.
Step 3: Create the Agent Prompt and Tool Use Loop
This is the core of the agent. We're going to create a class called LeadScoringAgent that holds the tool-use loop — the back-and-forth between Claude and our tool functions that keeps running until Claude has all the information it needs.
The loop works like this: Claude reads the lead data, decides which tools to call, we execute those tools and send the results back, and Claude keeps going until it has a final score and explanation. Here's the full agent class.
lead_scoring_agent.py (LeadScoringAgent class)class LeadScoringAgent:
"""
An AI agent that scores real estate leads using Claude and tool use.
Calls budget, intent, and urgency tools to produce a composite score.
"""
def __init__(self):
self.client = client
self.model = MODEL
self.tools = TOOLS
# Map tool names to their actual Python functions
self.tool_functions = {
"analyze_budget": analyze_budget,
"classify_intent": classify_intent,
"detect_urgency": detect_urgency
}
def _build_system_prompt(self) -> str:
return """You are an expert real estate lead scoring assistant.
Your job is to analyze incoming buyer leads and produce a composite lead score from 0-100.
For every lead you receive, you MUST:
1. Call analyze_budget to assess financial readiness
2. Call classify_intent to evaluate buyer seriousness
3. Call detect_urgency to understand purchase timeline pressure
After calling all three tools, calculate a composite score using this weighting:
- Budget score: 35% weight
- Intent score: 40% weight
- Urgency score: 25% weight
Then provide a final structured response with:
- composite_score (0-100, rounded to nearest integer)
- priority_tier: "A" (80-100), "B" (60-79), "C" (40-59), "D" (below 40)
- call_within_hours: recommended follow-up window (1, 4, 24, or 72 hours)
- summary: 2-3 sentence plain-English explanation of why this lead scored the way it did
- top_strength: the single biggest positive signal from this lead
- top_concern: the single biggest risk or uncertainty about this lead
Always respond with a valid JSON object for the final answer."""
def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
"""Executes the named tool function and returns its result as a JSON string."""
if tool_name not in self.tool_functions:
return json.dumps({"error": f"Unknown tool: {tool_name}"})
try:
result = self.tool_functions[tool_name](**tool_input)
return json.dumps(result)
except TypeError as e:
return json.dumps({"error": f"Invalid tool arguments: {str(e)}"})
def score_lead(self, lead_data: dict) -> dict:
"""
Main entry point. Takes a lead dictionary, runs the full agent loop,
and returns a structured scoring result.
"""
# Format the lead data into a clear message for Claude
lead_message = f"""Please score the following real estate lead:
Lead Name: {lead_data.get('name', 'Unknown')}
Annual Income: ${lead_data.get('annual_income', 0):,.0f}
Pre-Approved for Mortgage: {lead_data.get('pre_approved', False)}
Target Property Price: ${lead_data.get('target_price', 0):,.0f}
Down Payment Available: {lead_data.get('down_payment_pct', 0)}%
Inquiry Message: "{lead_data.get('inquiry_text', '')}"
Pages Visited on Website: {lead_data.get('pages_visited', 0)}
Number of Contact Attempts: {lead_data.get('contact_attempts', 1)}
Desired Move-In Timeline: {lead_data.get('move_in_timeline', 'unknown')}
Life Event Driving Purchase: {lead_data.get('life_event', 'none')}
Days Since First Contact: {lead_data.get('days_since_first_contact', 0)}
Use all three scoring tools, then return your final assessment as a JSON object."""
messages = [{"role": "user", "content": lead_message}]
# Tool use loop — keeps running until Claude stops calling tools
while True:
response = self.client.messages.create(
model=self.model,
max_tokens=2048,
system=self._build_system_prompt(),
tools=self.tools,
messages=messages
)
# Add Claude's response to the conversation history
messages.append({"role": "assistant", "content": response.content})
# If Claude is done (no more tool calls), extract the final answer
if response.stop_reason == "end_turn":
for block in response.content:
if hasattr(block, "text"):
try:
# Strip markdown code fences if Claude wrapped the JSON
text = block.text.strip()
if text.startswith("```"):
text = text.split("```")[1]
if text.startswith("json"):
text = text[4:]
return json.loads(text.strip())
except json.JSONDecodeError:
# Return raw text if JSON parsing fails
return {"raw_response": block.text}
break
# If Claude wants to use tools, execute them and send results back
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_result = self._execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": tool_result
})
# Send tool results back to Claude to continue the loop
messages.append({"role": "user", "content": tool_results})
return {"error": "Agent loop exited without producing a result"}
Step 4: Process Lead Data and Generate Scores
Now let's write the entry point script that feeds real lead data into the agent and prints the results. This is what you'd connect to your CRM, a CSV export, or a webhook from your lead capture form.
run_scoring.pyimport json
from lead_scoring_agent import LeadScoringAgent
def print_score_report(lead_name: str, result: dict) -> None:
"""Prints a formatted score report to the terminal."""
print("\n" + "=" * 55)
print(f" LEAD SCORE REPORT: {lead_name}")
print("=" * 55)
print(f" Composite Score : {result.get('composite_score', 'N/A')} / 100")
print(f" Priority Tier : {result.get('priority_tier', 'N/A')}")
print(f" Follow Up Within: {result.get('call_within_hours', 'N/A')} hours")
print(f"\n Summary:\n {result.get('summary', '')}")
print(f"\n Top Strength : {result.get('top_strength', '')}")
print(f" Top Concern : {result.get('top_concern', '')}")
print("=" * 55 + "\n")
def main():
agent = LeadScoringAgent()
# Sample leads — in production these would come from your CRM or database
leads = [
{
"name": "Maria Santos",
"annual_income": 145000,
"pre_approved": True,
"target_price": 520000,
"down_payment_pct": 22,
"inquiry_text": "Hi, I'm pre-approved and ready to make an offer. Can we schedule a showing this week? We need to move in within the next 30 days due to a job relocation.",
"pages_visited": 14,
"contact_attempts": 3,
"move_in_timeline": "1 month",
"life_event": "relocation",
"days_since_first_contact": 1
},
{
"name": "Kevin Tran",
"annual_income": 78000,
"pre_approved": False,
"target_price": 450000,
"down_payment_pct": 5,
"inquiry_text": "Just browsing to see what's available in the area. No rush.",
"pages_visited": 3,
"contact_attempts": 1,
"move_in_timeline": "just browsing",
"life_event": "none",
"days_since_first_contact": 14
},
{
"name": "Sandra Okafor",
"annual_income": 112000,
"pre_approved": False,
"target_price": 380000,
"down_payment_pct": 15,
"inquiry_text": "I'm very interested in this property. Our lease is ending in 3 months and we want to see a few options before deciding.",
"pages_visited": 8,
"contact_attempts": 2,
"move_in_timeline": "3-6 months",
"life_event": "lease ending",
"days_since_first_contact": 4
}
]
print("\n🏠 Naples AI — Real Estate Lead