refined-airport-68974
12/23/2025, 7:52 AMtall-airline-16756
12/23/2025, 7:52 AMtall-airline-16756
12/23/2025, 7:53 AMtool_choice setting, so LiveKit is telling you that the parameter will be ignored. It does not stop the agent from running, but you cannot control tool-selection behavior via tool_choice for that model.
What you can do:
• Remove tool_choice from your google.realtime.RealtimeModel configuration to stop the warning.
• If you need the model to prefer or avoid tools, encode that behavior in the prompt/instructions you send to the model (explicit guidance), since tool_choice cannot be used with Gemini Live.
If you want, tell me:
• the code snippet where you configure the model (redact keys), or
• whether you need help converting your tool_choice intent into a prompt-based instruction — I can suggest exact prompt examples. Sources: Gemini Live API plugin | LiveKit docs | Anthropic Claude LLM plugin guide | LiveKit docs | Agents (Node.js) | livekit/livekit_composite | DeepWikirefined-airport-68974
12/23/2025, 7:58 AMtall-airline-16756
12/23/2025, 7:59 AM{ "should_end_call": true, "reason": "..." }).
• In your agent runtime, inspect the tool's return value after the tool call completes.
• If the parameter is true, end the session for everyone by calling the LiveKit Server API to delete the room, or if you only want the agent to leave, call the agent session shutdown.
Options
• End for everyone: call LiveKit Server API DeleteRoom (disconnects all participants).
• End only the agent: call session.shutdown(drain=True) (agent leaves, participants stay).
Minimal Python example (Agent tool + handling)
from livekit.agents import function_tool, Agent, JobContext
from livekit import api
@function_tool()
async def check_condition(self, context):
# tool logic returns a dict with should_end_call
return {"should_end_call": True, "reason": "policy triggered"}
class MyAgent(Agent):
def __init__(self, job_context: JobContext):
super().__init__(instructions="You are helpful.")
self.job_context = job_context
async def on_tool_result(self, tool_name: str, result: dict):
# This method shows where to inspect tool results; adapt based on your agent hooks
if result.get("should_end_call"):
# Option A: terminate room (disconnects everyone)
await self.job_context.api.room.delete_room(
api.DeleteRoomRequest(room=self.job_context.room.name)
)
return
# Option B: agent leaves but room remains
# await self.job_context.session.shutdown(drain=True)
Notes
• DeleteRoom requires server API credentials (LIVEKIT_API_KEY / SECRET) and will disconnect all participants immediately.
• If you only want to disconnect a single participant, you can remove that participant via the server API (delete participant), but DeleteRoom is the simplest way to stop the whole call.
• If your runtime's agent hooks differ, implement the check in the callback that receives tool results (inspect the tool return and call delete_room or session.shutdown accordingly).
If you share a short snippet of how you currently handle tool results (redact any keys), I can give a tailored code sample for your exact runtime and language.
Sources: Job lifecycle | LiveKit docs | Agents telephony integration | LiveKit docs | Outbound Calling Script | LiveKit docs | Gemini Live API plugin | LiveKit docsrefined-airport-68974
12/23/2025, 8:36 AMtall-airline-16756
12/23/2025, 8:36 AMshould_end_call: true, compose the final agent reply (TTS).
• Send that reply through your AgentSession/voice output API and await confirmation that audio has been sent/played.
• After confirmation, call delete_room (or session.shutdown(drain=True) if agent-only) to close the session.
Python example (conceptual)
# inside your agent/tool result handler
if result.get("should_end_call"):
final_text = "This call will end now. Goodbye."
# Send speech via the session/llm output API (replace with your actual send method)
await session.send_text(final_text) # triggers TTS/audio
await session.flush_audio() # await delivery/playback (or an equivalent ACK)
# Now end the room for everyone
await job_context.api.room.delete_room(
api.DeleteRoomRequest(room=job_context.room.name)
)
Node/TS example (conceptual)
if (result.should_end_call) {
const finalText = "The call will end now. Goodbye.";
await session.sendText(finalText); // emits TTS via Gemini/Realtime
await session.waitForAudioSent(); // wait for audio send/playback confirmation
await livekitApi.deleteRoom({ room: ctx.room.name });
}
Implementation notes
• How you wait for the speech to finish depends on your runtime:
◦ If the voice plugin emits an event when audio frames are sent or playback completes, await that event.
◦ If only TTS send is available, wait a short safe delay after sending (e.g., ensure audio buffer has been flushed) or track message ACKs.
• Using session.shutdown(drain=True) can let the agent gracefully leave after finishing output if you want participants to remain.
• delete_room immediately disconnects everyone — ensure the final audio has been delivered before calling it.
If you share which SDK/language and how you currently send the bot's TTS (session methods or events you receive), I’ll give an exact code sample to match your runtime and how to await audio completion.refined-airport-68974
12/23/2025, 8:52 AMlivekit==1.0.23
livekit-agents==1.3.9
livekit-api==1.1.0
livekit-blingfire==1.1.0
livekit-plugins-google==1.3.9
livekit-plugins-noise-cancellation==0.2.5
livekit-protocol==1.1.1tall-airline-16756
12/23/2025, 8:52 AMAgentSession has no sendText method. Use session.say() to speak, then wait for playout and shut down or delete the room.
Minimal Python example (fits your versions):
from livekit.agents import function_tool, Agent, JobContext, FunctionContext
@function_tool()
async def end_call_tool(self, ctx: FunctionContext):
# Tool determines the call should end
final_text = "This call will end now. Goodbye."
# 1) queue TTS
await ctx.session.say(final_text)
# 2) wait for audio playout to complete
await ctx.wait_for_playout()
# 3a) To end for everyone: delete the room via server API
await ctx.job_context.api.room.delete_room(
# room name from job_context
# replace with correct field if needed
ctx.job_context.room.name
)
# 3b) OR to have the agent leave but keep room: await ctx.session.shutdown(drain=True)
Notes
• ctx.session.say() produces TTS and returns when queued; ctx.wait_for_playout() ensures playback finishes.
• Use delete_room (server API) to disconnect all participants; use session.shutdown(drain=True) if you want only the agent to leave gracefully.
• Ensure your agent has LiveKit server API credentials available via ctx.job_context.api (or configured client) to call delete_room.
If you share the snippet where you currently handle tool results (redact secrets), I’ll adapt this example to your exact hook names and context objects.
Sources: Agent speech and audio | LiveKit docs | Job lifecycle | LiveKit docs | Sending text | LiveKit docsrefined-airport-68974
12/23/2025, 8:57 AMsession = AgentSession(
# stt=sarvam.STT(
# language='en-IN',
# model="saarika:v2.5", # Latest Sarvam STT model
# ),
llm=google.realtime.RealtimeModel(
model="gemini-2.5-flash-native-audio-preview-12-2025",
voice="Aoede",
temperature=0.8,
# turn_detection=ServerVad(
# prefix_padding_ms=300, # Includes slight audio before speech start (prevents cutting off first words)
# silence_duration_ms=500, # CRITICAL: Reduce this. Default is often 1000ms+. 500-600ms is snappy.
# threshold=0.5, # Sensitivity (0.0 to 1.0). 0.5 is balanced.
# )
),
preemptive_generation=True,
user_away_timeout=30.0,
)tall-airline-16756
12/23/2025, 8:58 AMctx.session.say() will work with that AgentSession configuration. The Gemini Realtime model (configured under llm) supplies TTS voice, and session.say() queues synthesized speech from the agent. Sequence it like this when your tool returns `should_end_call: true`:
# inside your tool handler or agent callback, with access to FunctionContext (ctx) and JobContext (ctx.job_context)
await ctx.session.say("This call will end now. Goodbye.")
await ctx.wait_for_playout() # ensure audio finished playing
# Option A — end for everyone:
await ctx.job_context.api.room.delete_room(api.DeleteRoomRequest(room=ctx.job_context.room.name))
# Option B — agent leaves but room remains:
# await ctx.session.shutdown(drain=True)
Notes
• Your posted AgentSession config (Gemini model, voice="Aoede") supports TTS; no extra sendText method is needed.
• Use delete_room to disconnect all participants; use shutdown(drain=True) to have only the agent leave.
• If you need help hooking this into your exact callback where tool results arrive, share that snippet (redact secrets) and I’ll adapt the code.refined-airport-68974
12/23/2025, 8:59 AMhandle = await self.session.say(transfer_message)
# 2) wait for audio playout to complete
await handle.wait_for_playout()
is this right?tall-airline-16756
12/23/2025, 8:59 AMhandle = await self.session.say(transfer_message) queues TTS and returns a SpeechHandle.
• await handle.wait_for_playout() waits until playback completes.
After that you can safely end the call. Example follow-up actions:
• End for everyone:
await ctx.job_context.api.room.delete_room(api.DeleteRoomRequest(room=ctx.job_context.room.name))
• Agent-only graceful leave:
await ctx.session.shutdown(drain=True)
Tip: if you call delete_room, ensure playout finished (as you do) because delete_room disconnects participants immediately.refined-airport-68974
12/23/2025, 9:18 AMtall-airline-16756
12/23/2025, 9:18 AMdrain=True tells the session to finish sending any queued work (notably pending TTS/audio) before leaving.
Behavior:
• If drain=True, the agent will complete queued speech and other outgoing tasks, wait for playout (if applicable), then disconnect. This lets the agent finish its final message gracefully.
• If drain=False (or omitted), the session closes immediately and any queued audio may be aborted or not played.
When to use:
• Use drain=True when you want the agent to speak a final message and ensure it finishes before leaving.
• Use drain=False (or immediate close/delete_room) when you must terminate right away (e.g., emergency stop).
Example sequence to speak then leave:
handle = await ctx.session.say("Goodbye.")
await handle.wait_for_playout()
await ctx.session.shutdown(drain=True)
Note: Calling delete_room after playout will forcibly disconnect all participants immediately; shutdown(drain=True) only makes the agent leave gracefully.