LiveKit Agents

This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Written on 2026-10-01 against livekit-agents 1.8.3 (Python) with livekit-plugins-turn-detector 1.8.3, the LiveKit server 1.13.7, the LiveKit Node.js Agents SDK, LangChain 1.4.3 (create_agent) and FastAPI 0.142.2 on Python 3.12 (see the version baseline). Nothing here was run: a LiveKit agent needs a LiveKit server or Cloud project, live WebRTC audio, provider API keys and, for the telephony part, a SIP provider. Every code block and JSON document was written from the official documentation linked after it, and the Python names (AgentServer, TurnHandlingOptions, inference.*) follow the current documentation, which has been renamed more than once between releases, so check the SDK reference for the exact release you install. The turn-detector plugin snippet and the Node.js snippet are the least certain, because the pages describing them were not retrievable when this page was written.

LiveKit is an open-source real-time media platform built on WebRTC: an SFU (selective forwarding unit) that routes audio, video and data between participants in rooms. LiveKit Agents is a framework on top of it that lets a program join a room as a participant and talk to people. For the DocsAssistant that gives you a code-first alternative to the hosted ElevenLabs Agents platform and a transport-and-telephony-complete alternative to the Python-only frame pipeline of Pipecat: you own the agent code and the model choices, and LiveKit supplies the room, the WebRTC transport, the SIP bridge, and the process management.

Rooms, agent servers and jobs

Four terms carry the whole model:

Term Meaning

Room

A session in which participants (browsers, mobile apps, SIP callers, agents) publish and subscribe to audio tracks over WebRTC. Rooms are created on demand and are the unit of isolation.

Participant

A connection to a room. A phone caller bridged through SIP appears as an ordinary participant with SIP attributes.

Agent server (worker)

A long-running process that you write and run. It registers with the LiveKit server and announces that it can accept work. Older releases and much of the community material call it a worker; the current documentation says agent server.

Job

One unit of work: "put an agent in this room". When a room needs an agent, the LiveKit server dispatches a job to a registered agent server, which starts a subprocess that joins the room and runs your entrypoint.

Because each job is a subprocess, a crash or a memory leak in one call does not take down the others, and capacity is controlled by how many agent-server processes you run and how many jobs each accepts (load reporting lets the LiveKit server stop dispatching to a busy process). Dispatch is automatic by default (every new room gets an agent) or explicit: give the agent a name and dispatch it through the API or a SIP dispatch rule, which is what a telephone deployment uses.

flowchart LR subgraph Callers B["Browser or mobile app (WebRTC)"] P["Phone (PSTN)"] end P --> SP["SIP provider trunk"] SP --> SIP["LiveKit SIP service"] B --> LK SIP -->|"SIP participant"| LK["LiveKit server (rooms, SFU)"] LK -->|"job dispatch"| AS["Agent server (registered process)"] AS --> J["Job subprocess: entrypoint(ctx)"] J -->|"joins room as participant"| LK J --> S["AgentSession: VAD, STT, turn detector, LLM, TTS"] S --> CORE["DocsAssistant core: ask(conversationId, userId, text)"] CORE --> R[(Retriever, memory, ACL filter)]

The agent talks to the room, not to the caller’s network, so the same agent code serves a browser tab and a telephone call. The caller-to-room leg and its WebRTC details are the subject of WebRTC Data Channels and, for audio in the browser, of Voice in the Browser.

A first agent with AgentSession

AgentSession is the orchestrator inside a job. It owns the audio input and output, turn detection and interruption handling, and the model components, and it runs one active Agent (instructions plus tools) at a time. Agent subclasses are the unit you can hand a conversation between, for example from a greeter to a specialist. The model components can be constructed with plugins (livekit.plugins.openai, deepgram, elevenlabs, cartesia, …​) or, in the current documentation, through LiveKit’s inference gateway, which selects hosted STT, LLM and TTS models by name from LiveKit Cloud.

agent.py: the minimal LiveKit voice agent (STT, LLM, TTS pipeline)
from dotenv import load_dotenv

from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, TurnHandlingOptions, inference

load_dotenv(".env.local")


class DocsAssistantVoice(Agent):
    def __init__(self) -> None:
        super().__init__(
            instructions=(
                "You answer questions about the product documentation. "
                "Reply in short plain sentences with no markdown, lists or symbols."
            ),
        )


