nutritious-solstice-434
03/15/2026, 4:54 AMtall-airline-16756
03/15/2026, 4:54 AMtall-airline-16756
03/15/2026, 4:54 AMsession.say() or session.generate_reply(). That lets the agent speak while the tool is running.
• Tools and speech run concurrently, so inside tool code you should not await the speech handle directly. Instead, use context.wait_for_playout() to wait for speech playback to finish when you need to coordinate completion.
• Outside of tools (e.g., in normal agent flow), you can directly await generate_reply() or await the returned speech handle.
• For long-running tools where you want to provide user feedback (for example, “I’m checking that for you”), consider playing a short pre-synthesized hold message and interrupting it early if the tool finishes sooner.
If you’d like, tell me:
• Which SDK/language you’re using, and
• An example of the tool behavior you want (e.g., periodic status updates, a single “hold” message, or progressive narration)
I can then show a short code example tailored to your setup.
Sources: Function tool definition | LiveKit Documentation | Agent speech and audio | LiveKit Documentationnutritious-solstice-434
03/15/2026, 5:19 AMsession.say() with a short pre-set phrase, not generateReply. This is instant, doesn't create an LLM turn, and can be interrupted.
For the progressive filler, same thing -- session.say("Still working on that", { allowInterruptions: true }). When the tool completes and the LLM generates the real response, it naturally interrupts the filler.
1. The ctx parameter in the execute function likely has wait_for_playout() -- we should check if the LiveKit JS SDK exposes this, and if so, use it to coordinate.
The core problem we saw in the logs (agent saying "Please add it to my cart") is exactly because generateReply generates a full LLM response that has no idea it's supposed to be a brief filler -- it generates whatever the model thinks is appropriate, which sometimes is the user's perspective narrated back.tall-airline-16756
03/15/2026, 5:19 AMsession.say() vs generateReply() and using wait_for_playout() to coordinate speech during tool execution.
Your understanding is correct.
Key confirmations and guidance:
• Use session.say() for immediate acknowledgement or progressive fillers. It’s designed for short, interruptible speech and does not create a full LLM turn. That makes it appropriate for quick “I’m checking that for you” messages and periodic status updates.
• Do not use generateReply() for short filler speech. generateReply() creates a full LLM response, which can produce verbose or unintended content (like narrating the user’s intent) because the model treats it as a normal reply rather than an interruptible filler.
• Inside tools, tools run concurrently with speech and other agent operations. You should coordinate playback with context.wait_for_playout() (or ctx.wait_for_playout() depending on how your tool receives the context) instead of awaiting the speech handle directly. That ensures proper synchronization without blocking the tool thread in ways that could cause unexpected behavior.
• For long-running tools, pre-synthesized short hold audio is also a good option. Play it via session.say() (or as preloaded audio), allow interruptions, and cancel/interrupt it when the tool completes so the real reply can take over cleanly.
About the JS SDK exposure:
• You correctly called out the need to verify that the LiveKit JS SDK exposes wait_for_playout() on the tool context. If your tool ctx object includes wait_for_playout(), use it as described. If you don’t see it on your context object, tell me which SDK version and where the tool is executed (server-side tool vs client plugin) and I’ll help you find the exact API or a suitable alternative.
If you’d like, I can:
• Show a small JS code example that demonstrates using session.say() in a tool with ctx.wait_for_playout() coordination.
• Review a short snippet of your current tool code and point out where to switch from generateReply() to session.say() and how to call wait_for_playout().
Which would you prefer?nutritious-solstice-434
03/15/2026, 5:44 AMtall-airline-16756
03/15/2026, 5:44 AMlanguage="multi", and you can read the detected language from the transcription results.
• When the language is detected, update the agent’s TTS settings (voice/language) with update_options(language=detected_language) before calling session.say() so the filler phrase is spoken in the right language.
• Keep your phrase library keyed by language (e.g., phrases['es'].acknowledgement, phrases['en'].acknowledgement) and pick the phrase matching detected_language.
• For coordination inside tools, continue to use context.wait_for_playout() after session.say() so playback and tool execution stay in sync.
When to use this: best when you want a fully automatic, seamless experience and your users may switch languages mid-session.
2) Manual / function-based language switching (explicit user control)
• Offer a small tool or function like switch_language("es") that updates STT and TTS options via update_options() and sets the active language for phrase lookup.
• Keep per-language phrase dictionaries and call session.say(phrases[currentLanguage].filler, { allowInterruptions: true }).
• This is useful when you want to respect a user’s explicit preference or when automatic detection might be ambiguous.
Implementation tips and best practices
• Maintain a phrase dictionary per language rather than translating phrases at runtime. This avoids awkward machine translations and preserves idiomatic phrasing.
• Use short pre-synthesized hold audio (per language) for very frequent fillers to reduce latency; still allow interruption so the real reply interrupts the hold.
• Make sure you update both STT and TTS in the same switch so recognition and playback match.
• If you rely on automatic detection, add a small fallback: if detection confidence is low, ask the user (brief prompt) or default to the session’s configured language.
• Continue using session.say() for short fillers (with allowInterruptions) rather than generate_reply() so you don’t create a full LLM turn that may produce unexpected text.
If you want, I can:
• Show a short JS (or Python) snippet demonstrating language detection -> update_options -> session.say() -> context.wait_for_playout().
• Review the shape of your phrase library and give a recommended structure for keys and fallbacks.
Which would help you most — a code example in JS or Python, or a review of your current phrase structure?
Sources: Build a multilingual voice agent that automatically switches languages | Li... | Deepgram STT | LiveKit Documentation | Build Your First AI Voice Agent in Python: Complete Tutorial | LiveKit | ElevenLabs Change Language | LiveKit Documentation | Pipeline Translator Agent | LiveKit Documentation