Skip to content

Commit 2e0e44b

Browse files
committed
fix: preserve cache_metadata and usage_metadata in VertexAiSessionService event round-trip
VertexAiSessionService was dropping cache_metadata and usage_metadata fields during Event serialization/deserialization. This caused ContextCacheRequestProcessor to never find previous cache metadata, creating a new cache on every LLM call instead of reusing existing ones. The fix adds cache_metadata and usage_metadata to the event_metadata dict during append_event (write path) and restores them in _from_api_event (read path), matching the behavior of other session service implementations. Fixes #4698
1 parent 36e76b9 commit 2e0e44b

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

src/google/adk/sessions/vertex_ai_session_service.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from ..events.event import Event
3535
from ..events.event_actions import EventActions
3636
from ..events.event_actions import EventCompaction
37+
from ..models.cache_metadata import CacheMetadata
3738
from ..utils.vertex_ai_utils import get_express_mode_api_key
3839
from .base_session_service import BaseSessionService
3940
from .base_session_service import GetSessionConfig
@@ -289,6 +290,14 @@ async def append_event(self, session: Session, event: Event) -> Event:
289290
else None
290291
),
291292
}
293+
if event.usage_metadata:
294+
metadata_dict['usage_metadata'] = event.usage_metadata.model_dump(
295+
exclude_none=True, mode='json'
296+
)
297+
if event.cache_metadata:
298+
metadata_dict['cache_metadata'] = event.cache_metadata.model_dump(
299+
exclude_none=True, mode='json'
300+
)
292301
if event.grounding_metadata:
293302
metadata_dict['grounding_metadata'] = event.grounding_metadata.model_dump(
294303
exclude_none=True, mode='json'
@@ -389,6 +398,14 @@ def _from_api_event(api_event_obj: vertexai.types.SessionEvent) -> Event:
389398
getattr(event_metadata, 'grounding_metadata', None),
390399
types.GroundingMetadata,
391400
)
401+
usage_metadata = _session_util.decode_model(
402+
getattr(event_metadata, 'usage_metadata', None),
403+
types.GenerateContentResponseUsageMetadata,
404+
)
405+
cache_metadata = _session_util.decode_model(
406+
getattr(event_metadata, 'cache_metadata', None),
407+
CacheMetadata,
408+
)
392409
else:
393410
long_running_tool_ids = None
394411
partial = None
@@ -398,6 +415,8 @@ def _from_api_event(api_event_obj: vertexai.types.SessionEvent) -> Event:
398415
custom_metadata = None
399416
compaction_data = None
400417
grounding_metadata = None
418+
usage_metadata = None
419+
cache_metadata = None
401420

402421
if actions:
403422
actions_dict = actions.model_dump(exclude_none=True, mode='python')
@@ -433,5 +452,7 @@ def _from_api_event(api_event_obj: vertexai.types.SessionEvent) -> Event:
433452
branch=branch,
434453
custom_metadata=custom_metadata,
435454
grounding_metadata=grounding_metadata,
455+
usage_metadata=usage_metadata,
456+
cache_metadata=cache_metadata,
436457
long_running_tool_ids=long_running_tool_ids,
437458
)

tests/unittests/sessions/test_vertex_ai_session_service.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from google.adk.events.event import Event
2929
from google.adk.events.event_actions import EventActions
3030
from google.adk.events.event_actions import EventCompaction
31+
from google.adk.models.cache_metadata import CacheMetadata
3132
from google.adk.sessions.base_session_service import GetSessionConfig
3233
from google.adk.sessions.session import Session
3334
from google.adk.sessions.vertex_ai_session_service import VertexAiSessionService
@@ -249,6 +250,8 @@ def _convert_to_object(data):
249250
'artifact_delta',
250251
'custom_metadata',
251252
'requested_auth_configs',
253+
'cache_metadata',
254+
'usage_metadata',
252255
]:
253256
kwargs[key] = value
254257
else:
@@ -911,3 +914,63 @@ async def test_append_event_with_compaction_and_custom_metadata():
911914
# User custom_metadata is preserved without the internal _compaction key
912915
assert appended_event.custom_metadata == {'user_key': 'user_value'}
913916
assert '_compaction' not in (appended_event.custom_metadata or {})
917+
918+
919+
@pytest.mark.asyncio
920+
@pytest.mark.usefixtures('mock_get_api_client')
921+
async def test_append_event_with_cache_and_usage_metadata():
922+
"""cache_metadata and usage_metadata round-trip through append and get."""
923+
session_service = mock_vertex_ai_session_service()
924+
session = await session_service.get_session(
925+
app_name='123', user_id='user', session_id='1'
926+
)
927+
assert session is not None
928+
929+
cache_meta = CacheMetadata(
930+
cache_name='projects/123/locations/us-central1/cachedContents/456',
931+
expire_time=9999999999.0,
932+
fingerprint='abc123hash',
933+
invocations_used=3,
934+
contents_count=10,
935+
created_at=1700000000.0,
936+
)
937+
usage_meta = genai_types.GenerateContentResponseUsageMetadata(
938+
prompt_token_count=100,
939+
candidates_token_count=50,
940+
total_token_count=150,
941+
cached_content_token_count=80,
942+
)
943+
event_to_append = Event(
944+
invocation_id='cache_test_invocation',
945+
author='model',
946+
timestamp=1734005536.0,
947+
content=genai_types.Content(
948+
parts=[genai_types.Part(text='cached response')]
949+
),
950+
cache_metadata=cache_meta,
951+
usage_metadata=usage_meta,
952+
)
953+
954+
await session_service.append_event(session, event_to_append)
955+
956+
retrieved_session = await session_service.get_session(
957+
app_name='123', user_id='user', session_id='1'
958+
)
959+
assert retrieved_session is not None
960+
961+
appended_event = retrieved_session.events[-1]
962+
# cache_metadata is preserved
963+
assert appended_event.cache_metadata is not None
964+
assert appended_event.cache_metadata.cache_name == (
965+
'projects/123/locations/us-central1/cachedContents/456'
966+
)
967+
assert appended_event.cache_metadata.fingerprint == 'abc123hash'
968+
assert appended_event.cache_metadata.invocations_used == 3
969+
assert appended_event.cache_metadata.contents_count == 10
970+
assert appended_event.cache_metadata.created_at == 1700000000.0
971+
# usage_metadata is preserved
972+
assert appended_event.usage_metadata is not None
973+
assert appended_event.usage_metadata.prompt_token_count == 100
974+
assert appended_event.usage_metadata.candidates_token_count == 50
975+
assert appended_event.usage_metadata.total_token_count == 150
976+
assert appended_event.usage_metadata.cached_content_token_count == 80

0 commit comments

Comments
 (0)