Latency
The time between “user finishes speaking/typing” and “avatar starts responding on screen” is the sum of several stages, and most of them are yours to tune.
Where the time goes
Section titled “Where the time goes”- Your input pipeline: speech-to-text, if you’re doing voice input (entirely your stack, not covered by this API).
- Your brain: however long your LLM (or logic) takes to decide on a reply. See Bring your own brain.
- Speech synthesis: either 2wai’s TTS (
/speak) or your own (/audio). - Lipsync and encoding: 2wai renders the frames and streams them out.
- Network and transport: LiveKit room join + WebRTC jitter-buffer warmup. Typically the fastest leg on a stable network.
Stages 1-3 usually dominate; 4 is typically the fastest part once a session is warm.
- Stream your LLM output and speak in sentence chunks. Waiting for a full paragraph before calling
/speakadds the model’s entire generation time to your latency budget. Flushing at sentence boundaries lets the avatar start talking within a second or two of the first tokens arriving. See the streaming example in Bring your own brain. - Keep sessions warm. Creating a session has setup cost. For a multi-turn conversation, create one session and drive it with repeated
/speak//audiocalls rather than creating a new session per turn. - Use
/interrupton barge-in. If your product supports the user talking over the avatar, callPOST /v1/sessions/{id}/interruptthe moment you detect new input, rather than letting a stale reply finish and queuing the new one behind it. - If you bring your own TTS, prefer a streaming-capable engine. Chunking audio to
/audioas it’s generated (more=truebetween chunks) avoids waiting for the entire clip to synthesize before any of it can render. See Bring your own TTS. - Keep LiveKit’s
adaptiveStreamanddynacastoff for 2wai sessions. The agent’s video and audio are the only tracks you care about; adaptive logic can drop the video during our brief idle periods, which looks like a stall on the client. Every 2wai SDK ships this flipped off by default — if you’re wiringlivekit-clientyourself, passnew Room({ adaptiveStream: false, dynacast: false }).
- Sessions for keeping a session alive across a whole conversation instead of recreating it.
- Limits & concurrency: running near your concurrency cap can itself introduce queuing delay.