What is a Lead Qualification Agent?
A Lead Qualification Agent is an AI-powered system that automatically evaluates incoming sales leads to determine their likelihood of converting into paying customers. Built with LangChain, this agent leverages large language models (LLMs) to analyze lead data, ask clarifying questions, score prospects against predefined criteria, and route high-value opportunities to the right sales representatives — all without manual intervention.
Traditional lead qualification relies on sales development representatives (SDRs) manually reviewing forms, conducting discovery calls, and applying frameworks like BANT (Budget, Authority, Need, Timeline) or MEDDIC. An agent automates this process by:
- Ingesting lead information from CRMs, web forms, or APIs
- Engaging in conversational qualification via chat or email
- Applying scoring models and qualification frameworks consistently
- Enriching lead profiles with external data sources
- Escalating qualified leads to human sales teams with full context
Why a Lead Qualification Agent Matters
Manual lead qualification is slow, inconsistent, and expensive. Sales teams spend up to 60% of their time on unqualified leads. An AI agent delivers:
- Speed — leads are qualified within seconds of submission, even overnight or on weekends
- Consistency — every lead is evaluated against the same criteria, eliminating human bias and fatigue-driven errors
- Scalability — handle thousands of leads simultaneously without adding headcount
- Data enrichment — automatically pull firmographic data, technographic data, and intent signals
- Prioritization — route the hottest leads to your best closers instantly
- Cost reduction — lower SDR overhead while increasing pipeline quality
For developers, LangChain provides the perfect framework to build such agents. Its modular architecture lets you compose LLM calls, tools, memory, and retrieval capabilities into a coherent system that mimics a skilled SDR's reasoning process.
Architecture Overview
Our Lead Qualification Agent uses a ReAct (Reasoning + Acting) pattern implemented with LangChain's agent framework. Here's the high-level architecture:
- LLM Brain — GPT-4 or Claude 3.5 Sonnet for reasoning and conversation
- Tool Belt — Functions for CRM lookup, company research, scoring, and calendar scheduling
- Vector Store — ChromaDB storing historical lead outcomes for similarity-based scoring
- Memory — Conversation buffer maintaining context across multi-turn qualification
- Orchestrator — LangChain AgentExecutor managing the reasoning loop
The agent follows this workflow:
1. Receive lead (webhook, API, or manual input)
2. Extract structured information from raw input
3. Enrich with external data (Clearbit, Apollo, LinkedIn)
4. Compare against ideal customer profile (ICP) criteria
5. Generate qualification questions based on missing information
6. Conduct conversational qualification if needed
7. Score the lead using multi-factor model
8. Store result and route to appropriate sales tier
9. Log outcome for future model improvement
Setting Up Your Environment
Start by installing the required dependencies. Create a new Python project and set up a virtual environment:
# Create project directory
mkdir lead-qualification-agent
cd lead-qualification-agent
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install core dependencies
pip install langchain langchain-openai langchain-community
pip install chromadb pydantic python-dotenv
pip install httpx pandas # For API calls and data handling
Create a .env file to store API keys securely:
# .env file
OPENAI_API_KEY=your-openai-api-key-here
CLEARBIT_API_KEY=your-clearbit-api-key # Optional: for enrichment
CRM_API_URL=https://your-crm-instance.com/api
CRM_API_KEY=your-crm-api-key
Load environment variables in your application entry point:
# main.py
from dotenv import load_dotenv
load_dotenv()
import os
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain.memory import ConversationBufferMemory
from langchain.prompts import PromptTemplate
Building the Core Components
1. Define the Lead Data Model
Use Pydantic to create a structured, validated lead object that the agent can reason about reliably:
# models.py
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List
from enum import Enum
from datetime import datetime
class LeadSource(str, Enum):
WEB_FORM = "web_form"
LINKEDIN = "linkedin"
REFERRAL = "referral"
COLD_OUTBOUND = "cold_outbound"
EVENT = "event"
class LeadStatus(str, Enum):
NEW = "new"
QUALIFYING = "qualifying"
QUALIFIED = "qualified"
DISQUALIFIED = "disqualified"
NURTURE = "nurture"
class CompanyInfo(BaseModel):
name: str
industry: Optional[str] = None
employee_count: Optional[int] = None
revenue_range: Optional[str] = None # e.g., "1M-10M", "10M-50M"
tech_stack: List[str] = Field(default_factory=list)
funding_stage: Optional[str] = None
class Lead(BaseModel):
id: str = Field(default_factory=lambda: f"lead_{datetime.now().timestamp()}")
first_name: str
last_name: str
email: str
title: str
company: CompanyInfo
source: LeadSource
status: LeadStatus = LeadStatus.NEW
budget_mentioned: Optional[str] = None
authority_level: Optional[str] = None # e.g., "decision_maker", "influencer"
need_description: Optional[str] = None
timeline: Optional[str] = None
qualification_score: Optional[float] = None
notes: List[str] = Field(default_factory=list)
@field_validator("email")
def validate_email(cls, v):
if "@" not in v:
raise ValueError("Invalid email address")
return v.lower()
2. Create the Qualification Scoring Engine
The scoring engine applies configurable rules to calculate a lead score between 0 and 100. This is deterministic logic that complements the LLM's judgment:
# scoring.py
from typing import Dict, Any
import json
class QualificationScorer:
"""Multi-factor scoring engine for lead qualification."""
def __init__(self, icp_criteria: Dict[str, Any]):
"""
icp_criteria example:
{
"target_industries": ["SaaS", "Fintech", "Healthcare"],
"min_employees": 50,
"max_employees": 1000,
"decision_maker_titles": ["CEO", "CTO", "VP Engineering", "Director of IT"],
"budget_threshold": 50000, # minimum annual budget
"positive_signals": ["hiring_engineers", "raised_funding", "using_competitor"],
"negative_signals": ["using_legacy_system", "no_digital_presence"]
}
"""
self.icp = icp_criteria
self.weights = {
"industry_match": 20,
"company_size": 15,
"authority_level": 25,
"budget_indicator": 20,
"timeline_urgency": 10,
"positive_signals": 10
}
def score(self, lead_data: Dict[str, Any]) -> Dict[str, Any]:
scores = {}
breakdown = {}
# Industry match
industry = lead_data.get("company", {}).get("industry", "")
scores["industry_match"] = (
20 if industry in self.icp.get("target_industries", []) else 5
)
breakdown["industry_match"] = {
"value": scores["industry_match"],
"reason": f"Industry '{industry}' {'matches' if scores['industry_match']==20 else 'does not match'} target industries"
}
# Company size
employees = lead_data.get("company", {}).get("employee_count", 0)
min_emp = self.icp.get("min_employees", 0)
max_emp = self.icp.get("max_employees", 100000)
if min_emp <= employees <= max_emp:
scores["company_size"] = 15
breakdown["company_size"] = {"value": 15, "reason": f"{employees} employees within ICP range"}
elif employees > 0:
scores["company_size"] = 7
breakdown["company_size"] = {"value": 7, "reason": f"{employees} employees outside ICP range {min_emp}-{max_emp}"}
else:
scores["company_size"] = 3
breakdown["company_size"] = {"value": 3, "reason": "Unknown employee count"}
# Authority level
title = lead_data.get("title", "").lower()
decision_titles = [t.lower() for t in self.icp.get("decision_maker_titles", [])]
if any(dt in title for dt in decision_titles):
scores["authority_level"] = 25
breakdown["authority_level"] = {"value": 25, "reason": f"Title '{title}' indicates decision-making authority"}
elif "manager" in title or "lead" in title:
scores["authority_level"] = 15
breakdown["authority_level"] = {"value": 15, "reason": "Mid-level influencer detected"}
else:
scores["authority_level"] = 5
breakdown["authority_level"] = {"value": 5, "reason": "Limited authority based on title"}
# Budget indicator
budget = lead_data.get("budget_mentioned", "")
if budget:
try:
budget_num = int("".join(c for c in budget if c.isdigit()))
if budget_num >= self.icp.get("budget_threshold", 50000):
scores["budget_indicator"] = 20
breakdown["budget_indicator"] = {"value": 20, "reason": f"Mentioned budget ${budget_num} meets threshold"}
else:
scores["budget_indicator"] = 8
breakdown["budget_indicator"] = {"value": 8, "reason": f"Budget ${budget_num} below threshold"}
except ValueError:
scores["budget_indicator"] = 10
breakdown["budget_indicator"] = {"value": 10, "reason": "Budget mentioned but amount unclear"}
else:
scores["budget_indicator"] = 0
breakdown["budget_indicator"] = {"value": 0, "reason": "No budget information provided"}
# Timeline urgency
timeline = lead_data.get("timeline", "").lower()
urgent_keywords = ["immediately", "urgent", "asap", "this month", "this quarter"]
if any(kw in timeline for kw in urgent_keywords):
scores["timeline_urgency"] = 10
breakdown["timeline_urgency"] = {"value": 10, "reason": "Urgent timeline indicated"}
elif timeline:
scores["timeline_urgency"] = 5
breakdown["timeline_urgency"] = {"value": 5, "reason": "Timeline mentioned but not urgent"}
else:
scores["timeline_urgency"] = 0
breakdown["timeline_urgency"] = {"value": 0, "reason": "No timeline provided"}
# Positive signals
positive_count = 0
for signal in self.icp.get("positive_signals", []):
if signal.lower() in str(lead_data).lower():
positive_count += 1
scores["positive_signals"] = min(10, positive_count * 5)
breakdown["positive_signals"] = {"value": scores["positive_signals"], "reason": f"{positive_count} positive signals detected"}
total_score = sum(scores.values())
return {
"total_score": total_score,
"max_possible": 100,
"breakdown": breakdown,
"qualification_level": (
"hot" if total_score >= 75 else
"warm" if total_score >= 50 else
"cool" if total_score >= 30 else
"cold"
)
}
3. Build the Tool Set for the Agent
LangChain tools are functions decorated with @tool that the agent can invoke during its reasoning loop. Each tool has a clear name and description so the LLM knows when to use it:
# tools.py
from langchain.tools import tool
from typing import Optional, Dict, Any
import json
import httpx
from datetime import datetime, timedelta
# Mock CRM database for demonstration
MOCK_CRM: Dict[str, Dict[str, Any]] = {}
@tool
def lookup_existing_lead(email: str) -> str:
"""Search CRM for existing lead by email. Returns lead history if found, or 'not_found'."""
email_lower = email.lower().strip()
if email_lower in MOCK_CRM:
lead_data = MOCK_CRM[email_lower]
return json.dumps({
"found": True,
"lead_id": lead_data.get("id"),
"status": lead_data.get("status"),
"previous_interactions": lead_data.get("interactions", []),
"last_contacted": lead_data.get("last_contacted", "never"),
"notes": lead_data.get("notes", [])
}, indent=2)
return json.dumps({"found": False, "message": "No existing lead found with this email"})
@tool
def enrich_company_info(company_name: str) -> str:
"""Fetch enriched company data from external APIs. Returns firmographic and technographic data."""
# In production, call Clearbit, Apollo, or LinkedIn APIs here
# For demo, return simulated enrichment data
enrichment_db = {
"acme corp": {
"industry": "SaaS",
"employee_count": 350,
"revenue_range": "50M-100M",
"tech_stack": ["Salesforce", "AWS", "Slack", "HubSpot"],
"funding_stage": "Series C",
"recent_news": ["Hiring 50 engineers", "Launched new product line"],
"linkedin_url": "https://linkedin.com/company/acme-corp"
},
"globaltech solutions": {
"industry": "Fintech",
"employee_count": 1200,
"revenue_range": "200M-500M",
"tech_stack": ["Azure", "Datadog", "PagerDuty", "Kubernetes"],
"funding_stage": "Public",
"recent_news": ["Opened European office", "Won industry award"]
}
}
company_key = company_name.lower().strip()
for db_key, data in enrichment_db.items():
if company_key in db_key or db_key in company_key:
return json.dumps(data, indent=2)
# Simulate API call for unknown companies
simulated_data = {
"industry": "Technology",
"employee_count": 250,
"revenue_range": "10M-50M",
"tech_stack": ["Unknown"],
"funding_stage": "Private",
"recent_news": [],
"note": "Limited enrichment data available for this company"
}
return json.dumps(simulated_data, indent=2)
@tool
def calculate_qualification_score(lead_json: str) -> str:
"""Calculate qualification score from lead JSON data. Input must be valid JSON with fields:
company (with industry, employee_count), title, budget_mentioned, timeline."""
from scoring import QualificationScorer
icp = {
"target_industries": ["SaaS", "Fintech", "Healthcare", "DevOps"],
"min_employees": 50,
"max_employees": 2000,
"decision_maker_titles": ["CEO", "CTO", "VP", "Director", "Head of"],
"budget_threshold": 50000,
"positive_signals": ["hiring", "funding", "growth", "scaling"],
"negative_signals": ["legacy", "outsourcing"]
}
try:
lead_data = json.loads(lead_json)
scorer = QualificationScorer(icp)
result = scorer.score(lead_data)
return json.dumps(result, indent=2)
except json.JSONDecodeError:
return json.dumps({"error": "Invalid JSON input", "hint": "Provide valid JSON string"})
except Exception as e:
return json.dumps({"error": str(e)})
@tool
def save_qualified_lead(lead_json: str) -> str:
"""Save a qualified lead to the CRM. Only use when lead score >= 50.
Returns confirmation with lead ID."""
try:
lead_data = json.loads(lead_json)
email = lead_data.get("email", "").lower().strip()
lead_id = f"lead_{int(datetime.now().timestamp())}"
MOCK_CRM[email] = {
"id": lead_id,
"data": lead_data,
"status": "qualified",
"qualified_at": datetime.now().isoformat(),
"interactions": [],
"notes": [f"Auto-qualified by AI agent at {datetime.now().isoformat()}"]
}
return json.dumps({
"success": True,
"lead_id": lead_id,
"message": f"Lead {lead_data.get('first_name', 'Unknown')} {lead_data.get('last_name', '')} saved and qualified",
"next_step": "Routing to appropriate sales representative"
}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)})
@tool
def generate_qualification_questions(lead_json: str) -> str:
"""Generate personalized qualification questions based on missing ICP criteria in lead data.
Use when you need to gather more information from the lead."""
try:
lead_data = json.loads(lead_json)
questions = []
if not lead_data.get("budget_mentioned"):
questions.append("What budget range have you allocated for a solution like this?")
if not lead_data.get("timeline"):
questions.append("What's your expected timeline for implementing a solution?")
if not lead_data.get("need_description"):
questions.append("Can you describe the primary challenge or need you're looking to address?")
if not lead_data.get("authority_level") or lead_data.get("authority_level") not in ["decision_maker"]:
questions.append("Who else on your team would be involved in evaluating and purchasing a solution?")
if not questions:
questions.append("I have all the information I need. Let me summarize your requirements.")
return json.dumps({
"questions": questions,
"missing_fields": [
f for f in ["budget_mentioned", "timeline", "need_description", "authority_level"]
if not lead_data.get(f)
]
}, indent=2)
except Exception as e:
return json.dumps({"error": str(e)})
4. Set Up the Vector Store for Historical Pattern Matching
A ChromaDB vector store helps the agent compare new leads against previously qualified and disqualified leads to identify patterns:
# vector_store.py
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
import json
class LeadVectorStore:
"""Vector store for historical lead qualification patterns."""
def __init__(self, persist_directory: str = "./lead_vector_db"):
self.embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
self.persist_directory = persist_directory
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
self.vectorstore = None
def initialize(self):
"""Load or create the vector store."""
try:
self.vectorstore = Chroma(
embedding_function=self.embeddings,
persist_directory=self.persist_directory,
collection_name="lead_qualification_history"
)
except Exception:
# Create new if doesn't exist
self.vectorstore = Chroma(
embedding_function=self.embeddings,
persist_directory=self.persist_directory,
collection_name="lead_qualification_history"
)
return self
def add_historical_leads(self, leads: list):
"""Add historical lead outcomes to the vector store."""
documents = []
metadatas = []
for lead in leads:
# Create a rich text representation
doc_text = f"""
Lead: {lead.get('first_name', '')} {lead.get('last_name', '')}
Title: {lead.get('title', '')}
Company: {lead.get('company', {}).get('name', '')}
Industry: {lead.get('company', {}).get('industry', '')}
Employees: {lead.get('company', {}).get('employee_count', 'Unknown')}
Budget: {lead.get('budget_mentioned', 'Not mentioned')}
Timeline: {lead.get('timeline', 'Not mentioned')}
Need: {lead.get('need_description', 'Not described')}
Outcome: {lead.get('outcome', 'unknown')} # 'qualified', 'disqualified', 'nurture'
Score: {lead.get('qualification_score', 'N/A')}
"""
chunks = self.text_splitter.split_text(doc_text)
for i, chunk in enumerate(chunks):
documents.append(chunk)
metadatas.append({
"lead_id": lead.get("id", f"historical_{i}"),
"outcome": lead.get("outcome", "unknown"),
"score": lead.get("qualification_score", 0),
"chunk_index": i
})
if documents:
self.vectorstore.add_texts(
texts=documents,
metadatas=metadatas
)
return self
def find_similar_leads(self, lead_description: str, k: int = 5) -> str:
"""Find similar historical leads and their outcomes."""
if not self.vectorstore:
return json.dumps({"error": "Vector store not initialized"})
results = self.vectorstore.similarity_search_with_score(
lead_description, k=k
)
similar_leads = []
for doc, score in results:
similar_leads.append({
"content": doc.page_content,
"similarity_score": round(float(score), 4),
"outcome": doc.metadata.get("outcome", "unknown"),
"original_score": doc.metadata.get("score", "N/A")
})
return json.dumps({
"similar_leads_found": len(similar_leads),
"results": similar_leads,
"insight": "Use these historical outcomes to inform qualification decision"
}, indent=2)
Assembling the Agent
Now we compose all components into a working agent. The key is the system prompt that defines the agent's reasoning process and tool usage strategy:
# agent.py
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.memory import ConversationBufferMemory
from langchain.prompts import PromptTemplate
from langchain.callbacks import StdOutCallbackHandler
from tools import (
lookup_existing_lead,
enrich_company_info,
calculate_qualification_score,
save_qualified_lead,
generate_qualification_questions
)
from vector_store import LeadVectorStore
class LeadQualificationAgent:
"""Complete lead qualification agent using LangChain ReAct pattern."""
def __init__(self, verbose: bool = True):
self.llm = ChatOpenAI(
model="gpt-4o",
temperature=0.2, # Low temperature for consistent qualification
max_tokens=2000
)
# Register all tools
self.tools = [
lookup_existing_lead,
enrich_company_info,
calculate_qualification_score,
save_qualified_lead,
generate_qualification_questions
]
# Initialize vector store
self.vector_store = LeadVectorStore().initialize()
# Set up conversation memory
self.memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="output"
)
# Create the system prompt
self.system_prompt = """You are an expert Lead Qualification Agent for a B2B SaaS company.
Your job is to evaluate inbound leads and determine if they match our Ideal Customer Profile (ICP).
OUR ICP:
- Target industries: SaaS, Fintech, Healthcare, DevOps, Cybersecurity
- Company size: 50-2000 employees
- Decision makers: CTO, VP Engineering, Director of IT, CEO, Head of DevOps
- Budget threshold: $50,000+ annually
- Positive signals: Hiring engineers, recent funding, scaling infrastructure, using competitor products
QUALIFICATION WORKFLOW:
1. First, use lookup_existing_lead to check if this lead already exists in CRM
2. Use enrich_company_info to get firmographic and technographic data
3. Use generate_qualification_questions to identify what information is missing
4. If interacting with a user, ask the generated questions one at a time naturally
5. Once you have sufficient data, use calculate_qualification_score
6. If score >= 75 (hot): use save_qualified_lead and explain routing to sales team
7. If score 50-74 (warm): save and recommend nurture sequence
8. If score 30-49 (cool): save with nurture tag, recommend long-term nurture
9. If score < 30 (cold): explain disqualification reason, don't save
RULES:
- Always be professional and courteous
- Don't make up information — use tools to get real data
- When you don't know something, ask the lead directly
- Explain your qualification reasoning clearly
- Use calculate_qualification_score before making final determination
- Never save leads with score < 30 as qualified
You have access to the following tools: {tools}
Use this format:
Thought: I need to reason about what to do next
Action: the tool name to use (must be one of: {tool_names})
Action Input: the input for the tool (valid JSON or string as required)
Observation: the tool's output
... (repeat Thought/Action/Action Input/Observation as needed)
Thought: Now I can provide the final answer
Final Answer: Your comprehensive response to the user
Begin!"""
# Create the prompt template
self.prompt = PromptTemplate.from_template(
self.system_prompt + "\n\nChat History:\n{chat_history}\n\nHuman: {input}\n\n{agent_scratchpad}"
)
# Create the ReAct agent
self.agent = create_react_agent(
llm=self.llm,
tools=self.tools,
prompt=self.prompt
)
# Create the executor
self.executor = AgentExecutor(
agent=self.agent,
tools=self.tools,
memory=self.memory,
verbose=verbose,
handle_parsing_errors=True,
max_iterations=10,
early_stopping_method="generate",
callbacks=[StdOutCallbackHandler()] if verbose else []
)
def qualify_lead(self, lead_input: str) -> str:
"""Run qualification on a lead description or conversation turn."""
result = self.executor.invoke({"input": lead_input})
return result.get("output", "No output generated")
def reset_conversation(self):
"""Reset memory for a new lead conversation."""
self.memory.clear()
def get_conversation_history(self) -> str:
"""Retrieve the full conversation history."""
messages = self.memory.load_memory_variables({})
return str(messages.get("chat_history", ""))
Running the Agent: Complete Example
Here's how to run the agent with a sample lead submission:
# run_qualification.py
from agent import LeadQualificationAgent
import json
def main():
# Initialize the agent
print("Initializing Lead Qualification Agent...")
agent = LeadQualificationAgent(verbose=True)
# Sample lead input (simulating a web form submission)
lead_submission = """
New lead from website contact form:
Name: Sarah Chen
Email: sarah.chen@globaltech.com
Title: VP of Engineering
Company: GlobalTech Solutions
Message: We're scaling our infrastructure and looking for a monitoring solution.
Our current setup with Datadog is becoming too expensive at our scale.
We need something that can handle 1000+ microservices. Budget is around $150K annually.
Looking to implement within the next 2 months.
"""
print("\n=== Processing Lead Submission ===\n")
result = agent.qualify_lead(lead_submission)
print("\n=== Final Qualification Result ===\n")
print(result)
print("\n=== Conversation History ===\n")
print(agent.get_conversation_history())
if __name__ == "__main__":
main()
Expected output flow:
Initializing Lead Qualification Agent...
=== Processing Lead Submission ===
Thought: I need to evaluate this lead step by step. Let me start by checking CRM.
Action: lookup_existing_lead
Action Input: {"email": "sarah.chen@globaltech.com"}
Observation: {"found": false, "message": "No existing lead found with this email"}
Thought: Good, this is a new lead. Now let me enrich the company data.
Action: enrich_company_info
Action Input: {"company_name": "GlobalTech Solutions"}
Observation: {
"industry": "Fintech",
"employee_count": 1200,
"revenue_range": "200M-500M",
"tech_stack": ["Azure", "Datadog", "PagerDuty", "Kubernetes"],
"funding_stage": "Public",
"recent_news": ["Opened European office", "Won industry award"]
}
Thought: Excellent enrichment data. Let me now check what qualification questions to ask.
The lead already provided budget ($150K), timeline (2 months), and need description.
Let me calculate the qualification score now.
Action: calculate_qualification_score
Action Input: {"lead_json": "{\"first_name\": \"Sarah\", \"last_name\": \"Chen\", \"email\": \"sarah.chen@globaltech.com\", \"title\": \"VP of Engineering\", \"company\": {\"name\": \"GlobalTech Solutions\", \"industry\": \"Fintech\", \"employee_count\": 1200, \"revenue_range\": \"200M-500M\", \"tech_stack\": [\"Azure\", \"Datadog\", \"PagerDuty\", \"Kubernetes\"], \"funding_stage\": \"Public\"}, \"budget_mentioned\": \"$150K annually\", \"timeline\": \"within the next 2 months\", \"need_description\": \"Scaling infrastructure, looking for monitoring solution to replace Datadog for 1000+ microservices\"}"}
Observation: {
"total_score": 85,
"max_possible": 100,
"breakdown": {
"industry_match": {"value": 20, "reason": "Industry 'Fintech' matches target industries"},
"company_size": {"value": 15, "reason": "1200 employees within ICP range"},
"authority_level": {"value": 25, "reason": "Title 'VP of Engineering' indicates decision-making authority"},
"budget_indicator": {"value": 20, "reason": "Mentioned budget $150000 meets threshold"},
"timeline_urgency": {"value": 10, "reason": "Urgent timeline indicated"},
"positive_signals": {"value": 10, "reason": "2 positive signals detected"}
},
"qualification_level": "hot"
}
Thought: Score of 85 - this is a HOT lead. I should save it immediately.
Action: save_qualified_lead
Action Input: {"lead_json": "{\"first_name\": \"Sarah\", \"last_name\": \"Chen\", \"email\": \"sarah.chen@globaltech.com\", \"title\": \"VP of Engineering\", \"company\": {\"name\": \"GlobalTech Solutions\", \"industry\": \"Fintech\", \"employee_count\": 1200}, \"qualification_score\": 85, \"qualification_level\": \"hot\", \"budget_mentioned\": \"$150K annually\", \"timeline\": \"within the next 2 months\"}"}
Observation: {"success": true, "lead_id": "lead_1700000000", "message": "Lead Sarah Chen saved and qualified"}
=== Final Qualification Result ===
Here's my qualification assessment for Sarah Chen from GlobalTech Solutions:
**Qualification Score: 85/100 — HOT LEAD ⭐**
Key factors:
- ✅ Industry: Fintech matches our ICP perfectly
- ✅ Company size: 1,200 employees within our target range
- ✅ Authority: VP of Engineering is a strong decision-maker
- ✅ Budget: $150K exceeds our $50K threshold
- ✅ Timeline: 2-month implementation indicates urgency
- ✅ Signals: Scaling infrastructure, replacing competitor (Datadog)
**Recommendation: Immediate routing to Enterprise sales team.**
This lead has been saved to CRM (lead_id: lead_1700000000).
Sarah should be contacted within 24 hours. The discovery call should focus on
their microservices monitoring needs and Datadog migration path.
Priority: Highest — all criteria exceeded.
Building a REST API for the Agent
To integrate with web forms and CRMs, wrap the agent in a FastAPI endpoint:
# api.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
from typing import Optional
from agent import LeadQualificationAgent
import uvicorn
app = FastAPI(title="Lead Qualification Agent API")
agent = LeadQualificationAgent(verbose=False)
class LeadSubmission(BaseModel):
first_name: str
last_name: str
email: EmailStr