Interacting with artificial intelligence through a web browser or a smartphone chat app gets old fast. Typing out prompts while you are knee-deep in a server rack with both hands full of Ethernet cables is clumsy and impractical. I wanted something much more direct: I wanted to pick up a physical desk phone, dial an extension, and talk to my local AI assistant.
In our lab, we run a full enterprise VoIP environment powered by FreePBX 17 and Asterisk. By bridging Asterisk’s audio stream directly into our local AI pipeline on DEATHSTAR, I can pick up any Yealink desk phone in the office, dial Extension 102, and have a voice conversation with Jarvis. Here is how the telephony pipeline works and how we got latency low enough for natural speech.
1. The Telephony Foundation: FreePBX in Host Mode
FreePBX runs containerized on docker02 within our dedicated VoIP network (NET-VOIP-VLAN60 at 10.1.20.19). Because real-time audio (RTP UDP ports 10000–20000) suffers severe jitter and NAT issues when pushed through Docker’s virtual bridge network, the container runs in host networking mode:
# FreePBX compose configuration excerpt
services:
freepbx:
image: escomputers/freepbx:17-nofail2ban
network_mode: "host" # Required for clean UDP/RTP media streaming
restart: unless-stopped
volumes:
- ./freepbx_data:/data
Our office hardware phones (Yealink SIP-T46S endpoints) auto-provision over VLAN 60 through our internal TFTP/HTTP provisioning service, while softphones (BareSIP and Linphone) run on laptops for mobile testing.
2. The Audio Pipeline: Bridging Asterisk to Local AI
Making a phone call work with a large language model requires stringing together four discrete micro-operations in sub-second time:
- Inbound Call Capture (Asterisk AGI): When Extension
102is dialed, Asterisk triggers a custom FastAGI daemon written in Python. It answers the call, establishes a bi-directional raw audio stream (8kHz 16-bit PCM mono), and listens for speech. - Speech-to-Text (Whisper): Audio packets stream into our local faster-whisper engine running on the
DEATHSTARGPU. As soon as a natural pause in speech is detected (VAD silence threshold), Whisper transcribes the speech to text (typically ~180ms). - LLM Inference (Ollama / Local Models): The text transcript passes to the local LLM with our lab system prompt and tool definitions. Jarvis checks whether the query is a question (e.g. “What is the status of docker02?”) or an operational request.
- Text-to-Speech Streaming (Piper): Rather than waiting for the entire LLM response to generate, tokens stream directly into Piper TTS. Piper synthesizes speech chunks in parallel, converts the audio to 8kHz μ-law format, and pipes it back into the Asterisk channel in real time.
3. Defeating Audio Latency
The Golden Rule of Voice AI: Anything over 1.2 seconds of conversational latency feels awkward and robotic. If you wait for a 400-word LLM reply to finish generating before starting speech synthesis, the phone call feels dead.
We achieved an average conversational turnaround of 650–750 milliseconds by implementing sentence-boundary streaming: the moment the LLM generates a complete clause (a comma or period), Piper immediately begins audio playback on the phone while the LLM continues writing the remainder of the sentence.
4. Real-World Use: Rack Diagnostics & Disaster Callouts
Being able to dial your AI from an office phone has proved surprisingly practical:
- Hands-Free Rack Diagnostics: When I’m in the server closet troubleshooting cabling, I pick up the cordless handset, dial
102, and ask: “Jarvis, is SW-CORE-01 port 14 linking up?” Jarvis checks the UniFi API and speaks the port state directly in my ear. - Automated Emergency Callouts: If a Disaster-level trigger fires in Zabbix (e.g., an EqualLogic iSCSI path drops or a host loses power), FreePBX can automatically initiate an outbound SIP call to the office desk phone, announce the incident over speakerphone, and ask for acknowledgment.
It combines 30-year-old bulletproof telephony protocols with cutting-edge local machine learning—fast, completely private, and running entirely within our own walls.