Skip to content

Commit 0aac440

Browse files
committed
fix: Fix Vertex AI Live API session replay on reconnect / modality switch
Previously seeding Gemini 3.1 live history on Vertex AI collapsed turns into a single user turn and hardcoded turn_complete=True, causing the model to immediately trigger a tool call upon reconnect. Since history_config is now supported on Vertex AI, we can remove the workaround and determine turn_complete based on the last turn role (preventing model execution when history ends in a model turn) Change-Id: I624a7adc8acdd9a88f0fc17e08051ac8a5b6f9bd
1 parent e72bf9b commit 0aac440

2 files changed

Lines changed: 22 additions & 40 deletions

File tree

src/google/adk/models/gemini_llm_connection.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,30 +83,10 @@ async def send_history(self, history: list[types.Content]):
8383
]
8484

8585
if contents:
86-
# Gemini Enterprise Agent Platform does not support history_config in the SDK.
87-
# To initialize a live session with prior history without hitting a 1007
88-
# protocol error (invalid role mid-session), we consolidate previous multi-turn
89-
# interactions into a unified contextual preamble on a single user role turn.
90-
if (
91-
self._is_gemini_3_1_flash_live
92-
and self._api_backend != GoogleLLMVariant.GEMINI_API
93-
):
94-
collapsed_text = 'Previous conversation history:\n'
95-
for c in contents:
96-
text_parts = ''.join(p.text for p in c.parts if p.text)
97-
collapsed_text += f'[{c.role}]: {text_parts}\n'
98-
contents = [
99-
types.Content(
100-
role='user', parts=[types.Part.from_text(text=collapsed_text)]
101-
)
102-
]
103-
10486
logger.debug('Sending history to live connection: %s', contents)
10587
await self._gemini_session.send_client_content(
10688
turns=contents,
107-
turn_complete=True
108-
if self._is_gemini_3_1_flash_live
109-
else (contents[-1].role == 'user'),
89+
turn_complete=history[-1].role == 'user',
11090
)
11191
else:
11292
logger.info('no content is sent')

tests/unittests/models/test_gemini_llm_connection.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,31 +1507,42 @@ async def mock_receive_generator():
15071507

15081508
@pytest.mark.asyncio
15091509
async def test_send_history_gemini_31_turn_complete(mock_gemini_session):
1510-
"""Verify Gemini 3.1 Live history seeding explicitly appends turn_complete=True."""
1510+
"""Verify Gemini 3.1 Live history seeding turn_complete logic based on last turn role."""
15111511
from google.adk.models.google_llm import GoogleLLMVariant
15121512

15131513
conn = GeminiLlmConnection(
15141514
mock_gemini_session,
15151515
api_backend=GoogleLLMVariant.GEMINI_API,
15161516
model_version='gemini-3.1-flash-live-preview',
15171517
)
1518-
mock_gemini_session.send_client_content = mock.AsyncMock()
15191518

1520-
mock_contents = [
1519+
# Case 1: Last turn is from model (turn_complete should be False)
1520+
mock_gemini_session.send_client_content = mock.AsyncMock()
1521+
mock_contents_model_last = [
15211522
types.Content(role='user', parts=[types.Part.from_text(text='hi')]),
15221523
types.Content(role='model', parts=[types.Part.from_text(text='hello')]),
15231524
]
1524-
await conn.send_history(mock_contents)
1525+
await conn.send_history(mock_contents_model_last)
1526+
mock_gemini_session.send_client_content.assert_called_once_with(
1527+
turns=mock_contents_model_last,
1528+
turn_complete=False,
1529+
)
15251530

1531+
# Case 2: Last turn is from user (turn_complete should be True)
1532+
mock_gemini_session.send_client_content = mock.AsyncMock()
1533+
mock_contents_user_last = [
1534+
types.Content(role='user', parts=[types.Part.from_text(text='hi')]),
1535+
]
1536+
await conn.send_history(mock_contents_user_last)
15261537
mock_gemini_session.send_client_content.assert_called_once_with(
1527-
turns=mock_contents,
1538+
turns=mock_contents_user_last,
15281539
turn_complete=True,
15291540
)
15301541

15311542

15321543
@pytest.mark.asyncio
1533-
async def test_send_history_collapse_vertex_ai(mock_gemini_session):
1534-
"""Verify history prompt collapse when seeding Gemini 3.1 Live on Vertex AI backend."""
1544+
async def test_send_history_no_collapse_vertex_ai(mock_gemini_session):
1545+
"""Verify history is not collapsed when seeding Gemini 3.1 Live on Vertex AI backend."""
15351546
from google.adk.models.google_llm import GoogleLLMVariant
15361547

15371548
conn = GeminiLlmConnection(
@@ -1547,18 +1558,9 @@ async def test_send_history_collapse_vertex_ai(mock_gemini_session):
15471558
]
15481559
await conn.send_history(mock_contents)
15491560

1550-
assert mock_gemini_session.send_client_content.call_count == 1
1551-
called_turns = mock_gemini_session.send_client_content.call_args.kwargs[
1552-
'turns'
1553-
]
1554-
assert len(called_turns) == 1
1555-
assert called_turns[0].role == 'user'
1556-
assert 'Previous conversation history:' in called_turns[0].parts[0].text
1557-
assert '[user]: hi' in called_turns[0].parts[0].text
1558-
assert '[model]: hello' in called_turns[0].parts[0].text
1559-
assert (
1560-
mock_gemini_session.send_client_content.call_args.kwargs['turn_complete']
1561-
is True
1561+
mock_gemini_session.send_client_content.assert_called_once_with(
1562+
turns=mock_contents,
1563+
turn_complete=False,
15621564
)
15631565

15641566

0 commit comments

Comments
 (0)