Real-Time Voice AI with LiveKit, WebRTC and FastAPI

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

5 October 2026 · 8 min read

A voice agent feels intelligent only when it feels fast. Latency, not model quality, is usually what makes a demo feel robotic. This article covers the architecture I use to build real-time voice AI systems with LiveKit, WebRTC and a FastAPI backend.

Why WebRTC and LiveKit

HTTP is the wrong transport for voice. WebRTC is built for real-time audio: it handles jitter, packet loss and echo cancellation, and it keeps a persistent, low-latency connection. LiveKit gives you a managed SFU and clean server SDKs so you do not have to write the signalling layer yourself.

  • Sub-second audio transport instead of request/response round trips.
  • Built-in echo cancellation, noise suppression and device handling.
  • A room model that scales from one caller to many participants.
  • Server-side SDKs in Python so the agent logic stays in one place.

The latency budget

Human conversation tolerates roughly 300 to 500 milliseconds of turn-taking delay before it feels unnatural. That budget is split across speech-to-text, the LLM and text-to-speech, so every stage has to stream.

  1. Capture: browser or telephony sends audio to the room.
  2. STT: stream partial transcripts as the user speaks.
  3. LLM: start generating as soon as a final transcript arrives.
  4. TTS: synthesise the first sentence while the model is still writing.
  5. Playback: push audio back into the room immediately.

Turn detection and interruptions

Fixed silence timers feel clumsy. A better approach combines voice activity detection with a semantic check: is the user actually done, or just pausing? Crucially, when a user speaks over the agent, you must cancel the in-flight generation and stop playback. Barge-in is what makes a voice agent feel like a conversation rather than a recording.

Connecting a LiveKit agent to FastAPI

Keep the real-time loop close to LiveKit and put business logic behind a FastAPI service. The agent streams audio and transcripts; FastAPI handles retrieval, tool calls and persistence.

from livekit.agents import JobContext, WorkerOptions, cli
from livekit.agents.voice import Agent, AgentSession
from livekit.plugins import deepgram, openai, silero

async def entrypoint(ctx: JobContext) -> None:
    await ctx.connect()

    session = AgentSession(
        vad=silero.VAD.load(),
        stt=deepgram.STT(model="nova-2"),
        llm=openai.LLM(model="gpt-4o-mini"),
        tts=openai.TTS(voice="alloy"),
    )

    agent = Agent(instructions="You are a helpful support agent. Be concise.")
    await session.start(room=ctx.room, agent=agent)

if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))

The FastAPI side stays a normal, testable service. The agent calls it for anything stateful — looking up an order, booking an appointment or fetching knowledge for a RAG step.

import httpx

async def lookup_order(order_id: str) -> dict:
    async with httpx.AsyncClient(timeout=2.0) as client:
        response = await client.get(f"https://api.example.com/orders/{order_id}")
        response.raise_for_status()
        return response.json()

What breaks in production

  • Network jitter: always degrade gracefully rather than freezing the call.
  • Cold starts: keep workers warm, because a slow first token ruins the first impression.
  • Accents and background noise: test STT on real audio, not studio samples.
  • Tool latency: give the agent something to say while a backend call is in flight.
  • Cost: streaming STT and TTS are billed per second, so measure both.