Skip to content
Learn Agentic AI14 min read0 views

Building a Lead Qualification Chat Agent: Collecting and Scoring User Information

Build a complete lead qualification chat agent with structured question flows, conditional branching logic, real-time lead scoring models, and CRM integration to convert website visitors into qualified sales opportunities.

From Traffic to Pipeline

Most B2B websites convert less than 3% of visitors into leads. A well-built lead qualification chat agent changes that equation by engaging visitors in real time, asking the right questions, scoring their fit, and routing qualified leads to sales — all without human intervention. The best qualification agents feel like a helpful conversation, not a form to fill out.

The architecture has three layers: a question flow engine that guides the conversation, a scoring model that evaluates fit in real time, and a CRM integration that delivers qualified leads to the right salesperson.

The Lead Data Model

Start by defining what a qualified lead looks like:

from pydantic import BaseModel, Field
from enum import Enum
from datetime import datetime

class LeadScore(str, Enum):
    HOT = "hot"        # 80-100 points, route to sales immediately
    WARM = "warm"      # 50-79 points, nurture sequence
    COLD = "cold"      # 0-49 points, marketing list

class LeadData(BaseModel):
    session_id: str
    name: str | None = None
    email: str | None = None
    company: str | None = None
    company_size: str | None = None
    role: str | None = None
    budget: str | None = None
    timeline: str | None = None
    use_case: str | None = None
    pain_points: list[str] = Field(default_factory=list)
    score: int = 0
    grade: LeadScore = LeadScore.COLD
    source_page: str | None = None
    created_at: datetime = Field(default_factory=datetime.utcnow)

    def calculate_score(self) -> int:
        score = 0

        # Company size scoring
        size_scores = {
            "1-10": 5, "11-50": 10, "51-200": 20,
            "201-1000": 30, "1000+": 40
        }
        score += size_scores.get(self.company_size or "", 0)

        # Role scoring - decision makers score higher
        role_keywords = {
            "cto": 25, "vp": 25, "director": 20, "head": 20,
            "manager": 15, "engineer": 5, "developer": 5,
        }
        if self.role:
            role_lower = self.role.lower()
            for keyword, points in role_keywords.items():
                if keyword in role_lower:
                    score += points
                    break

        # Budget scoring
        budget_scores = {
            "under_1k": 5, "1k_5k": 15, "5k_20k": 25,
            "20k_50k": 35, "50k_plus": 40
        }
        score += budget_scores.get(self.budget or "", 0)

        # Timeline scoring - urgency increases score
        timeline_scores = {
            "immediate": 20, "this_month": 15,
            "this_quarter": 10, "exploring": 5
        }
        score += timeline_scores.get(self.timeline or "", 0)

        self.score = min(score, 100)
        if self.score >= 80:
            self.grade = LeadScore.HOT
        elif self.score >= 50:
            self.grade = LeadScore.WARM
        else:
            self.grade = LeadScore.COLD

        return self.score

Conversational Question Flow

Use a state machine to guide the conversation while keeping it natural. The agent asks questions in a logical sequence but adapts based on previous answers:

from dataclasses import dataclass, field

@dataclass
class QualificationStep:
    field_name: str
    question: str
    follow_ups: dict[str, str] = field(default_factory=dict)
    skip_if: str | None = None

QUALIFICATION_FLOW = [
    QualificationStep(
        field_name="use_case",
        question="What brings you to our site today? I'd love to understand what you're looking for.",
    ),
    QualificationStep(
        field_name="company",
        question="Which company are you with? That will help me tailor my recommendations.",
    ),
    QualificationStep(
        field_name="company_size",
        question="How large is your team?",
    ),
    QualificationStep(
        field_name="role",
        question="And what's your role there?",
    ),
    QualificationStep(
        field_name="timeline",
        question="What's your timeline for getting started?",
        follow_ups={
            "immediate": "Great, sounds like this is a priority. Let me make sure I connect you with the right person.",
            "exploring": "No rush at all. Let me share some resources that might help your evaluation.",
        },
    ),
    QualificationStep(
        field_name="email",
        question="What's the best email to reach you at? I'll send over the details we discussed.",
    ),
]

