diff --git a/changelog/4147.fixed.md b/changelog/4147.fixed.md new file mode 100644 index 000000000..985990c4d --- /dev/null +++ b/changelog/4147.fixed.md @@ -0,0 +1 @@ +- Fixed Gemini Live message handling to process all `server_content` fields independently. Gemini 3.x can bundle multiple fields (e.g. `model_turn` and `output_transcription`) on the same message, but the previous `elif` chain only processed the first match, silently dropping the rest. diff --git a/src/pipecat/services/google/gemini_live/llm.py b/src/pipecat/services/google/gemini_live/llm.py index 7564d341b..f58972ea4 100644 --- a/src/pipecat/services/google/gemini_live/llm.py +++ b/src/pipecat/services/google/gemini_live/llm.py @@ -1371,7 +1371,12 @@ class GeminiLiveLLMService(LLMService): # Reset failure counter if connection has been stable self._check_and_reset_failure_counter() - if message.server_content and message.server_content.interrupted: + # server_content fields are NOT mutually exclusive — + # Gemini 3.x can bundle multiple content fields and + # turn_complete on the same message, so process the + # content-bearing fields before closing the turn. + sc = message.server_content + if sc and sc.interrupted: # NOTE: while the service triggers interruptions in # the specific case of barge-ins, it does *not* # emit UserStarted/StoppedSpeakingFrames, as the @@ -1384,23 +1389,30 @@ class GeminiLiveLLMService(LLMService): # turn strategies. logger.debug("Gemini VAD: interrupted signal received") await self.broadcast_interruption() - elif message.server_content and message.server_content.model_turn: + if sc and sc.model_turn: await self._handle_msg_model_turn(message) - elif message.server_content and message.server_content.turn_complete: + if sc and sc.input_transcription: + await self._handle_msg_input_transcription(message) + if sc and sc.output_transcription: + await self._handle_msg_output_transcription(message) + if ( + sc + and sc.grounding_metadata + and not sc.model_turn + and not sc.output_transcription + ): + # model_turn/output_transcription already defer + # bundled grounding metadata to turn_complete. + await self._handle_msg_grounding_metadata(message) + if sc and sc.turn_complete: if not message.usage_metadata: logger.warning("Received turn_complete without usage_metadata") await self._handle_msg_turn_complete(message) if message.usage_metadata: await self._handle_msg_usage_metadata(message) - elif message.server_content and message.server_content.input_transcription: - await self._handle_msg_input_transcription(message) - elif message.server_content and message.server_content.output_transcription: - await self._handle_msg_output_transcription(message) - elif message.server_content and message.server_content.grounding_metadata: - await self._handle_msg_grounding_metadata(message) - elif message.tool_call: + if message.tool_call: await self._handle_msg_tool_call(message) - elif message.session_resumption_update: + if message.session_resumption_update: self._handle_msg_resumption_update(message) except Exception as e: if not self._disconnecting: