Building a Lead Qualification Chatbot with CrewAI and Gradio

Introduction

Imagine you're a busy real estate agent with dozens of new leads flooding your inbox daily. Some are ready to make an offer this week, while others are just casually browsing. How do you quickly identify which leads deserve your immediate attention? This is where an AI-powered lead qualification chatbot comes in.

Responding quickly to potential leads is crucial in the real estate industry. Buyers and sellers expect instant engagement, but manually qualifying leads can be time consuming and inconsistent. Agents often struggle to determine which prospects are serious or just browsing. As lead volume increases, filtering and prioritizing high-quality leads becomes a bottleneck, impacting conversion rates and overall efficiency.

Why Automation Matters

Traditional chatbots often provide scripted responses, lacking the ability to adapt to nuanced buyer inquiries. A more effective approach is using AI-driven automation to engage leads conversationally, ask relevant questions, and categorize them based on intent and readiness. By automating lead qualification, real estate professionals can scale personalized interactions, ensuring high-priority leads receive immediate follow-up while reducing manual effort.

What You'll Learn

By the end of this tutorial, you'll learn how to:

  • Build a multi-agent AI system using CrewAI
  • Create a responsive chatbot interface with Gradio
  • Design and implement a lead scoring algorithm
  • Deploy a complete lead qualification solution
  • Extend the system for your specific needs

The Solution Overview

To tackle this challenge, we'll build an AI-powered lead qualification chatbot using CrwAI and Gradio. CrewAI allows us to orchestrate multiple AI agents, each with specialized roles, while Gradio provides a user-friendly interface for seamless interactions. Our chatbot will:

  • Ask targeted questions to assess a lead's buying intent, budget, and timeline
  • Classify leads (e.g., hot, warm, or cold) based on their responses
  • Provide next-step recommendations, such as scheduling a call or sending relevant listings

You can view the final chatbot code on GitHub if you're in a rush, but I recommend following along for a deeper understanding.

Technology Stack

We'll use two powerful Python frameworks to make development fast and straightforward:

  1. CrewAI serves as the backbone of our chatbot's intelligence. Though we'll start with a single crew with one agent and one task, this foundation gives us room to expand functionality easily in the future.
  2. Gradio provides an intuitive way to deploy and interact with our AI chatbot. With just a few lines of code, we can create a real-time chat interface that's both functional and professional.

Getting Started

Before diving into the architecture details, let's set up a basic version of our chatbot to see it in action.

Prerequisites

  • Python 3.10+
  • uv package manager
  • OpenAI API key

Quick Setup

git clone git@github.com:zinyando/crewai_lead_qualification_chatbot.git
cd crewai_lead_qualification_chatbot
crewai install
python src/crewai_lead_qualification_chatbot/main.py

Once running, you'll see a simple chat interface where you can start interacting with the chatbot. Try answering a few questions to see how it evaluates your responses.

System Architecture

Now that you've seen the chatbot in action, let's explore how it's built. Our architecture consists of four main components, working together to create a seamless conversation experience.

1. ChatFlow: The Conversation Orchestrator

The ChatFlow component, built on CrewAI's Flow system, manages the entire conversation state and logic.

@persist()
class ChatFlow(Flow[ChatState]):
    def __init__(self, persistence=None):
        super().__init__(persistence=persistence)
        self.question_manager = QuestionManager()

    @start()
    def initialize_chat(self):
        if not self.state.current_question_id:
# Initialize first question
            self.state.current_question_text, self.state.current_question_id = (
                self.question_manager.get_question("q1")
            )

This component handles:

  • State persistence across messages
  • Conversation flow control
  • Response processing and validation
  • Lead data collection and organization

2. QuestionManager: The Flow Controller

QuestionManager handles the sequential flow of questions, ensuring we gather all necessary information in a logical order.

class QuestionManager:
    def __init__(self):
        self.questions = self._load_questions()
        self.total_questions = len(self.questions)

    def _load_questions(self):
        questions_path = Path(__file__).parent / "questions.yaml"
        with open(questions_path, "r") as file:
            data = yaml.safe_load(file)
            return data["questions"]

Questions are stored in a .yaml file for easy modification:

questions:
  q1:
    id: "full_name"
    question: "What is your full name?"
    next: "q2"
  q2:
    id: "email"
    question: "What is your email address?"
    next: "q3"

This approach makes it easy to:

  • Load and manage question sequences
  • Track question progress
  • Handle question transitions
  • Modify the conversation flow without changing code

3. Lead Scoring System: The Quality Evaluator

Our lead scoring system uses industry-standard criteria to evaluate lead quality in real-time, implementing a 100-point scoring algorithm:

