Traditional IVRs make callers do the routing work.
You call a business, listen to a menu, remember the options, press a number, and hope you picked the right path. That works, but it is not how people naturally ask for help.
Most callers already know what they want:
The ai-powered-call-router
example turns that sentence into the routing decision.
Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-powered-call-router
This is a Python Flask app that handles inbound calls with Telnyx Call Control and classifies caller intent with Telnyx AI Inference.
The flow looks like this:
Inbound call
-> Telnyx sends a Call Control webhook
-> Flask verifies the webhook
-> app answers the call
-> app speaks a greeting
-> app gathers caller speech
-> AI classifies intent
-> app announces the transfer
-> Call Control transfers the caller
Instead of asking the caller to press buttons, the app asks what they need and routes based on their answer.
The example uses:
The source keeps the route table intentionally small:
ROUTE_TABLE = {
"billing": "+1XXXXXXXXXX",
"sales": "+1XXXXXXXXXX",
"support": "+1XXXXXXXXXX",
}
In a real app, those destinations might be queues, agents, contact center flows, or PBX extensions.
Once the inbound call is answered, the app plays a greeting. After the greeting finishes, it starts speech capture using Call Control's AI gather action.
The important detail is that the app waits for the call.speak.ended
event before starting the gather. That avoids the greeting and the caller's first response overlapping.
The gather step produces a call.ai_gather.ended
event with the caller's utterance.
That text becomes the input to the intent classifier.
The app sends the caller's transcribed request to Telnyx AI Inference and asks the model to classify it into one of the supported routes.
The default model in the sample is:
meta-llama/Llama-3.3-70B-Instruct
The model does not need to write a long answer. It only needs to return a routing label such as:
billing
sales
support
That keeps the AI part narrow and practical. The LLM is not running the whole call center. It is doing one job: mapping natural language to a route.
After intent classification, the app speaks a short announcement:
Transferring you to billing. Please hold.
Then it waits for that announcement to finish before calling the transfer action.
That makes the caller experience cleaner. They hear what is happening before the bridge is created.
The example uses a blind transfer: Telnyx dials the destination and connects the original caller once the destination answers.
Voice automation should not trust random HTTP requests.
The app verifies inbound Telnyx webhooks with Ed25519 signature verification before processing the event:
event = unwrap_with_ed25519(
request.get_data(),
request.headers,
key=TELNYX_PUBLIC_KEY,
)
That means call actions like answer, gather, and transfer only run after the app verifies the webhook came from Telnyx.
Clone the examples repo:
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/ai-powered-call-router
Create a virtual environment and install dependencies:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Create your .env
file:
cp .env.example .env
Set the required values:
TELNYX_API_KEY=<your_telnyx_api_key>
TELNYX_PUBLIC_KEY=<your_telnyx_public_key>
TELNYX_CONNECTION_ID=<your_call_control_application_id>
AI_MODEL=meta-llama/Llama-3.3-70B-Instruct
PORT=5000
Then run:
python app.py
Expose your local server with a tunnel and point your Telnyx Call Control Application webhook to:
https://<your-tunnel-domain>/webhook
This is a good example of where AI is useful without making the system vague.
The caller still moves through a deterministic telephony flow:
The model only handles the messy human-language part.
That is the sweet spot for a lot of production AI apps: use the LLM where language is ambiguous, but keep the workflow itself explicit and observable.
Before using this with real callers, I would add:
But the core idea is simple: callers should be able to say what they need, and your app should route them without forcing them through a rigid menu.