server = AgentServer()


@server.rtc_session(agent_name="docs-assistant")
async def entrypoint(ctx: agents.JobContext):
    session = AgentSession(
        stt=inference.STT(model="assemblyai/universal-3-5-pro", language="en"),
        llm=inference.LLM(model="google/gemma-4-31b-it"),
        tts=inference.TTS(model="fishaudio/s2.1-pro", voice="fa4c9eb3dccc4806b382b40d61c6b10a"),
        turn_handling=TurnHandlingOptions(turn_detection=inference.TurnDetector()),
    )
    await session.start(room=ctx.room, agent=DocsAssistantVoice())
    await session.generate_reply(instructions="Greet the caller and say you are an AI assistant.")


if __name__ == "__main__":
    agents.cli.run_app(server)

Source: LiveKit docs: Voice AI quickstart (the model names are the quickstart’s examples and change often; pick yours from the model pages). The file registers an agent server under the dispatch name docs-assistant; python agent.py dev (or lk agent dev) runs it locally against your project, and python agent.py start runs the production mode. The greeting states that the caller is speaking to an AI, which matters on telephone lines (see Telephony with Twilio).

Pipeline or realtime

The same AgentSession supports the two architectures described on Voice Architectures. The pipeline form above is the cascade: separate STT, LLM and TTS, with a turn detector deciding when the user has finished. The realtime form gives the session a speech-to-speech model as its llm and lets the provider handle recognition and synthesis; provider-side turn detection then applies, and you trade control for a lower latency floor, as discussed on Realtime APIs.

agent_realtime.py: realtime model, and realtime model with a separate TTS
from livekit.agents import AgentSession
from livekit.plugins import openai

# speech in, speech out, handled by the provider
session = AgentSession(llm=openai.realtime.RealtimeModel(voice="marin"))

# speech in, text out from the realtime model, a TTS of your choice speaks the reply
session = AgentSession(
    llm=openai.realtime.RealtimeModel(modalities=["text"]),
    tts="fishaudio/s2.1-pro:fa4c9eb3dccc4806b382b40d61c6b10a",
)

Source: LiveKit docs: OpenAI Realtime API plugin. Gemini Live has an equivalent plugin. The second form is useful when the voice must be a particular cloned or branded voice while the model keeps native audio understanding; it gives up the model’s own prosody. Whether the realtime model or a pipeline hits your latency budget is measured with the methods in Production Voice Agents, not assumed.

Turn detection and interruptions

The framework’s voice-activity detection (Silero) finds speech and silence, but silence is a poor proxy for "finished": a pause in the middle of a sentence is not an end of turn. LiveKit therefore ships a small turn detector language model that reads the transcript so far, together with the acoustic signal, and predicts whether the speaker has finished. It runs on the agent’s CPU in tens of milliseconds, is released as the livekit-plugins-turn-detector package (1.8.3, in step with livekit-agents), and has English and multilingual variants whose weights are fetched with a download-files command before the first run. The general theory, including why endpointing delay trades latency for cut-offs, is on Turn-Taking and Interruptions.

agent_turns.py: turn detector plugin and interruption options
from livekit.agents import AgentSession, TurnHandlingOptions
from livekit.plugins import silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel

session = AgentSession(
    vad=silero.VAD.load(),
    turn_handling=TurnHandlingOptions(
        turn_detection=MultilingualModel(),
        interruption={
            "enabled": True,
            "mode": "adaptive",
            "false_interruption_timeout": 2.0,
            "resume_false_interruption": True,
        },
    ),
    # stt=..., llm=..., tts=...
)

Source: LiveKit docs: Turn detection and interruptions (the interruption keys and TurnHandlingOptions; the MultilingualModel import path follows the plugin’s package layout and could not be re-checked against the current page). Install with pip install "livekit-agents[silero,turn-detector]" and run python agent.py download-files once, also in the container image build, so that the weights are not downloaded on the first call. The hosted inference.TurnDetector() in the earlier example runs the equivalent model on LiveKit Cloud. A false interruption is a cough or a "mm-hm" that stops the agent without a real barge-in; the resume option lets the agent continue the sentence it was saying.