def calculate_lead_score(self) -> Dict[str, Any]:
    """
    Calculate lead score based on industry standard metrics:
    - Budget alignment (30 points)
    - Timeline urgency (25 points)
    - Pre-approval status (20 points)
    - Agent exclusivity (15 points)
    - Motivation clarity (10 points)
    """
    score = 0
    reasons = []
    state_dict = self.state.model_dump()

# Budget alignment (30 points)
    budget = str(state_dict.get("budget") or "0").replace("$", "").replace(",", "")
    try:
        budget_value = float(budget)
        if budget_value >= 500000:
            score += 30
            reasons.append("High budget range (30 points)")
        elif budget_value >= 300000:
            score += 20
            reasons.append("Medium budget range (20 points)")
        else:
            score += 10
            reasons.append("Lower budget range (10 points)")
    except ValueError:
        reasons.append("Could not evaluate budget (0 points)")

The scoring system evaluates leads based on multiple criteria:

  • Budget alignment (30 points)
  • Timeline urgency (25 points)
  • Pre-approval status (20 points)
  • Agent exclusivity (15 points)
  • Motivation clarity (10 points)

Leads are then categorized as:

  • Hot (80-100 points): High-priority leads ready to move forward
  • Warm (50-79 points): Medium-priority leads with good potential
  • Cold (0-49 points): Low-priority leads requiring nurturing

4. Gradio Interface: The User Experience Layer

The Gradio interface provides a clean, professional chat experience that feels natural to users:

def create_lead_qualification_chatbot() -> gr.Blocks:
    chatbot = ChatbotInterface()

    demo = gr.ChatInterface(
        chatbot.chat,
        chatbot=gr.Chatbot(
            label="Lead Qualification Chat",
            height=600,
            show_copy_button=True,
        ),
        textbox=gr.Textbox(
            placeholder="Type your response here and press Enter",
            container=False,
            scale=7,
        ),
        title="Lead Qualification Chatbot",
    )

This creates an interface with:

  • Clean, professional visual design
  • Real-time response handling
  • Message history display
  • Error handling and user feedback

Data Models

Our lead qualification system relies on well-structured data models to ensure reliable operation.

ChatState: The Conversation's Memory

The ChatState model, implemented using Pydantic, serves as the memory for our conversation:

from pydantic import BaseModel
from typing import Optional

class ChatState(BaseModel):
    id: str = ""
    message: Optional[str] = None
    history: Optional[str] = None

# LeadData
    full_name: Optional[str] = None
    email: Optional[str] = None
    phone: Optional[str] = None
    property_type: Optional[str] = None
    price_range: Optional[str] = None
    location: Optional[str] = None
    timeline: Optional[str] = None
    financing: Optional[str] = None
    other_agents: Optional[str] = None
    search_reason: Optional[str] = None

# QuestionState
    current_question_id: str = ""
    current_question_text: str = ""
    next_question_id: str = ""
    next_question_text: str = ""
    is_complete: bool = False

This model provides:

  • Type validation for all fields
  • Optional fields with default values
  • Clear separation of lead data and control fields
  • Serializable structure for persistence

Lead Scoring Criteria

Our lead scoring system uses weighted criteria defined as a Python dictionary:

yaml
Copy
LEAD_SCORING_CRITERIA = {
    "budget_alignment": {
        "weight": 30,
        "thresholds": {
            "high": {"value": 500000, "points": 30},
            "medium": {"value": 300000, "points": 20},
            "low": {"points": 10}
        }
    },
    "timeline_urgency": {
        "weight": 25,
        "categories": {
            "immediate": 25,
            "3_6_months": 15,
            "6_plus_months": 5
        }
    }
# Other criteria omitted for brevity
}

LEAD_QUALITY_THRESHOLDS = {
    "hot": 80,# High-priority leads
    "warm": 50,# Medium-priority leads
    "cold": 0# Low-priority leads
}

This structure makes it easy to adjust scoring weights and thresholds as your business needs evolve.

Implementation Walkthrough

Now let's walk through building each component of our chatbot step by step.

Setting Up the Project Structure

First, let's organize our project files:

src/crewai_lead_qualification_chatbot/
├── crews/
│   └── chat_crew/
│       ├── config/
│       │   ├── tasks.yaml
│       │   └── agents.yaml
│       └── chat_crew.py 
├── models.py 
├── question_manager.py
├── main.py
└── questions.yaml  

Building the Question Flow

The conversation flow is driven by our question sequence. Let's implement the QuestionManager:

# question_manager.py
import yaml
from pathlib import Path

