fix(google): allow generate_reply without a mutable chat context - #7193
fix(google): allow generate_reply without a mutable chat context#7193Darshak03 wants to merge 1 commit into
Conversation
generate_reply raised RealtimeError whenever mutable_chat_context was False, which is every Gemini 3.1 Live model, leaving them with no way to open an agent-initiated turn: greetings, replies after a handoff and prompts after a tool result all go through it. The capability describes whether the client may append to the conversation, not whether the model can generate. A session connected with history_config=initial_history_in_client_content keeps its own history and rejects the placeholder user turn used as the trigger, but it does accept realtime text input. Pick the trigger the session accepts instead of refusing. Fixes livekit#7192
| self._send_client_event( | ||
| types.LiveClientRealtimeInput(text=instructions if is_given(instructions) else ".") | ||
| ) |
There was a problem hiding this comment.
π‘ Text input disappears before generation
With Gemini 3.1, generate_reply(user_input=...) sends "." because instructions is absent. The immutable context path never forwards the user's text, so the model answers without it.
Prompt for agents
Gemini 3.1 sessions cannot receive user_input through AgentActivity's existing update_chat_ctx flow. AgentActivity adds user_input to a copied context, but RealtimeSession._sync_chat_ctx intentionally skips non-tool context additions when mutable_chat_context is false. generate_reply then receives no user_input parameter and sends the fallback dot. Preserve the provider-neutral generate_reply(user_input=...) contract by adding an immutable-session input path that forwards the actual user text exactly once before generation. Keep per-turn instructions distinct from user content, and add an end-to-end unit test through AgentSession or AgentActivity rather than testing RealtimeSession.generate_reply alone.
Was this helpful? React with π or π to provide feedback.
There was a problem hiding this comment.
The gap is real, but it is pre-existing and not in the code this PR touches, and this change strictly improves it.
Two corrections on the mechanism first. generate_reply has no user_input parameter β the abstract signature in llm/realtime.py is instructions, tool_choice, tools, and no plugin extends it. user_input is a private parameter of AgentActivity._realtime_reply_task, and it is never forwarded to generate_reply. The text is delivered a step earlier:
if user_input is not None:
chat_ctx = self._rt_session.chat_ctx.copy()
msg = chat_ctx.add_message(role="user", content=user_input)
await self._rt_session.update_chat_ctx(chat_ctx) # carries the text
...
generate_reply_fut = self._rt_session.generate_reply(...) # only triggersSo the text is dropped by _sync_chat_ctx, which builds turns only when mutable_chat_context is true. That is where the gap lives, and it predates this PR.
On main today generate_reply raises RealtimeError for these models, so this path produces no turn at all. After this change the turn happens, with the user text still missing β a broken path becomes a partial one. Audio input is unaffected either way, since _realtime_generation_task sends audio straight to the model.
Worth flagging for whoever picks the remaining gap up: send_realtime_input(text=...) is itself a generation trigger β that is exactly why this PR uses it. Forwarding user text through it inside update_chat_ctx would start a turn, and generate_reply's "." would then start a second one. Closing the gap needs either the plugin holding the unsent user text so generate_reply can send it as the trigger, or letting generate_reply carry user content β neither of which belongs in this change.
generate_reply()raisedRealtimeErrorwhenevermutable_chat_contextwas False. That capability is computed as"3.1" not in model, so every Gemini 3.1 Live model hit it β leaving those models with no way to open an agent-initiated turn at all. Greetings, replies after a handoff, and prompts issued after a tool result all go throughgenerate_reply(), and each raised instead.The capability describes whether the client may append to the conversation mid-session, not whether the model can generate. The plugin already acts on exactly that when it connects:
A session configured that way keeps its own history and rejects the placeholder user turn
generate_reply()sends as a trigger. That is a reason to send a different trigger, not to refuse β the same session accepts realtime text input, which starts a generation without touching the context.Fix: drop the refusal and branch on the capability to pick the trigger.
instructionsis frequentlyNOT_GIVEN(agent_activitypassesinstructions or NOT_GIVEN), so the realtime-input path falls back to the same"."nudge the other path already uses._send_taskalready routesLiveClientRealtimeInput.texttosend_realtime_input, so the send path is unchanged.Nothing in
agent_activitygatesgenerate_reply()onmutable_chat_contextβ its uses there are handoff reuse, chat context reset, and the interrupted-tool commit β so this is contained to the plugin, and no capability is added or changed.Tests: three, covering the mutable path (placeholder turn, unchanged), the immutable path (realtime text, no client content, no longer refused up front), and the missing-
instructionsfallback. The two immutable-path tests fail onmainwithRealtimeError: generate_reply is not compatible with 'gemini-3.1-flash-live-preview'.Fixes #7192