Tools that call the RAG core

A voice agent that answers from documentation has two designs. In the first, the voice layer is only an adapter: every user turn is forwarded to the same ask(conversationId, userId, text) operation that the text channels use, and the reply is spoken as it streams (the approach used for ElevenLabs' custom LLM). In the second, the LiveKit LLM is the front-line model and the DocsAssistant is a tool that it calls when it needs facts. The second keeps small talk, clarification and "one moment" phrases cheap and local, and fits LiveKit’s tool model.

docs_tool.py: a function tool that calls DocsAssistant
import httpx

from livekit.agents import Agent, RunContext, function_tool

DOCS_URL = "https://docsassistant.internal/ask"


class DocsAssistantVoice(Agent):
    def __init__(self, user_id: str) -> None:
        super().__init__(
            instructions=(
                "You are a voice assistant for the product documentation. "
                "Call search_docs for any factual question and read the answer in one or two short sentences."
            ),
        )
        self._user_id = user_id

    @function_tool()
    async def search_docs(self, context: RunContext, question: str) -> str:
        """Look up an answer in the product documentation.

        Args:
            question: the user's question, rephrased as a self-contained sentence.
        """
        async with httpx.AsyncClient(timeout=8.0) as client:
            response = await client.post(
                DOCS_URL,
                json={"conversationId": context.session.userdata.get("thread_id"),
                      "userId": self._user_id,
                      "text": question},
            )
        response.raise_for_status()
        return response.json()["answer"]     # short, plain text, no markdown or URLs

Source: LiveKit docs: Tool definition and use (function tools are methods decorated with @function_tool, with the docstring becoming the tool description; RunContext gives access to the session and its data). The user ID should come from the verified caller identity, not from what the model passes in, so the ACL filter of Access Control and Privacy still applies end to end. The endpoint can be the FastAPI service of Serving RAG as an API. A tool call adds a model round trip plus retrieval to the first-audio latency, so have the agent say a short filler before long lookups, or run the lookup as a background tool, and return answers already shortened for the ear. Reasoning about which tool a model selects, and the injection risks of reading retrieved text aloud, are covered on Tools and in Prompt Injection.

MCP servers can also be attached to an agent (Python SDK), which is the route when the DocsAssistant already runs behind an MCP endpoint as in RAG Behind MCP.

LangChain and LangGraph integration

If the DocsAssistant is already a LangChain 1.4 create_agent graph, the livekit-agents[langchain] extra provides an LLMAdapter that wraps a compiled LangGraph graph (which is what create_agent returns) so that it acts as the session’s llm. The graph keeps its tools, retrieval and checkpointed memory; LiveKit supplies speech and turn-taking.

agent_langchain.py: a LangChain agent as the LiveKit LLM
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from livekit.agents import Agent, AgentSession
from livekit.plugins import langchain as lk_langchain

from docsassistant.tools import search_docs     # LangChain tool over the retriever

graph = create_agent(
    model="openai:gpt-4.1-mini",
    tools=[search_docs],
    system_prompt="Answer from the documentation in short spoken sentences without markdown.",
    checkpointer=InMemorySaver(),
)

session = AgentSession(
    llm=lk_langchain.LLMAdapter(graph, config={"configurable": {"thread_id": "call-123"}}),
    # stt=..., tts=..., turn_handling=...
)

Source: LiveKit docs: LangChain and LangGraph integration (adapter parameters graph, stream_mode, config, context, subgraphs; the import alias and the exact constructor call follow that page’s description and were not run). The adapter supports graphs only: a plain LCEL chain such as prompt | llm or a bare chat model is not accepted. The default messages stream mode forwards LLM tokens as they arrive, which gives the best time to first audio; custom mode lets a node write explicit text such as "looking that up" through the stream writer. Use the room name or call ID as the thread_id, so memory follows the call, as described in Short-Term Memory and Checkpointers and, for streaming, in Streaming.

LiveKit SIP: phone calls into and out of rooms

LiveKit SIP is a service that bridges a SIP provider (a Twilio Elastic SIP trunk, Telnyx, a carrier, or your own Asterisk) to LiveKit rooms. A caller becomes a participant, so the agent does not know or care that the audio came from a telephone. Three objects configure it:

  • Inbound trunk. Accepts calls from your provider, restricted by source IP, called number or credentials.

  • Dispatch rule. Decides which room an inbound call lands in (one room per caller, a shared room, or one per PIN) and which agent to dispatch into it.

  • Outbound trunk. Holds the provider address and credentials used to place calls; you place a call by creating a SIP participant in a room.

inbound-trunk.json and dispatch-rule.json: inbound calls to a named agent
{
  "trunk": {
    "name": "docs-inbound",
    "numbers": ["+15105550100"]
  }
}
{
  "dispatch_rule": {
    "name": "docs-dispatch",
    "rule": { "dispatchRuleIndividual": { "roomPrefix": "call-" } },
    "roomConfig": {
      "agents": [{ "agentName": "docs-assistant", "metadata": "inbound-support" }]
    }
  }
}

Source: LiveKit docs: Dispatch rules (the numbers field is from the SIP inbound-trunk API reference; the page example sets only the name). Create them with lk sip inbound create inbound-trunk.json and lk sip dispatch create dispatch-rule.json, then point the provider’s trunk at your LiveKit SIP endpoint. An individual rule creates a new room per caller, so every call gets its own job and its own agent, and the dispatch metadata arrives in the job as ctx.job.metadata. Create the rule once and reuse it; do not create one per call.

outbound.py: the agent places an outbound call
import json

from livekit import agents, api

server = agents.AgentServer()


@server.rtc_session(agent_name="docs-outbound")
async def entrypoint(ctx: agents.JobContext):
    dial_info = json.loads(ctx.job.metadata)
    phone_number = dial_info["phone_number"]

    try:
        await ctx.api.sip.create_sip_participant(
            api.CreateSIPParticipantRequest(
                room_name=ctx.room.name,
                sip_trunk_id="ST_xxxx",
                sip_call_to=phone_number,
                participant_identity=phone_number,
                wait_until_answered=True,
            )
        )
    except api.SipCallError as e:
        print(f"call failed: {e.sip_status_code} {e.sip_status}")
        ctx.shutdown()
        return
    # ... then start the AgentSession as in the first example

Source: LiveKit docs: Make outbound calls. The outbound trunk is created with lk sip outbound create from a JSON file with the provider address, numbers and credentials. Dispatching the job with the number in its metadata (lk dispatch create --agent-name docs-outbound --metadata …​ or the API) keeps the dialing code out of the front end. For an outbound call, start the speech pipeline only after the callee answers, and watch for voicemail: a machine greeting must not be treated as a person (see Production Voice Agents). Provider-side setup is on Telephony with Twilio and Telephony with Asterisk and SIP.

Self-hosted or LiveKit Cloud

The LiveKit server is open source (Apache 2.0) and the same SDKs and agent code run against either deployment.

Aspect LiveKit Cloud Self-hosted

Operations

Managed SFU network, SIP, scaling, observability; optional managed agent hosting (lk agent deploy) so you do not run agent servers yourself.

You run the server (1.13.7 here), Redis for multi-node, TURN, TLS, SIP and your own agent servers.

Network

Global edge; clients connect to a nearby point of presence.

Whatever regions you deploy; you need a domain and a trusted TLS certificate, TURN/TLS for restrictive firewalls, and an RTC UDP port range.

Models

inference gateway for hosted STT, LLM, TTS and the turn detector; no provider accounts needed for those.

Use provider plugins with your own keys, or self-hosted models; the turn detector runs on the agent’s CPU.

Data location

Audio and transcripts traverse LiveKit’s infrastructure (regional restrictions exist).

Stays in your network and in the region you choose, which is the usual reason for self-hosting.

Cost model

Per-minute and per-resource pricing (see the pricing page).

Servers and bandwidth; scaling is bound by CPU and network bandwidth.

livekit.yaml: a minimal self-hosted server configuration
port: 7880
rtc:
  port_range_start: 50000
  port_range_end: 60000
  tcp_port: 7881
  use_external_ip: true
redis:
  address: redis:6379
keys:
  APIkeyExample: "replace-with-a-long-random-secret"
turn:
  enabled: true
  domain: turn.example.com
  tls_port: 5349

Source: LiveKit docs: Deploy to a VM (requirements: domain with a trusted certificate, load balancer or reverse proxy for TLS, embedded TURN, Redis for distributed mode, an RTC port range, use_external_ip in clouds, host networking for Docker). Key names are from the server configuration reference; verify them against 1.13.7. For a local trial, livekit-server --dev starts with built-in development keys, and the container images are covered on the pages under Docker. Agent servers are ordinary stateless containers: they need the LiveKit URL and API key and secret, plus the model-provider keys, and scale by adding replicas.

The Node.js variant

The Agents framework also exists for Node.js and TypeScript (@livekit/agents with plugin packages such as @livekit/agents-plugin-openai). The concepts are identical (agent server, job, voice.Agent, voice.AgentSession, function tools with a schema), which makes it the natural choice when the DocsAssistant back end and the front end are already TypeScript, for instance next to the Quarkus or Spring services of the earlier pages calling it over HTTP.

agent.ts: the Node.js equivalent of the first example
import { type JobContext, WorkerOptions, cli, defineAgent, voice } from '@livekit/agents';
import * as openai from '@livekit/agents-plugin-openai';
import { fileURLToPath } from 'node:url';

export default defineAgent({
  entry: async (ctx: JobContext) => {
    await ctx.connect();
    const agent = new voice.Agent({
      instructions: 'You answer questions about the product documentation in short plain sentences.',
    });
    const session = new voice.AgentSession({
      llm: new openai.realtime.RealtimeModel({ voice: 'marin' }),
    });
    await session.start({ agent, room: ctx.room });
  },
});

cli.runApp(new WorkerOptions({ agent: fileURLToPath(import.meta.url) }));

Source: LiveKit docs: Voice AI quickstart (Node.js tab). The Node snippet is reconstructed from the SDK’s structure and was not checked line by line. The Node SDK has fewer plugins than the Python SDK and, as noted in the documentation, no MCP support; the LangChain adapter is Python only. For a Java shop that wants no Python process at all, the practical route is the Node agent calling the Spring AI service over HTTP, or the realtime route on Realtime APIs.

Alternatives and what to avoid

  • Pipecat is the other mainstream open-source Python framework, strongest in frame-level control of the pipeline and transports; see Pipecat. Pipecat can use LiveKit as a transport, so the choice is not exclusive.

  • TEN Framework is an open-source framework for real-time conversational agents built from extensions, with its own graph-based runtime. It is worth evaluating if you want a multi-language, extension-oriented design; its community and ecosystem are smaller than LiveKit’s and Pipecat’s.

  • Kyutai Unmute and Moshi are open-weight research and open-source systems. Moshi is a full-duplex speech-to-speech model, and Unmute wraps a text LLM with Kyutai’s streaming STT and TTS. They matter when audio must stay on your own GPUs and you accept the integration work; they are not a framework for rooms, telephony or tools, so they pair with one of the frameworks above. The model side is introduced on Speech Models.

  • Vocode was an early open-source voice-agent library. It is dormant and no longer actively developed, so this guide does not recommend it for new work.

Choosing LiveKit Agents

Choose LiveKit Agents when you need telephony and browser or mobile clients on the same agent, want an open-source stack you can self-host later, and are comfortable running agent-server processes. Its SIP service, dispatch rules and process model are the main differentiators; the turn detector and the interruption handling are good defaults for a conversational agent.

Situation Leaning

Fastest path to a talking prototype, no infrastructure

ElevenLabs Agents or LiveKit Cloud with inference models.

DocsAssistant already a LangChain graph, calls by phone and web

LiveKit Agents with the LLMAdapter and a SIP dispatch rule.

Strict data residency, own GPUs

Self-hosted LiveKit server and agent servers with self-hosted models.

Maximum control of every frame of the pipeline in Python

Pipecat, possibly over a LiveKit transport.

No framework wanted

Building a Voice Pipeline by Hand.

TypeScript-only team

The Node.js SDK, accepting fewer plugins and no MCP or LangChain adapter.

Whatever the choice, test with recorded calls, measure latency per stage, and plan for failure paths (provider outages, dropped SIP legs, agent-server restarts mid-call); those are the topics of Production Voice Agents, which also points at the LLMOps & Evaluation material for scoring conversations.