class QuestionManager:
    def __init__(self):
        self.questions = self._load_questions()
        self.total_questions = len(self.questions)

    def _load_questions(self):
        questions_path = Path(__file__).parent / "questions.yaml"
        with open(questions_path, "r") as file:
            data = yaml.safe_load(file)
            return data["questions"]

    def get_question(self, question_id: str) -> tuple[str, str]:
        """Get question text and ID for a given question number"""
        if question_id not in self.questions:
            return "", ""
        return self.questions[question_id]["question"], question_id

    def get_next_question(self, current_id: str) -> tuple[str, str]:
        """Get the next question text and ID"""
        if current_id not in self.questions:
            return "", ""
            
        next_id = self.questions[current_id]["next"]
        if next_id == "end" or next_id not in self.questions:
            return "", ""
            
        return self.questions[next_id]["question"], next_id

    def get_field_id(self, question_id: str) -> str:
        """Get the field ID for a question"""
        if question_id not in self.questions:
            return ""
        return self.questions[question_id]["id"]

    def is_last_question(self, question_id: str) -> bool:
        """Check if this is the last question"""
        if question_id not in self.questions:
            return True
        return self.questions[question_id]["next"] == "end"

Implementing the Lead Scoring System

Next, let's build our lead scoring system:

def calculate_lead_score(self) -> Dict[str, Any]:
        """
        Calculate lead score based on industry standard metrics:
        - Budget alignment (30 points)
        - Timeline urgency (25 points)
        - Pre-approval status (20 points)
        - Agent exclusivity (15 points)
        - Motivation clarity (10 points)
        """
        score = 0
        reasons = []
        state_dict = self.state.model_dump()

        # Budget alignment (30 points)
        budget = str(state_dict.get("budget") or "0").replace("$", "").replace(",", "")
        try:
            budget_value = float(budget)
            if budget_value >= 500000:
                score += 30
                reasons.append("High budget range (30 points)")
            elif budget_value >= 300000:
                score += 20
                reasons.append("Medium budget range (20 points)")
            else:
                score += 10
                reasons.append("Lower budget range (10 points)")
        except ValueError:
            reasons.append("Could not evaluate budget (0 points)")

        # Timeline urgency (25 points)
        timeline = str(state_dict.get("timeline") or "").lower()
        if "immediate" in timeline or "1 month" in timeline:
            score += 25
            reasons.append("Immediate timeline (25 points)")
        elif "3 month" in timeline or "6 month" in timeline:
            score += 15
            reasons.append("Medium-term timeline (15 points)")
        else:
            score += 5
            reasons.append("Long-term timeline (5 points)")

        # Pre-approval status (20 points)
        pre_approved = str(state_dict.get("pre_approved") or "").lower()
        if "yes" in pre_approved:
            score += 20
            reasons.append("Pre-approved for mortgage (20 points)")
        elif "in process" in pre_approved:
            score += 10
            reasons.append("Pre-approval in process (10 points)")

        # Agent exclusivity (15 points)
        other_agents = str(state_dict.get("other_agents") or "").lower()
        if "no" in other_agents:
            score += 15
            reasons.append("Not working with other agents (15 points)")
        elif "considering" in other_agents:
            score += 5
            reasons.append("Considering other agents (5 points)")

        # Motivation clarity (10 points)
        motivation = str(state_dict.get("search_reason") or "").lower()
        if motivation and len(motivation) > 20:
            score += 10
            reasons.append("Clear motivation provided (10 points)")
        elif motivation:
            score += 5
            reasons.append("Some motivation indicated (5 points)")

        # Calculate lead quality
        quality = "Hot" if score >= 80 else "Warm" if score >= 50 else "Cold"

        return {
            "score": score,
            "quality": quality,
            "reasons": reasons,
            "timestamp": datetime.now().isoformat(),
        }

Creating the Gradio Interface

Let's implement the user interface using Gradio:

