stocky-oil-29870
02/25/2026, 6:37 AMtall-airline-16756
02/25/2026, 6:37 AMtall-airline-16756
02/25/2026, 6:37 AMasync def hangup_call():
"""End the call by gracefully shutting down and deleting the room."""
ctx = get_job_context()
if ctx is None:
logger.warning("hangup_call called but no job context available")
return
<http://logger.info|logger.info>(f"Hanging up call - shutting down job for room {ctx.room.name}")
try:
# Get the current SpeechHandle (if any) and wait for playout
speech_handle = None
if hasattr(ctx, "room") and ctx.room is not None:
# local_participant.current_speech is where the active SpeechHandle is stored
speech_handle = getattr(ctx.room.local_participant, "current_speech", None)
if speech_handle:
await speech_handle.wait_for_playout()
ctx.shutdown()
# Ensure the room is deleted promptly after graceful shutdown.
await ctx.api.room.delete_room(
api.DeleteRoomRequest(room=ctx.room.name)
)
except Exception as e:
logger.error(f"Error during hangup/shutdown: {e}")
Notes and alternatives
• If you obtain SpeechHandle directly when you call session.say() or session.generate_reply() (e.g., handle = await session.say(...)), you can store that handle in your agent/session context and await handle.wait_for_playout() during hangup instead of reading current_speech.
• If this function may be called from different contexts (e.g., from a function tool where you have a context.session object), use the session-provided current_speech there.
• If you only need to force immediate shutdown (skip waiting for audio), you can call ctx.shutdown() right away and delete the room — just be aware that any in-flight speech may be cut off.
If you'd like, share:
• How you obtain/track the SpeechHandle in your flow (do you call session.say() or session.generate_reply()?), or
• A short snippet showing where you set current_speech,
and I can adapt the snippet to your exact setup.
Sources: Agents telephony integration | LiveKit Documentation | Agent speech and audio | LiveKit Documentationstocky-oil-29870
02/25/2026, 7:06 AMtall-airline-16756
02/25/2026, 7:06 AMctx.room.local_participant before the agent has connected, and that property isn't available until after a successful connect. I'll walk through a safe, clear fix and a couple of alternatives so you can choose what fits your flow.
What caused the error
• cannot access local participant before connecting happens because ctx.room.local_participant is only valid after the session connects.
• Also, current_speech is a property of the AgentSession (or the SpeechHandle you obtained when calling session.say() / session.generate_reply()), not something you should rely on via local_participant before connection.
Recommended approach (safe ordering)
1. Keep a reference to the active AgentSession or any SpeechHandle when you create it, and store that reference in the job context (for example ctx.userdata["session"] or ctx.userdata["current_speech"]).
2. On hangup:
◦ If you have a SpeechHandle, await its wait_for_playout() to let audio finish.
◦ Call the session shutdown/drain to gracefully close audio/speech.
◦ Await the job context close/shutdown.
◦ Delete the room only after shutdown completes.
Example implementation you can adapt:
async def hangup_call():
"""End the call by gracefully shutting down and deleting the room."""
ctx = get_job_context()
if ctx is None:
logger.warning("hangup_call called but no job context available")
return
<http://logger.info|logger.info>(f"Hanging up call - shutting down job for room {ctx.room.name}")
try:
# Get stored session or speech handle from context userdata (store these at session creation)
session = None
speech_handle = None
if hasattr(ctx, "userdata"):
session = ctx.userdata.get("session")
speech_handle = ctx.userdata.get("current_speech")
# If you have a SpeechHandle, wait for playout
if speech_handle:
await speech_handle.wait_for_playout()
# If you have the AgentSession, request graceful shutdown that drains queued speech
if session:
# If the session provides a drain option, use it. Otherwise call its shutdown API.
try:
session.shutdown(drain=True)
except Exception:
# Fallback to calling shutdown without drain if drain option not present
session.shutdown()
# Ensure the job context fully closes/shuts down
# Use ctx.aclose() or ctx.shutdown() depending on your environment (await the appropriate method)
if hasattr(ctx, "aclose"):
await ctx.aclose()
else:
ctx.shutdown()
# Only delete the room after the session and ctx are shut down
await ctx.api.room.delete_room(api.DeleteRoomRequest(room=ctx.room.name))
except Exception as e:
logger.error(f"Error during hangup/shutdown: {e}")
How to store the session/speech handle when you create them
• When you create the AgentSession or call session.say() / session.generate_reply(), save references:
# Example when creating session:
session = AgentSession(...)
ctx.userdata["session"] = session
# Example when calling say/generate_reply:
speech_handle = await session.say("Goodbye")
ctx.userdata["current_speech"] = speech_handle
Alternative quick fixes (use with caution)
• If you don't have the session or speech handle available, skip accessing local_participant entirely and proceed to shutdown/delete the room. Note: this may cut off in-flight audio.
• Before reading ctx.room.local_participant, check a connection flag or only access it after ctx.connect() has executed successfully.
Why this will fix the error
• You avoid accessing local_participant before connect.
• You await the SpeechHandle's playout via wait_for_playout() which is the correct object/method to wait on.
• You shut down the session and await ctx close before deleting the room, preventing race conditions and premature resource deletion.
If you want, next I can:
• Adapt the example to the exact methods your AgentSession exposes (share the session creation snippet or method names you use).
• Show a minimal entrypoint example that stores session and speech handles in ctx.userdata so hangup_call can always find them. Sources: Agent speech and audio | LiveKit Documentation | Job lifecycle | LiveKit Documentation