rough-hairdresser-81331
07/22/2025, 5:59 PMtall-airline-16756
07/22/2025, 5:59 PMtall-airline-16756
07/22/2025, 6:02 PMfrom livekit import api, rtc
from livekit.agents import get_job_context
async def hangup_call():
ctx = get_job_context()
if ctx is None:
return
await ctx.api.room.delete_room(
api.DeleteRoomRequest(
room=ctx.room.name,
)
)
class MyAgent(Agent):
...
@function_tool
async def end_call(self, ctx: RunContext):
current_speech = ctx.session.current_speech
if current_speech:
await current_speech.wait_for_playout()
await hangup_call()
By deleting the room when the call ends, you ensure that all participants are properly disconnected and the agent session does not continue after the caller hangs up.
Sources: Agents telephony integration | LiveKit Docsrough-hairdresser-81331
07/22/2025, 6:05 PMtall-airline-16756
07/22/2025, 6:06 PMhangup. Think of it like listening for a signal that says, "Hey, I'm done with this call!"
Here's a friendly example that shows exactly how to do this:
async def entrypoint(ctx: JobContext):
await ctx.connect()
session = AgentSession()
agent = YourAgent(job_context=ctx)
await session.start(agent=agent, room=ctx.room)
async def async_on_participant_attributes_changed(changed_attributes: dict, participant: rtc.Participant):
if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
if 'sip.callStatus' in changed_attributes:
call_status = changed_attributes['sip.callStatus']
if call_status == 'hangup':
await cleanup_call(ctx)
def on_participant_attributes_changed_handler(changed_attributes: dict, participant: rtc.Participant):
asyncio.create_task(async_on_participant_attributes_changed(changed_attributes, participant))
ctx.room.on("participant_attributes_changed", on_participant_attributes_changed_handler)
async def cleanup_call(ctx: JobContext):
await ctx.api.room.delete_room(
api.DeleteRoomRequest(
room=ctx.room.name,
)
)
What this does is pretty cool - when the SIP caller hangs up, it'll automatically wrap things up by deleting the room and ending the agent session. No loose ends, no fuss!
Sources: Agents telephony integration | LiveKit Docs | telephony/sip_lifecycle.pyrough-hairdresser-81331
07/22/2025, 6:41 PMtall-airline-16756
07/22/2025, 6:42 PMasyncio.create_task in the participant_attributes_changed event handler. Think of it like a helpful workaround for LiveKit's event system.
Basically, LiveKit's event handlers are synchronous (like a quick chat), but sometimes you need to do some async tasks (like cleaning up a room) that take a bit more time. By using asyncio.create_task, you're basically saying, "Hey, go do this task in the background while I keep things moving!"
• Your event system stays smooth and responsive - no waiting around!
• Your async tasks (like room deletion) get handled without interrupting the flow
It's a super common approach when you're working with LiveKit and need to juggle sync and async tasks. Pretty neat, right?
Sources: telephony/sip_lifecycle.py | agent/main.py | examples/participant_attributes.pyrough-hairdresser-81331
07/22/2025, 6:53 PMtall-airline-16756
07/22/2025, 6:54 PMon_participant_attributes_changed_handler and async_on_participant_attributes_changed outside the entrypoint function. I totally get that you might want more flexibility with your code placement.
• Just keep in mind that when you define handlers inside entrypoint, they naturally have access to cool local variables like ctx (your JobContext) and other resources.
• If you decide to move these functions outside, no worries! You'll just need to pass along any dependencies like ctx or agent instances as parameters to your handler functions.
• Pro tip: Make sure you still register your event handler (ctx.room.on("participant_attributes_changed", handler)) within entrypoint after connecting to the room.
This is actually the approach used in all the official examples – it helps ensure your handlers have everything they need. Just manage those dependencies thoughtfully, and you'll be golden! 😊
Sources: telephony/sip_lifecycle.py | agent/main.py | server/main.py