red-dentist-96118
12/17/2025, 7:15 PMtall-airline-16756
12/17/2025, 7:15 PMtall-airline-16756
12/17/2025, 7:15 PMaudio stream (pre-recorded or pre-rendered frames) when calling session.say() so playback doesn’t depend on live TTS. This ensures audio frames exist even if TTS fails.
• Add fallback/error handling in your TTS node
Catch exceptions in your TTS pipeline and either:
• yield a short silence frame, or
• yield a fallback prerecorded clip, or
• log the error and continue without raising an exception that would abort the session.
• Validate TTS setup before speaking
Confirm the TTS model/plugin is initialized and supports the requested voice. If initialization fails, switch to fallback audio instead of attempting live synthesis.
• Monitor and recover automatically
Detect when no frames are emitted (timeout or zero-frame output) and trigger a recovery path: play fallback audio, retry synthesis once, or skip the utterance without throwing an unhandled error.
If you want, tell me which method you’re using now (calling session.say() directly, a custom tts_node, or a TTS plugin/model name) and I’ll give a concise code example you can drop into your pipeline.
Sources: How to Set Up Speech-to-Text Input with Text-Only Output in LiveKit Agents | Agent speech and audio | LiveKit docs | Pipeline nodes and hooks | LiveKit docs | Text-to-speech (TTS) models | LiveKit Docsred-dentist-96118
12/17/2025, 7:16 PMtall-airline-16756
12/17/2025, 7:17 PMtext = llm_response.strip()
if not text:
# skip speaking or play fallback audio
await session.say(audio=fallback_audio)
else:
await session.say(text=text)
2) Use a fallback audio clip or silence
• Play a short prerecorded message or silence so the pipeline always receives frames.
• Silence can be a single generated audio frame or a short silent stream.
3) Defensive TTS wrapper
• Wrap TTS calls so they never raise out-of-band exceptions to the session; return silence/fallback on error.
async def safe_say(session, text):
text = (text or "").strip()
if not text:
await session.say(audio=fallback_audio) # or return
return
try:
await session.say(text=text)
except Exception:
await session.say(audio=fallback_audio)
4) In custom tts_node implementations
• If the LLM yields empty text, yield silence or fallback frames instead of raising.
• Catch synth errors and yield silent frames to keep stream alive.
5) Monitoring / metrics
• Log occurrences of empty outputs and count retries to decide when to alter model prompts or fallback behavior.
If you tell me which SDK/language you use (Python/JS) and whether you prefer skipping, silence, or a spoken fallback, I’ll give a copy-paste snippet for your code.red-dentist-96118
12/17/2025, 7:18 PMtall-airline-16756
12/17/2025, 7:18 PMasync def safe_say(session, text):
# Normalize and skip empty/whitespace-only responses
if not (text or "").strip():
return # no-op and continue
# Normal speak path
await session.say(text=text)
If you use a custom tts_node generator, do the same inside the generator:
async def tts_node(text_iter, model_settings):
async for chunk in text_iter:
if not (chunk or "").strip():
continue # skip this chunk and keep streaming
async for frame in synthesize_frames(chunk, model_settings):
yield frame
Use these patterns wherever you accept LLM output before calling TTS; this prevents crashes by skipping empty inputs and continuing the session. Would you like a version with logging or timeout handling added?