class ChatbotInterface:
    def __init__(self):
        self.chat_flows = {}
        self.chat_id = None

    def chat(self, message: str, history: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Process chat messages and maintain conversation history"""
        if not self.chat_id or not message:
            # Initialize new chat session
            self.chat_id = str(uuid.uuid4())
            self.chat_flows[self.chat_id] = ChatFlow()
            response = self._process_message("", self.chat_id, history)
            return [{"role": "assistant", "content": response}]

        # Process user message
        response = self._process_message(message, self.chat_id, history)

        # Create a new history list to avoid modifying the input
        new_history = history.copy()
        new_history.append({"role": "user", "content": message})
        new_history.append({"role": "assistant", "content": response})
        return [{"role": "assistant", "content": response}]

    def _process_message(
        self, message: str, chat_id: str, history: List[Dict[str, Any]] = []
    ) -> str:
        """Process a message through the chat flow"""
        chat_flow = self.chat_flows[chat_id]

        result = chat_flow.kickoff(
            inputs={
                "id": chat_id,
                "message": message,
                "history": (
                    "\\n".join(f"{msg['role']}: {msg['content']}" for msg in history)
                    if history
                    else ""
                ),
            }
        )
        return result

def create_lead_qualification_chatbot() -> gr.Blocks:
    """Create and configure the lead qualification chatbot"""
    chatbot = ChatbotInterface()

    demo = gr.ChatInterface(
        chatbot.chat,
        chatbot=gr.Chatbot(
            label="Lead Qualification Chat",
            height=600,
            show_copy_button=True,
            type="messages",
        ),
        textbox=gr.Textbox(
            placeholder="Type your response here",
            container=True,
            scale=7,
            show_label=False,
        ),
        submit_btn="Send",
        title="Lead Qualification Chatbot",
        description="I'll help qualify you as a potential lead by asking a series of questions about your real estate needs.",
        theme="soft",
        examples=[
            "Hi, I'm looking to buy a property",
            "Hi, I'm looking to rent a property",
        ],
        type="messages",
        autofocus=True,
    )

    return demo

Tying It All Together

Finally, let's create our main.py to launch the application:

if __name__ == "__main__":
    chatbot = create_lead_qualification_chatbot()
    chatbot.launch(share=True)

Testing Your Chatbot

Once you have the chatbot running, test it with different scenarios:

  1. Hot Lead Scenario: A buyer with a large budget, immediate timeline, and pre-approval
  2. Warm Lead Scenario: A buyer with a medium budget and 3-6 month timeline
  3. Cold Lead Scenario: A buyer just starting to browse with no defined timeline

Here's what a conversation with a hot lead might look like:

Chatbot: Welcome to our real estate lead qualification chatbot! What is your full name?
User: John Smith
Chatbot: Nice to meet you, John! What is the best email address to reach you?
User: john.smith@example.com
...
Chatbot: What is your budget range for purchasing a property?
User: Around $600,000
...
Chatbot: How soon are you looking to purchase?
User: Within the next month
...
Chatbot: Thank you for providing all this information!

Lead Score: 92/100 (Hot)
Reasons:
- High budget range (30 points)
- Immediate purchase timeline (25 points)
- Pre-approved for mortgage (20 points)
- Working exclusively with us (15 points)
- Clear motivation for purchase (10 points)

Enhancing Your Chatbot

Now that you have a working lead qualification chatbot, consider these enhancements:

1. CRM Integration

Connect your chatbot to a CRM system to automatically:

  • Create new lead records
  • Update existing lead information
  • Schedule follow-up tasks based on lead quality

2. Advanced Question Flow

Implement branching conversation paths based on previous answers:

  • Ask different follow-up questions based on property type
  • Adjust questions based on buying vs. selling intent
  • Skip irrelevant questions based on earlier responses

3. Multi-agent Implementation

Expand the CrewAI implementation to use multiple specialized agents:

  • Qualification Agent: Focuses on gathering basic information
  • Knowledge Agent: Answers property-specific questions
  • Closing Agent: Handles scheduling and next steps

4. Enhanced Analytics

Add analytics to track and improve chatbot performance:

  • Conversion rates from different lead sources
  • Question completion rates
  • Average lead scores by source or property type
  • A/B testing different question sequences

Conclusion

Throughout this tutorial, we've built a sophisticated lead qualification chatbot that demonstrates the power of combining CrewAI's intelligent agents with Gradio's user-friendly interface. The system we've created can save real estate professionals countless hours by automatically:

  1. Engaging with potential leads in a natural conversation
  2. Gathering critical qualification information
  3. Scoring leads based on industry-standard criteria
  4. Categorizing leads for appropriate follow-up

This demo is just the beginning - you can extend it with new functionality to meet your specific business needs. The combination of CrewAI's flexible agent framework and Gradio's powerful interface capabilities opens up endless possibilities for automation and improvement in the lead qualification process.

The complete code is available on GitHub, and I welcome contributions from the community. Whether you're building this for a real estate agency or adapting it for another industry, the principles and patterns we've covered will serve as a solid foundation for your AI-powered lead qualification system.

I'd love to hear about your experience building chatbots with CrewAI flows! Connect with me on X (formerly Twitter) or LinkedIn to share your thoughts and questions.


AI should drive results, not complexity. AgentemAI helps businesses build scalable, efficient, and secure AI solutions. See how we can help.