Systems Library / AI Model Setup / How to Implement Conversation Branching Logic
AI Model Setup advanced

How to Implement Conversation Branching Logic

Build conditional conversation flows that adapt based on user responses.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Linear chatbots feel like automated phone trees. They frustrate users because they do not adapt. Conversation branching logic lets your AI take different paths based on what users say, what data you extract from their responses, and what state they are in. I use this pattern to build qualification bots, onboarding flows, and intake forms that feel like talking to a smart human, not filling out a web form.

The business value is measurable. A branching qualification bot can pre-screen leads 24 hours a day, route hot leads to a calendar link, and send warm leads to a nurture sequence, all without a human touching anything until the meeting is booked.

What You Need Before Starting

Step 1: Define Your Conversation State

State is everything in a branching conversation. Track where the user is, what you know about them, and what branch they are on.

from pydantic import BaseModel
from typing import Optional, Literal
from enum import Enum

class ConversationBranch(str, Enum):
    INTAKE = "intake"
    QUALIFY_BUDGET = "qualify_budget"
    QUALIFY_TIMELINE = "qualify_timeline"
    BOOK_CALL = "book_call"
    NURTURE = "nurture"
    DISQUALIFY = "disqualify"
    COMPLETE = "complete"

class LeadState(BaseModel):
    name: Optional[str] = None
    company: Optional[str] = None
    problem: Optional[str] = None
    budget: Optional[str] = None
    timeline: Optional[str] = None
    is_decision_maker: Optional[bool] = None
    current_branch: ConversationBranch = ConversationBranch.INTAKE
    turn_count: int = 0
    qualified: Optional[bool] = None
    messages: list = []

Step 2: Build Branch-Specific System Prompts

Each branch has a different goal. The system prompt drives that goal.

BRANCH_PROMPTS = {
    ConversationBranch.INTAKE: """You are a friendly intake specialist. 
Your goal is to learn the user's name, company, and main problem.
Ask one question at a time. Be conversational, not formal.
When you have their name, company, and problem, say EXACTLY: [INTAKE_COMPLETE]""",

    ConversationBranch.QUALIFY_BUDGET: """You are qualifying a prospect's budget.
You already know their name and problem from earlier in the conversation.
Ask naturally about their budget range for solving this problem.
If they share a budget over $2,000/month, say EXACTLY: [BUDGET_QUALIFIED]
If they share a budget under $2,000/month or refuse to share, say EXACTLY: [BUDGET_LOW]""",

    ConversationBranch.QUALIFY_TIMELINE: """You are learning when the prospect wants to solve their problem.
Ask how soon they need this fixed.
If they say within 90 days, say EXACTLY: [TIMELINE_HOT]
If they say longer or unsure, say EXACTLY: [TIMELINE_WARM]""",

    ConversationBranch.BOOK_CALL: """This is a qualified prospect. Your job is to get them to book a call.
Be direct. Offer two specific time options.
Provide the booking link: https://calendly.com/example/30min
Say EXACTLY: [CALL_BOOKED] once they confirm interest in booking.""",

    ConversationBranch.NURTURE: """This prospect is not ready to buy yet.
Offer a free resource and invite them to a future conversation.
Be warm, not pushy. End with [NURTURE_COMPLETE]""",

    ConversationBranch.DISQUALIFY: """This prospect is not a good fit right now.
Be kind and direct. Wish them well and suggest they revisit in 6 months.
End with [DISQUALIFY_COMPLETE]""",
}

Step 3: Build the Branch Router

Extract intent signals from the model's output and route to the next branch.

def detect_signal(response: str) -> Optional[str]:
    signals = [
        "INTAKE_COMPLETE", "BUDGET_QUALIFIED", "BUDGET_LOW",
        "TIMELINE_HOT", "TIMELINE_WARM", "CALL_BOOKED",
        "NURTURE_COMPLETE", "DISQUALIFY_COMPLETE"
    ]
    for signal in signals:
        if f"[{signal}]" in response:
            return signal
    return None

