5-12-23 13:16:33,647 - livekit.plugins.google - WA...
# ask-ai
r
2025-12-23 131633,647 - livekit.plugins.google - WARNING - tool_choice is not supported by the Google Realtime API.
t
Thanks for reaching out, Gaurav. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I see you're asking about the warning: "tool_choice is not supported by the Google Realtime API." This warning means the Gemini Live (Google Realtime) model does not accept the
tool_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 | DeepWiki
r
suppose that gemini realtime made a tool call in the middle of the voice conversation. then I have to close the connection if a certain parameter is returned as true in the tool response. how would i do that?
t
Thanks — you're asking how to detect a tool result from Gemini Realtime during a voice conversation and close the connection when a returned parameter is true. Below is a concise, workable approach and example you can adapt. High-level approach • Implement the tool so it returns a structured result (e.g.,
{ "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)
Copy code
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 docs
r
what if I want the bot to say something before the room is deleted?
t
Good question — you can have the agent speak a final message before closing the room by sequencing three steps: 1) generate/send the bot's final speech, 2) wait for that speech to be delivered (or for an acknowledgement), then 3) call the LiveKit server API to delete the room (or shut down the agent). Below is a concise pattern and code examples you can adapt. Key steps • When the tool returns
should_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)
Copy code
# 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)
Copy code
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.
r
Unresolved attribute reference 'sendText' for class 'AgentSession'
Copy code
livekit==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.1
t
You're seeing that error because
AgentSession
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):
Copy code
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 docs
r
will this say method work in this case?
Copy code
session = 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,
)
t
Yes —
ctx.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`:
Copy code
# 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.
r
Copy code
handle = await self.session.say(transfer_message)
# 2) wait for audio playout to complete
await handle.wait_for_playout()
is this right?
t
Yes — that is correct. •
handle = 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:
Copy code
await ctx.job_context.api.room.delete_room(api.DeleteRoomRequest(room=ctx.job_context.room.name))
• Agent-only graceful leave:
Copy code
await ctx.session.shutdown(drain=True)
Tip: if you call
delete_room
, ensure playout finished (as you do) because
delete_room
disconnects participants immediately.
r
ctx.session.shutdown(drain=True) what does drain=true mean here?
t
drain=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:
Copy code
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.