fresh-hairdresser-44285
12/16/2025, 11:48 AMtall-airline-16756
12/16/2025, 11:48 AMtall-airline-16756
12/16/2025, 11:48 AMsession.generate_reply("Thank you, have a nice day.") 2) hangup_call() / delete_room.
• If you must do both in one tool, wrap them in a single function/tool (e.g., initiate_end_of_call) that first sends the goodbye speech (or session.say) and then calls delete_room — invoke this tool once when the workflow finishes.
• Use allow_interruptions=False and wait for playout completion (or await the speech handle) before calling hangup so the session doesn't reopen on brief user replies.
• Ensure your hangup_call() actually calls delete_room (delete room terminates for all participants). Simply ending agent logic will leave the call open.
• If using turn-based VAD/STT triggers, confirm the final user "thank you" doesn't create a new agent turn — you may need to disable automatic reactivation at the final step.
To give a precise code fix, please share:
1. The code for how you currently trigger end_call / hangup_call (is it a tool decorated @function_tool or called from server logic?).
2. Whether you call delete_room inside your hangup function.
3. Whether you use session.generate_reply vs session.say and how you handle allow_interruptions.
4. How turns are controlled (manual turn control, VAD, or auto reactivation) and the exact final prompt/instruction you give the agent.
Tell me those details and I’ll provide a concrete change you can drop into your agent to stop the thank-you loop and reliably end the call.
Sources: Agents telephony integration | LiveKit docs | Pipeline nodes and hooks | LiveKit docs | Agent speech and audio | LiveKit docsfresh-hairdresser-44285
12/16/2025, 11:51 AM@function_tool()
async def end_call(self, ctx: RunContext):
"""Called when the user wants to end the call"""
participant_identity = self.participant.identity if self.participant else "unknown"
<http://logger.info|logger.info>(f"ending the call for {participant_identity}")
# Set ended_reason to indicate the assistant decided to end the call
is_video = self.is_video
print("video- true form",is_video)
if is_video:
update_call_status(
room_name=self.job_ctx.room.name,
ended_reason="assistant-ended-web-session"
)
set_ended_reason("assistant-ended-web-session")
else:
update_call_status(
room_name=self.job_ctx.room.name,
ended_reason="assistant-ended-call"
)
set_ended_reason("assistant-ended-call")
# Say the end call message if configured
# Check if using realtime model
if hasattr(ctx.session, 'stt') and ctx.session.stt is None:
# Realtime model - skip session.say()
<http://logger.info|logger.info>(f"Realtime model detected - skipping session.say() for end call message")
else:
# Traditional model - use session.say()
if self.end_call_message:
<http://logger.info|logger.info>(f"Using custom end call message: {self.end_call_message[:50]}...")
await ctx.session.say(self.end_call_message)
else:
# Use a default end call message
await ctx.session.say("Thank you for your time. Have a great day!")tall-airline-16756
12/16/2025, 11:51 AMend_call only calls ctx.session.say(...) for non-realtime sessions and never actually deletes the room. Ensure you call the room deletion after the goodbye so the call truly ends for participants. Example sequence inside the tool:
a. await ctx.session.say(...) (or session.generate_reply)
b. call your room delete API (e.g., await ctx.api.room.delete_room(...) or whatever delete_room wrapper you have)
• If using a realtime model (where you skip session.say), explicitly delete the room immediately in end_call (realtime does not play audio, so skipping say without deleting leaves the call open).
• Prevent reactivation during goodbye:
◦ If you send the goodbye with ctx.session.say(...), await the returned handle (or use allow_interruptions=False) and only call delete after that handle completes. This avoids the user’s brief “thank you” triggering a new turn.
• Wrap speech + hangup in the same function and call it exactly once. Do not rely on the LLM to both utter the goodbye and then independently invoke end_call later.
• Add logging around the final steps to confirm order:
◦ Log before say, after say completes, before delete, and after delete.
Small code sketch (insert into your tool after your existing message logic):
# say goodbye if applicable
if hasattr(ctx.session, 'stt') and ctx.session.stt is None:
<http://logger.info|logger.info>("Realtime model: skipping say, proceeding to delete room")
else:
if self.end_call_message:
handle = await ctx.session.say(self.end_call_message, allow_interruptions=False)
else:
handle = await ctx.session.say("Thank you for your time. Have a great day!", allow_interruptions=False)
await handle # wait for playout to finish
# now delete the room to end the call for everyone
await ctx.api.room.delete_room(api.DeleteRoomRequest(room=self.job_ctx.room.name))
<http://logger.info|logger.info>("Room deleted, call ended")
If you want, share: 1) Whether your current `hangup_call`/`update_call_status` already calls delete_room (or confirm it does not). 2) Whether you observe realtime vs. traditional behavior when testing (phone/SIP vs web).
I’ll give the exact change to drop into your tool once you confirm those two points.fresh-hairdresser-44285
12/16/2025, 11:54 AM- Call Rescheduling:
If the candidate wishes to reschedule, handle it empathetically and conversationally.
Your goal: confirm a suitable date and time and end call.
Guidelines:
• If a specific date/time is provided: Confirm it naturally and restate it to avoid confusion.
• If the timing is vague: Politely ask for clarification (both day and time).
• If multiple options are given: Choose and confirm the first available one.
• If only time is given: Confirm the date also.
• **After confirmation: Thank them for their flexibility, confirm the rescheduled slot, provide a brief friendly closing statement (e.g., "Talk to you then!"), then immediately call end_call() tool in that SAME message. DO NOT wait for any further acknowledgment. DO NOT announce you are ending the call.**
the main problem is the agent is not able to invoke the end call tool. the agent is not reaching this code.
something wrong in the prompt it seems.tall-airline-16756
12/16/2025, 11:54 AMend_call tool. You’re right: the LLM often won’t both speak and call a tool from a single freeform assistant message when the prompt asks it to “call end_call() in that SAME message.” Let’s narrow this down and fix it.
Quick actionable changes to try (pick one or combine as appropriate):
1) Replace the inline instruction to call end_call() from the assistant-generated message with a deterministic tool call from your runtime after the agent confirms the slot.
• Let the LLM produce the confirmation text only.
• Immediately after you detect that the LLM’s reply contains a confirmation (e.g., regex for date/time or a “confirmed” intent), call your end_call tool from server code once. This guarantees the hangup runs.
2) If you must have the model trigger the tool, make the tool a single explicit function with a strict tool-schema return and require the tool call via tools-routing rather than free text. Example flow:
• Prompt: “If conversation is finished, respond ONLY with the JSON tool call: end_call(). Do not output any other text.”
• Register end_call as a function-tool and enforce strict tool invocation in your agent runtime so the model chooses that tool call instead of a natural-language goodbye.
3) Make the final step atomic in the tool (what you already do partially): have the tool both say the goodbye (await playout) and then call delete_room. Ensure you always call delete_room inside end_call. For realtime sessions, call delete_room immediately; for non-realtime, await the say handle before deleting.
4) Prevent reactivation while goodbye is playing:
• Use allow_interruptions=False and await the returned handle before deleting the room.
• Add a short server-side grace window: after sending the final message, ignore further incoming turns for ~1–2s before calling delete to avoid picking up a quick “thank you” from user.
Prompt tweaks (if you want the model to decide):
• Avoid asking the model to both produce natural text AND call a tool in the same assistant message. Replace with either:
◦ “Produce ONLY the confirmation text. The runtime will call end_call() after this reply.” or
◦ “If you want to end the call now, respond with exactly: CALL_TOOL:end_call” (and map that token to a tool invocation in your server).
What I need to confirm to give the exact code you should drop in:
1. Do you run the agent in a tools-enabled mode where the model can return a structured tool call (function tool)?
2. Does your update_call_status or other code already call ctx.api.room.delete_room anywhere else, or does end_call need to perform the delete?
3. Are you using realtime sessions (STT present) when you see the problem, or only traditional sessions?
Answer those and I’ll give a minimal code snippet you can paste into your end_call tool or your runtime controller to ensure the call always ends immediately after confirmation.