class QualificationEngine:
    def __init__(self):
        self.lead = LeadData(session_id="")
        self.current_step = 0
        self.completed_fields: set[str] = set()

    def get_next_question(self) -> str | None:
        while self.current_step < len(QUALIFICATION_FLOW):
            step = QUALIFICATION_FLOW[self.current_step]
            if step.field_name not in self.completed_fields:
                return step.question
            self.current_step += 1
        return None

    def process_answer(self, field_name: str, value: str) -> str | None:
        setattr(self.lead, field_name, value)
        self.completed_fields.add(field_name)

        step = QUALIFICATION_FLOW[self.current_step]
        follow_up = step.follow_ups.get(value)

        self.current_step += 1
        next_q = self.get_next_question()

        if follow_up and next_q:
            return f"{follow_up} {next_q}"
        return next_q

AI-Powered Extraction

Instead of rigid multiple-choice answers, let the LLM extract structured data from natural conversation:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

from agents import Agent, function_tool, Runner

@function_tool
def update_lead_field(field_name: str, value: str) -> str:
    """Update a lead qualification field with extracted information.
    field_name: one of name, email, company, company_size, role, budget, timeline, use_case
    value: the extracted value from the conversation
    """
    return f"Updated {field_name} to {value}"

@function_tool
def get_qualification_status() -> str:
    """Check which qualification fields have been collected and what to ask next."""
    return "Returns current lead state and next question"

qualification_agent = Agent(
    name="Lead Qualifier",
    instructions="""You are a friendly lead qualification agent. Your goal is to
    understand the visitor's needs and collect qualification information naturally.

    Rules:
    - Never ask more than one question at a time
    - Extract information from natural responses (if they say "I'm a CTO at Acme",
      extract both role and company)
    - Be conversational, not robotic
    - After collecting use_case, company, role, and timeline, ask for email
    - Use update_lead_field to store extracted information
    - If the lead score is high, express enthusiasm and offer to book a demo""",
    tools=[update_lead_field, get_qualification_status],
)

CRM Integration

When the lead is qualified, push it to your CRM. Here is a HubSpot integration example:

interface HubSpotContact {
  email: string;
  firstname?: string;
  lastname?: string;
  company?: string;
  jobtitle?: string;
  hs_lead_status: string;
  lead_score: number;
}

async function pushToHubSpot(lead: LeadData): Promise<string> {
  const nameParts = (lead.name || "").split(" ");

  const contact: HubSpotContact = {
    email: lead.email!,
    firstname: nameParts[0],
    lastname: nameParts.slice(1).join(" "),
    company: lead.company || undefined,
    jobtitle: lead.role || undefined,
    hs_lead_status: lead.grade === "hot" ? "NEW" : "OPEN",
    lead_score: lead.score,
  };

  const response = await fetch(
    "https://api.hubapi.com/crm/v3/objects/contacts",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.HUBSPOT_TOKEN}`,
      },
      body: JSON.stringify({ properties: contact }),
    },
  );

  if (!response.ok) {
    const error = await response.json();
    if (error.category === "CONFLICT") {
      return await updateExistingContact(lead.email!, contact);
    }
    throw new Error(`HubSpot API error: ${error.message}`);
  }

  const result = await response.json();
  return result.id;
}

Real-Time Score Display

Give the user a sense of progress by subtly showing qualification advancement. On the backend, recalculate the score after each field update and send it to the frontend:

async def on_field_update(ws, engine: QualificationEngine):
    score = engine.lead.calculate_score()
    await ws.send_json({
        "type": "lead_progress",
        "fields_collected": len(engine.completed_fields),
        "total_fields": len(QUALIFICATION_FLOW),
        "score": score,
        "grade": engine.lead.grade.value,
    })

FAQ

How do I prevent the agent from feeling like an interrogation?

Interleave value-giving with questions. After the user shares their use case, provide a relevant insight or case study before asking the next question. Limit consecutive questions to two before offering something helpful. The conversation should feel like a consultative discussion, not a form with a chatbot face.

What if the user provides their email early — should I skip ahead?

Yes. Always extract information opportunistically. If the user says "I'm John at john@acme.com, we need help with X," extract the name, email, and use case in one pass, skip those steps in the flow, and move directly to the questions you still need answered. Rigid flows that repeat already-answered questions frustrate users and reduce conversion rates.

How do I handle leads who refuse to share their email?

Respect the boundary. Offer alternatives: "No problem at all. Would you prefer I share some resources you can review on your own?" Provide value regardless — a useful recommendation or link. Track these sessions separately as anonymous qualified leads. Often, users return later and are more willing to share contact information after seeing the agent is genuinely helpful.


#LeadQualification #Sales #CRM #Scoring #ChatAgent #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.