From 6b93a72b18d46d3781f59f3e570ec98e0a0957b2 Mon Sep 17 00:00:00 2001 From: chelsealong Date: Sat, 1 Aug 2026 08:11:38 +0800 Subject: [PATCH] fix(sessions): preserve original Event.id from raw_event in VertexAiSessionService _from_api_event overwrote the ADK event id with the Vertex resource name, so the same logical event reported a different id depending on whether it was seen via streaming or via a reload. Fall back to the resource name only when raw_event carries no id, which keeps older data working. Port of #6531 to the v1 line, requested by @ashubham on that PR. --- .../adk/sessions/vertex_ai_session_service.py | 5 ++- .../test_vertex_ai_session_service.py | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/google/adk/sessions/vertex_ai_session_service.py b/src/google/adk/sessions/vertex_ai_session_service.py index 8c1fdc134e2..85cfb4f9dc3 100644 --- a/src/google/adk/sessions/vertex_ai_session_service.py +++ b/src/google/adk/sessions/vertex_ai_session_service.py @@ -436,10 +436,13 @@ def _from_api_event(api_event_obj: vertexai.types.SessionEvent) -> Event: event_dict = copy.deepcopy(raw_event_dict) timestamp_obj = getattr(api_event_obj, 'timestamp', None) event_dict.update({ - 'id': api_event_obj.name.split('/')[-1], 'invocation_id': getattr(api_event_obj, 'invocation_id', None), 'author': getattr(api_event_obj, 'author', None), }) + # Preserve the original ADK event id from raw_event so it stays stable + # across streaming and reload; only fall back to the Vertex resource + # name if raw_event lacks an id (older data). + event_dict.setdefault('id', api_event_obj.name.split('/')[-1]) if timestamp_obj: event_dict['timestamp'] = timestamp_obj.timestamp() return Event.model_validate(event_dict) diff --git a/tests/unittests/sessions/test_vertex_ai_session_service.py b/tests/unittests/sessions/test_vertex_ai_session_service.py index c5c9996ef5e..f87a334aa3a 100644 --- a/tests/unittests/sessions/test_vertex_ai_session_service.py +++ b/tests/unittests/sessions/test_vertex_ai_session_service.py @@ -255,6 +255,24 @@ def _generate_mock_events_for_session_5(num_events): 'user_id': 'user_with_override', } +MOCK_EVENT_WITH_RAW_EVENT_ID_JSON = [{ + 'name': ( + 'projects/test-project/locations/test-location/' + 'reasoningEngines/123/sessions/override/events/658731057016733696' + ), + 'invocationId': 'e-1', + 'author': 'user_with_override', + 'timestamp': '2024-12-12T12:12:12.123456Z', + 'content': {}, + 'actions': {}, + 'rawEvent': { + 'id': 'c452feb0-b9ad-48b1-b863-c7dd2f085378', + 'invocationId': 'e-1', + 'author': 'user_with_override', + 'content': {}, + }, +}] + MOCK_SESSION = Session( app_name='123', user_id='user', @@ -1311,3 +1329,30 @@ class DummyModel(pydantic.BaseModel): assert appended_event.actions.compaction is not None assert appended_event.actions.compaction.start_timestamp == 1000.0 + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('mock_get_api_client') +async def test_get_session_preserves_original_event_id_from_raw_event( + mock_api_client_instance: MockAsyncClient, +) -> None: + """get_session should keep the original ADK Event.id persisted in raw_event. + + Regression test for the raw_event id being overwritten by the Vertex event + resource name, which made the same logical event report a different id + depending on whether it was observed via streaming or via a reload. + """ + mock_api_client_instance.session_dict['6'] = MOCK_SESSION_WITH_OVERRIDE_JSON + mock_api_client_instance.event_dict['6'] = ( + copy.deepcopy(MOCK_EVENT_WITH_RAW_EVENT_ID_JSON), + None, + ) + session_service = mock_vertex_ai_session_service() + session = await session_service.get_session( + app_name='123', user_id='user_with_override', session_id='6' + ) + assert session is not None + assert len(session.events) == 1 + assert ( + session.events[0].id == 'c452feb0-b9ad-48b1-b863-c7dd2f085378' + ), 'Event.id should come from raw_event, not the Vertex resource name.'