liveavatar-starter-livekit-agent-python
Reference implementation for this guide. Demo entrypoint:
src/byo_livekit_demo.py.This guide is best suited for developers who already run a LiveKit deployment and need control beyond what the LiveKit avatar plugin provides. The plugin handles the orchestration between your LiveKit agent and the avatar participant out of the box — start there first. Reach for the BYO flow below only if your use case needs more control over room lifecycle, dispatch, recording, or worker fleet.
Prerequisites
Before you start, make sure you have:- A LiveAvatar account with an API key — sign up at app.liveavatar.com.
- An
AVATAR_IDfrom your account. The default in the starter repo is sandbox-compatible; swap it for one of your own avatars when going to production. - A LiveKit Cloud project with
LIVEKIT_URL,LIVEKIT_API_KEY, andLIVEKIT_API_SECRET. Sign up at cloud.livekit.io if you do not have one. - Python 3.11+ and
uv(orpip) to install the starter’s dependencies. - The LiveKit CLI (
brew install livekit-cli, thenlk cloud auth) for deploying the worker to LiveKit Cloud.
LIVEKIT_URL is required here — you own the room, so you tell LiveAvatar where to join.
Quickstart
Clone the reference implementation and run it against the LiveAvatar API and your own LiveKit project..env.local:
Both terminals load credentials from
.env.local via load_dotenv. The dev worker registers against whichever LK project LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET point to — make sure those match the project you intend to dispatch into.src/worker.py exposes three CLI subcommands inherited from LiveKit Agents:
To verify the worker is actually registered before running the dispatcher, check
lk agent status (after deploy) or look for a registered worker log line in Terminal 1.
Behind the scenes, the dispatcher mints room tokens, asks LiveAvatar to join your room as a participant, and dispatches your worker into the room by name. The next sections break down the full flow.
Understanding the flow
- You mint two LiveKit tokens. One for the LiveAvatar avatar, one for the viewer.
- LiveAvatar joins your room. Using the minted avatar token, LiveAvatar dispatches its avatar into your room as a participant.
- Your agent joins your room. Your worker is dispatched into the same room by name, alongside the avatar and the viewer.
- The worker drives the connection. Using the WebSocket returned when the session starts, the worker controls the avatar — running STT → LLM → TTS and streaming audio for lip-sync.
Ownership at a glance
Understanding the code
When we runpython src/byo_livekit_demo.py the dispatcher does the following:
- Mint room + tokens against your LK project. Generates a fresh room name, then mints separate tokens for the avatar participant and the browser viewer.
- Create a LiveAvatar session pinned to your room. Calls
create_session_tokenwithlivekit_config={livekit_url, livekit_room, livekit_client_token=avatar_token}so LiveAvatar’s avatar joins your room. - Start the session. Calls
start_sessionto get the avatar media-server WebSocket URL (ws_url). - Dispatch the agent into the room. Calls
AgentDispatchService.create_dispatch(agent_name=AGENT_NAME, room=room_name, metadata=json.dumps({ws_url, session_id})). LK Cloud routes the dispatch to a registered worker. - Open the viewer. Serves
viewer/index.htmllocally and opens a browser tab withLIVEKIT_URL+ viewer token preloaded. - Worker accepts the dispatch. Reads metadata, opens the LiveAvatar WebSocket, runs STT → LLM → TTS, and tees synthesized audio to the WebSocket for lip-sync.
- Clean up on exit. The worker registers a shutdown callback that calls
stop_sessionon the LiveAvatar API whenever the job ends.
livekit_config.livekit_client_token is the avatar’s token, not the viewer’s. The avatar uses it to join your room as a participant. Mint the viewer token separately for the browser client.File structure
The starter is split into a backend (the dispatcher + the worker) and a small frontend used to visualize the session locally. Backend (src/):
Frontend (
viewer/):
We ship a minimal frontend so you can see the avatar and talk to it locally without standing up your own client. Treat it as a reference, not as production frontend code.
The avatar joins the room under the participant identity
"avatar" (set as AVATAR_IDENTITY in byo_livekit_demo.py). When building your own frontend, subscribe to that identity’s audio + video tracks to render the avatar.Customizing the agent
The most common things you’ll want to tweak after the quickstart:Productionizing
This demo is wired up for fast local iteration. When productionizing, move dispatch logic to a backend service, deploy the worker to LiveKit Cloud, and tighten up session lifecycle.Improvements beyond the demo
A few things in the starter exist purely for local convenience. We recommend the following swaps:- Build your own frontend. Replace the local viewer HTTP server and auto-launched browser tab with a real client that joins your LK room using the viewer token.
- Move dispatch to a backend service.
byo_livekit_demo.pymints LK tokens and calls the LiveAvatar API client-side because it’s a single-process demo. In production, that logic belongs on a backend that authenticates the user, mints the viewer token, callscreate_session_token+start_session+create_dispatch, and returns only{room_url, viewer_token}to the browser. Never shipLIVEKIT_API_SECRET,LIVEAVATAR_API_KEY, or the avatar token to the browser. - Deploy the worker to LiveKit Cloud.
python src/worker.py devis for local iteration only. The repo ships aDockerfileandlivekit.tomlforlk agent deploy(see below). - Rename
AGENT_NAME. The defaultmy-agentcollides across LK Cloud accounts and makeslk agent logspainful. Pick something project-specific (e.g.support-agent,tutor-bot) before deploying. - Guard against leaked sessions. The worker registers an
add_shutdown_callbackthat callsstop_sessionwhenever the job ends. This is the load-bearing cleanup path in production — your backend dispatcher should fire-and-forget aftercreate_dispatchand let the worker handle lifecycle. As a safety net for runners that crash without it (SIGKILL, OOM), setmax_session_durationserver-side when callingcreate_session_tokenso LiveAvatar will close abandoned sessions on its own. - Manual termination. To end a session early (e.g. user clicks “end call”), call
stop_session(session_id)directly from your backend. The worker’s shutdown callback will fire when the job is torn down.
Deploying the worker to LiveKit Cloud
The repo ships aDockerfile and livekit.toml for lk agent deploy.
The
Dockerfile runs python src/worker.py download-files at build time to bake VAD + turn-detector model weights into the image. This is what makes cold-starts in LK Cloud fast. Don’t strip it.python src/worker.py dev terminal entirely. Dispatches go straight to the cloud-managed worker fleet.