Skip to main content
Build a voice agent on LiveKit Agents where one model hears the caller, decides when they’ve finished, and writes the reply. LiveKit carries the audio and a text-to-speech plugin speaks the reply. In about ten minutes you’ll be talking to it, in your terminal or in a LiveKit room.

Full code on GitHub

A hotel front desk agent with two tools, ElevenLabs speech, and a scripted caller that runs a whole call, barge-in included.

What you’ll build

  1. A front desk agent for a fictional hotel with two tools: one looks up a booking, the other changes its checkout time.
  2. An AgentSession that uses Dialog-RSN-1 as the model and ElevenLabs as the voice. Dialog-RSN-1 replaces the speech-to-text plugin, the turn detector and the LLM.
  3. Barge-in. Talk over the agent and it stops, and the model’s history keeps only the words the caller heard.
  4. A scripted caller that joins the room and runs a whole call, barge-in included, so you can test without a microphone.

What changes from a standard LiveKit pipeline

A standard LiveKit pipeline chains speech-to-text, an LLM and text-to-speech, with a turn detector deciding when the caller has finished. Dialog-RSN-1 takes the caller’s audio and handles every job except the voice: Dialog-RSN-1 plugs into LiveKit through its own plugin, livekit-plugins-polyai. Dialog-RSN-1 speaks the OpenAI Realtime protocol, but LiveKit’s OpenAI plugin isn’t built for it. Why not the OpenAI plugin explains the difference.

Before you begin

  1. A Dialog-RSN-1 API key. Sign up or log in at studio.poly.ai and create one in the API Keys tab. The quickstart walks through it.
  2. An ElevenLabs API key. The free tier works.
  3. Python 3.10 or later, and uv.
  4. The LiveKit CLI: brew install livekit-cli.
  5. For Step 6: a LiveKit Cloud project, or a local server (brew install livekit).

Step 1: Get the code

The repo has three files you’ll work with:

Step 2: Configure your environment

Open .env and set two keys:
The ElevenLabs plugin reads ELEVEN_API_KEY, not ELEVENLABS_API_KEY. Leave the LIVEKIT_* values alone until Step 6.

Step 3: Point the session at Dialog-RSN-1

agent.py builds the whole session in a few lines:
Compared with the LiveKit starter template, the session leaves out three things:
  • No stt. Dialog-RSN-1 takes the room’s audio as it arrives. You still see the caller’s transcript in LiveKit, because Dialog-RSN-1 sends it with each reply. It comes at the end of a completed reply, so a turn whose reply is cancelled before it finishes generating has no transcript.
  • No turn_handling. You have no turn detector to configure. Dialog-RSN-1 judges each pause in the context of the conversation. To make it more or less patient, say so in the instructions. The prompt guide shows how.
  • No instructions on generate_reply(). The plugin drops per-reply instructions with a warning, so the greeting lives in the agent’s main instructions.
polyai.realtime.RealtimeModel() reads POLY_API_KEY and connects to wss://api.studio.poly.ai/v1/realtime. To use a different endpoint, set DIALOG_REALTIME_URL. It asks Dialog-RSN-1 for text, and the TTS plugin speaks each reply as the text streams in. The two tools are ordinary LiveKit function tools:
Each tool returns its result as a JSON string, which gives the model structured data to read. Dialog-RSN-1 accepts function tools only and refuses any other kind, so the plugin sends function tools and skips the rest with a warning.

Step 4: Run the agent

The console runs the agent on your machine and connects it to your microphone and speakers. You don’t need a LiveKit server for this step. Wear headphones so the agent doesn’t hear itself. To type instead of talk, add --text. Text mode skips TTS and runs only Dialog-RSN-1 and the tools:

Step 5: Test the conversation

The agent greets you with “Hello, Grand Meridian Hotel. How can I help you today?” Then try four things:
  1. Say “Can you look up booking GM40912?” The agent calls look_up_reservation and reads back the booking.
  2. Say “Could I check out at two instead?” The agent tells you whether there’s a fee and asks you to confirm before it calls change_checkout_time.
  3. Ask something with a long answer, then talk over the reply. The agent stops and answers your new question.
  4. Pause mid-sentence, for example while you read out the booking code. The agent should wait for you to finish.
If the agent doesn’t start, see Troubleshooting.

Step 6: Run it in a LiveKit room

To use LiveKit Cloud, put your project’s URL, API key and secret in .env. To stay on your machine, start a local server and keep the defaults from .env.example:
Start the agent. It joins every new room on the server:
On LiveKit Cloud, you can talk to the agent from your project’s Agent Console. On a local server, or without a microphone, run the scripted caller in a second terminal:
The caller joins a new room and plays five recorded lines, each one after the agent finishes its reply. The third line, “Sorry, stop, stop”, plays 1.5 seconds into a reply instead. The first three exchanges look like this:
The agent stops at “checking out”, where the caller cut in, and Dialog-RSN-1’s history stops there too. When the caller interrupts, LiveKit deletes the full reply from the server and adds back only the words the caller heard. The model builds its next reply on that shorter version. When the call ends, the caller saves the agent’s side to call.wav so you can listen back.

Add Dialog-RSN-1 to an existing LiveKit agent

  1. Install the plugin from GitHub. It isn’t on PyPI yet:
  2. Import it: from livekit.plugins import polyai.
  3. Add POLY_API_KEY to your environment.
  4. In your AgentSession, set llm=polyai.realtime.RealtimeModel(). Remove stt and turn_handling, and keep your tts.
  5. Move any instructions you pass to generate_reply() into the agent’s main instructions.

Why not the OpenAI plugin

You can point LiveKit’s openai.realtime.RealtimeModel at Dialog-RSN-1 by changing base_url and the key, but it’s built for OpenAI’s models. livekit-plugins-polyai is built for Dialog-RSN-1: The plugin implements LiveKit’s realtime model interface directly instead of patching another plugin, so a LiveKit upgrade is less likely to break it. Found a bug, or want a feature? Open an issue or send a pull request.

Choose a different voice

To change the ElevenLabs voice, pass a different voice_id. The repo uses Sarah, which works on the free tier. Some library voices, such as Rachel, don’t. To switch provider, swap the plugin. For Cartesia, install its plugin:
Add CARTESIA_API_KEY to .env, then change the tts line:
Nothing else in agent.py changes. LiveKit’s TTS models page lists every provider it supports.

Troubleshooting

What doesn’t carry over

  • Turn-handling options. LiveKit’s turn detector, endpointing delays and preemptive generation don’t apply, because Dialog-RSN-1 decides when the turn ends.
  • Per-reply instructions. generate_reply(instructions=...) has no effect, because the plugin doesn’t send them. Put the behavior in the agent’s instructions.
  • STT features. With no STT plugin, options such as keyterm prompting have nothing to configure. Dialog-RSN-1 transcribes the caller as part of each turn.
  • Playback reports. The plugin doesn’t send Dialog-RSN-1’s poly.output_audio.* events yet. The streaming text-to-speech guide explains what they add.

Where to go next

Last modified on October 14, 2026