How to Implement Conversation Branching Logic
Build conditional conversation flows that adapt based on user responses.
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
- Python 3.9+
- OpenAI API key
- A clear map of your conversation flow (draw it on paper first, seriously)
pydanticfor state management
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
- Replace the simple regex fact extraction with structured outputs so you capture lead data accurately into a Pydantic model that syncs to your CRM
- Add a webhook trigger at the CALL_BOOKED signal to fire a Zapier zap that creates the CRM contact and sends the confirmation email
- Build an analytics layer that tracks conversion rates between each branch so you can identify where leads are dropping off
Related Reading
- How to Write System Prompts That Control AI Behavior - branch-specific prompts need tight engineering to stay on task
- How to Build AI Guardrails for Safe Outputs - qualification bots that go off-script can be a liability
- How to Build Persona-Based AI Assistants - combine branching with a strong persona for a more natural experience
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