quiet-umbrella-73832
12/19/2025, 7:07 PMtall-airline-16756
12/19/2025, 7:07 PMtall-airline-16756
12/19/2025, 7:08 PMquiet-umbrella-73832
12/19/2025, 7:15 PMtall-airline-16756
12/19/2025, 7:15 PMquiet-umbrella-73832
12/19/2025, 7:17 PMtall-airline-16756
12/19/2025, 7:17 PMquiet-umbrella-73832
12/19/2025, 7:19 PMtall-airline-16756
12/19/2025, 7:19 PMquiet-umbrella-73832
12/19/2025, 7:21 PMtall-airline-16756
12/19/2025, 7:22 PMimport asyncio
import random
from contextlib import asynccontextmanager
from livekit.agents import AgentSession # adjust import to your SDK layout
# Tune this to <= your plan's concurrent TTS limit (start with 3)
MAX_CONCURRENT_TTS = 3
_tts_semaphore = asyncio.Semaphore(MAX_CONCURRENT_TTS)
async def _backoff_sleep(attempt: int):
base_ms = (2 ** attempt) * 100
jitter = random.uniform(0, 100)
await asyncio.sleep((base_ms + jitter) / 1000.0)
@asynccontextmanager
async def tts_session_with_limits(*session_args, **session_kwargs):
"""
Acquire semaphore, attempt to create/start a TTS AgentSession with retries on 429.
Yields the started session (caller is responsible for using and closing it).
"""
await _tts_semaphore.acquire()
try:
max_attempts = 3
for attempt in range(max_attempts):
try:
session = AgentSession(*session_args, **session_kwargs)
# Replace `start(...)` with your actual session start call
await session.start(...) # e.g., await session.start(agent=agent, room=room)
yield session
return
except Exception as e:
# Inspect the exception to detect 429 handshake error.
# Adjust detection based on the actual exception type/message you see.
msg = str(e).lower()
if "w sserverhandshakeerror" in msg or "429" in msg or "too many requests" in msg:
if attempt < max_attempts - 1:
await _backoff_sleep(attempt)
continue
# Non-retryable or out of attempts -> re-raise
raise
finally:
try:
_tts_semaphore.release()
except Exception:
pass
Example usage:
async def handle_tts_request(...):
async with tts_session_with_limits(tts="cartesia/sonic-3") as session:
try:
# send fragments over the session websocket
await session.send_fragment(...) # replace with actual API calls
await session.finish() # or close/complete pattern
finally:
await session.close()
Notes and tuning
• Set MAX_CONCURRENT_TTS to 3 (or lower) to start; your plan’s visible concurrent TTS limit is 5, but leaving headroom prevents handshake bursts. Raise slowly if stable.
• The retry logic specifically targets 429/handshake errors; if your SDK raises a specific exception class for handshake failures, replace the text check with isinstance(...) or inspect the error fields.
• Instrument metrics: count handshake-429s, current semaphore waiters, and average handshake latency to tune values.
If you share the exact start/handshake calls (what you replace for await session.start(...) and the exception class/message you actually see), I’ll refine the snippet to match your SDK calls and error detection precisely.
Sources: Quotas and limits | LiveKit docs | server-sdk-go | livekit/livekit_composite | DeepWikiquiet-umbrella-73832
12/19/2025, 7:29 PMsession = AgentSession(
llm= openai.realtime.RealtimeModel(
voice="marin",
model="gpt-realtime",
turn_detection=TurnDetection(
type="server_vad",
threshold=0.5,
prefix_padding_ms=300,
silence_duration_ms=500,
create_response=True,
interrupt_response=True,
)),
turn_detection=MultilingualModel(),
vad=ctx.proc.userdata["vad"],
userdata={
"facility_id": facility_id,
"dialed_number": dialed_number,
},
)tall-airline-16756
12/19/2025, 7:30 PMllm and `tts`/`stt` while using the semaphore + retry wrapper (drop‑in for your code).
• Recommend exact semaphore sizes if you share typical peak concurrent TTS+STT requests.
Which would you prefer?