Systems Library / AI Capabilities / How to Create an AI Voice IVR System
AI Capabilities voice audio

How to Create an AI Voice IVR System

Build an AI-powered phone system that handles calls intelligently.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

An ai voice ivr phone system automated with natural language understanding replaces the painful "press 1 for sales, press 2 for support" menu with a conversational experience. I build these for businesses handling 50+ inbound calls daily. The caller says what they need in plain language, the AI understands the intent, and routes or resolves it.

Callers get answers faster. Your team handles fewer repetitive calls.

What You Need Before Starting

Step 1: Set Up Twilio Voice Webhook

from flask import Flask, request
from twilio.twiml.voice_response import VoiceResponse, Gather

app = Flask(__name__)

@app.route("/incoming-call", methods=["POST"])
def incoming_call():
    response = VoiceResponse()
    gather = Gather(
        input="speech",
        action="/process-speech",
        speech_timeout="auto",
        language="en-US"
    )
    gather.say("Hello, thanks for calling. How can I help you today?", voice="Polly.Joanna")
    response.append(gather)
    response.say("I did not catch that. Let me connect you to our team.")
    response.redirect("/connect-agent")
    return str(response)

Step 2: Process Speech with AI

import anthropic

client = anthropic.Anthropic()

INTENT_MAP = {
    "billing": {"action": "transfer", "number": "+15551234001", "department": "Billing"},
    "support": {"action": "transfer", "number": "+15551234002", "department": "Support"},
    "sales": {"action": "transfer", "number": "+15551234003", "department": "Sales"},
    "hours": {"action": "answer", "response": "We are open Monday through Friday, 9 AM to 6 PM Eastern time."},
    "address": {"action": "answer", "response": "We are located at 123 Main Street, Suite 200."},
}

@app.route("/process-speech", methods=["POST"])
def process_speech():
    speech_result = request.form.get("SpeechResult", "")
    caller = request.form.get("From", "unknown")

    intent = classify_intent(speech_result)
    return handle_intent(intent, speech_result, caller)

def classify_intent(speech_text):
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=50,
        messages=[{
            "role": "user",
            "content": f"""Classify this caller's intent into ONE category: billing, support, sales, hours, address, other

Caller said: "{speech_text}"

Respond with ONLY the category name."""
        }]
    )
    return response.content[0].text.strip().lower()

Step 3: Handle Different Intents

from twilio.twiml.voice_response import VoiceResponse

def handle_intent(intent, speech_text, caller):
    response = VoiceResponse()
    action = INTENT_MAP.get(intent, {"action": "transfer", "number": "+15551234000"})

    if action["action"] == "answer":
        response.say(action["response"], voice="Polly.Joanna")
        gather = Gather(input="speech", action="/process-speech", speech_timeout="auto")
        gather.say("Is there anything else I can help with?", voice="Polly.Joanna")
        response.append(gather)

    elif action["action"] == "transfer":
        response.say(f"I will connect you to our {action.get('department', '')} team now.", voice="Polly.Joanna")
        response.dial(action["number"])

    log_call(caller, speech_text, intent, action["action"])
    return str(response)

Step 4: Add FAQ Handling

Answer common questions without transferring:

FAQ_RESPONSES = {
    "What are your hours?": "We are open Monday through Friday, 9 AM to 6 PM Eastern.",
    "Where are you located?": "Our office is at 123 Main Street, Suite 200.",
    "What is your return policy?": "You can return any item within 30 days for a full refund.",
}

def check_faq(speech_text):
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=200,
        system=f"Answer the caller's question using ONLY these FAQs:\n{json.dumps(FAQ_RESPONSES)}\nIf the question is not in the FAQs, respond with 'TRANSFER'.",
        messages=[{"role": "user", "content": speech_text}]
    )
    answer = response.content[0].text
    if "TRANSFER" in answer:
        return None
    return answer

Step 5: Track Call Analytics

def log_call(caller, speech, intent, action):
    conn = sqlite3.connect("ivr.db")
    conn.execute("""
        INSERT INTO call_log (caller, speech_text, intent, action, called_at)
        VALUES (?, ?, ?, ?, datetime('now'))
    """, (caller, speech, intent, action))
    conn.commit()

def get_ivr_report(days=7):
    conn = sqlite3.connect("ivr.db")
    start = f"-{days} days"
    total = conn.execute("SELECT COUNT(*) FROM call_log WHERE called_at > datetime('now', ?)", (start,)).fetchone()[0]
    by_intent = conn.execute("SELECT intent, COUNT(*) FROM call_log WHERE called_at > datetime('now', ?) GROUP BY intent", (start,)).fetchall()
    answered = conn.execute("SELECT COUNT(*) FROM call_log WHERE action = 'answer' AND called_at > datetime('now', ?)", (start,)).fetchone()[0]

    return {"total_calls": total, "self_served": answered, "by_intent": dict(by_intent)}

What to Build Next

Add voicemail handling. When nobody answers, the AI takes a detailed message, transcribes it, classifies the urgency, and routes it to the right person with a summary. No more checking voicemail.

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