Building an AI Calling Platform with Python, LiveKit and Telephony

By Asadullah Khan · AI & Voice AI Engineer, Karachi, Pakistan

10 October 2026 · 8 min read

An AI calling platform is more than a voice agent with a phone number attached. It is telephony, media transport, agent logic and call state working together — plus the compliance rules that come with handling real phone calls. This is the architecture I use to build them.

The five layers

  1. Telephony: a phone number and a SIP trunk from a carrier.
  2. Media transport: a WebRTC room that carries the audio.
  3. Agent: speech-to-text, the LLM and text-to-speech in a loop.
  4. Call state: who called, when, why and what happened.
  5. Compliance: consent, recording disclosure, retention and opt-out.

Telephony: getting calls in and out

You need a carrier that can route a phone number to a SIP endpoint. Common choices are Twilio, Telnyx and Plivo. The carrier does not run your agent; it hands the audio to your media layer over SIP and gets audio back.

  • Inbound: the carrier forwards calls to your SIP endpoint.
  • Outbound: your platform originates a call through the carrier API.
  • Always buy numbers in the regions you actually serve.
  • Test DTMF, call transfers and voicemail detection early.

Wiring telephony into LiveKit

LiveKit provides a SIP bridge so that a phone call becomes a room participant. That means your agent code does not need to know whether the caller is in a browser or on a phone — it just joins a room and speaks.

Originating a call is a normal API call to your carrier or your own orchestration service.

import httpx

async def start_outbound_call(to_number: str, agent_id: str) -> str:
    payload = {"to": to_number, "from": OUTBOUND_NUMBER, "agent": agent_id}
    async with httpx.AsyncClient(timeout=5.0) as client:
        response = await client.post(
            "https://api.carrier.example/v1/calls",
            json=payload,
            headers={"Authorization": f"Bearer {CARRIER_TOKEN}"},
        )
        response.raise_for_status()
        return response.json()["call_id"]

Call state is the hard part

Voice agents fail in messy ways: the caller hangs up mid-sentence, the network drops, or two webhooks arrive for the same call. Model call state explicitly so the system can recover.

  • Store one row per call with a status: queued, ringing, in_progress, completed, failed.
  • Treat webhooks as at-least-once: make handlers idempotent with a unique event ID.
  • Persist transcripts and outcomes against the call ID.
  • Expire stale calls with a scheduled job instead of leaving rows open forever.

Compliance is not optional

Automated calls are regulated. Depending on where your callers are, you may need prior consent, a recording disclosure, an opt-out mechanism and a defined retention period. In Pakistan, telemarketing and recording are governed by PTA rules; in the EU and UK, GDPR applies to the personal data in every call.

  • Disclose automated calling and recording at the start of the call.
  • Maintain a do-not-call list and honour opt-outs immediately.
  • Encrypt recordings and set a deletion schedule.
  • Log consent with a timestamp and the exact wording shown.

Costs and scaling

Voice is billed by the second across several layers: telephony minutes, STT, TTS and LLM tokens. Measure the cost per call as a first-class metric, because latency and cost both scale linearly with talk time. Keep workers warm to avoid cold-start silence, and cap concurrent calls per worker so one busy period cannot starve the rest.