def route_branch(signal: str, state: LeadState) -> ConversationBranch:
    routing = {
        "INTAKE_COMPLETE": ConversationBranch.QUALIFY_BUDGET,
        "BUDGET_QUALIFIED": ConversationBranch.QUALIFY_TIMELINE,
        "BUDGET_LOW": ConversationBranch.DISQUALIFY,
        "TIMELINE_HOT": ConversationBranch.BOOK_CALL,
        "TIMELINE_WARM": ConversationBranch.NURTURE,
        "CALL_BOOKED": ConversationBranch.COMPLETE,
        "NURTURE_COMPLETE": ConversationBranch.COMPLETE,
        "DISQUALIFY_COMPLETE": ConversationBranch.COMPLETE,
    }
    return routing.get(signal, state.current_branch)

Step 4: Build the Main Conversation Handler

Tie everything together. Each turn: get context, call the model, detect signals, update state, route.

import openai
import re

client = openai.OpenAI(api_key="YOUR_API_KEY")

def extract_facts(response: str, state: LeadState) -> LeadState:
    """Parse facts from model responses to update state."""
    # In production, use structured outputs for this
    # This is a simplified version
    if not state.name:
        name_match = re.search(r"(?:name is|I'm|I am|call me)\s+([A-Z][a-z]+)", response)
        if name_match:
            state.name = name_match.group(1)
    return state

def chat_turn(user_message: str, state: LeadState) -> tuple[str, LeadState]:
    state.turn_count += 1
    system_prompt = BRANCH_PROMPTS.get(state.current_branch, BRANCH_PROMPTS[ConversationBranch.INTAKE])

    # Add context about what we know
    context_lines = []
    if state.name:
        context_lines.append(f"User's name: {state.name}")
    if state.company:
        context_lines.append(f"Company: {state.company}")
    if state.problem:
        context_lines.append(f"Problem: {state.problem}")

    if context_lines:
        system_prompt += "\n\nContext you already have:\n" + "\n".join(context_lines)

    messages = [{"role": "system", "content": system_prompt}]
    messages.extend(state.messages[-8:])  # Last 8 turns
    messages.append({"role": "user", "content": user_message})

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        temperature=0.4
    )

    reply = response.choices[0].message.content

    # Clean signal tags from user-visible output
    clean_reply = re.sub(r'\[[\w_]+\]', '', reply).strip()

    # Detect routing signal
    signal = detect_signal(reply)
    if signal:
        new_branch = route_branch(signal, state)
        state.current_branch = new_branch

    # Update conversation history
    state.messages.extend([
        {"role": "user", "content": user_message},
        {"role": "assistant", "content": clean_reply}
    ])

    # Extract facts from conversation
    state = extract_facts(user_message, state)

    return clean_reply, state

Step 5: Add a Max-Turn Guardrail

Prevent infinite loops. If a user is stuck in a branch too long, escalate or exit gracefully.

BRANCH_MAX_TURNS = {
    ConversationBranch.INTAKE: 5,
    ConversationBranch.QUALIFY_BUDGET: 3,
    ConversationBranch.QUALIFY_TIMELINE: 3,
    ConversationBranch.BOOK_CALL: 4,
    ConversationBranch.NURTURE: 2,
}

def check_branch_timeout(state: LeadState, branch_turn_count: int) -> Optional[ConversationBranch]:
    max_turns = BRANCH_MAX_TURNS.get(state.current_branch)
    if max_turns and branch_turn_count >= max_turns:
        # Escalate to human or move to nurture
        if state.current_branch in [ConversationBranch.QUALIFY_BUDGET, ConversationBranch.QUALIFY_TIMELINE]:
            return ConversationBranch.NURTURE
    return None

Step 6: Run a Full Conversation

def run_qualification_flow():
    state = LeadState()
    print("Bot: Hi! I'm here to see if we can help you. What's your name and what challenge are you facing?")

    while state.current_branch != ConversationBranch.COMPLETE:
        user_input = input("You: ").strip()
        if not user_input:
            continue

        reply, state = chat_turn(user_input, state)
        print(f"Bot: {reply}")
        print(f"[Branch: {state.current_branch.value}]")

    print("\nConversation complete.")
    print(f"Lead summary: Name={state.name}, Qualified={state.qualified}")

run_qualification_flow()

What to Build Next

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems