Skip to content

Commit 5ce33b9

Browse files
GWealecopybara-github
authored andcommitted
fix: Capture and include LLM usage metadata in summarized events
This change modifies the LLM event summarizer to extract the usage_metadata from the LLM's generate content response and include it in the newly created compacted Event Close #4014 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 905222465
1 parent 0c4f157 commit 5ce33b9

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

src/google/adk/apps/llm_event_summarizer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ async def maybe_summarize_events(
104104
contents=[Content(role='user', parts=[Part(text=prompt)])],
105105
)
106106
summary_content = None
107+
summary_usage_metadata = None
107108
async for llm_response in self._llm.generate_content_async(
108109
llm_request, stream=False
109110
):
110111
if llm_response.content:
111112
summary_content = llm_response.content
113+
summary_usage_metadata = llm_response.usage_metadata
112114
break
113115

114116
if summary_content is None:
@@ -132,4 +134,5 @@ async def maybe_summarize_events(
132134
author='user',
133135
actions=actions,
134136
invocation_id=Event.new_id(),
137+
usage_metadata=summary_usage_metadata,
135138
)

tests/unittests/apps/test_llm_event_summarizer.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414

1515
import unittest
1616
from unittest.mock import AsyncMock
17-
from unittest.mock import Mock
1817

1918
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
2019
from google.adk.events.event import Event
2120
from google.adk.events.event_actions import EventActions
2221
from google.adk.events.event_actions import EventCompaction
2322
from google.adk.models.base_llm import BaseLlm
2423
from google.adk.models.llm_request import LlmRequest
24+
from google.adk.models.llm_response import LlmResponse
25+
from google.genai import types
2526
from google.genai.types import Content
2627
from google.genai.types import FunctionCall
2728
from google.genai.types import FunctionResponse
@@ -57,10 +58,13 @@ async def test_maybe_compact_events_success(self):
5758
expected_prompt = self.compactor._DEFAULT_PROMPT_TEMPLATE.format(
5859
conversation_history=expected_conversation_history
5960
)
60-
mock_llm_response = Mock(content=Content(parts=[Part(text='Summary')]))
61+
llm_response = LlmResponse(
62+
content=Content(parts=[Part(text='Summary')]),
63+
usage_metadata=None,
64+
)
6165

6266
async def async_gen():
63-
yield mock_llm_response
67+
yield llm_response
6468

6569
self.mock_llm.generate_content_async.return_value = async_gen()
6670

@@ -72,6 +76,7 @@ async def async_gen():
7276
'Summary',
7377
)
7478
self.assertEqual(compacted_event.author, 'user')
79+
self.assertIsNone(compacted_event.usage_metadata)
7580
self.assertIsNotNone(compacted_event.actions)
7681
self.assertIsNotNone(compacted_event.actions.compaction)
7782
self.assertEqual(compacted_event.actions.compaction.start_timestamp, 1.0)
@@ -94,16 +99,42 @@ async def test_maybe_compact_events_empty_llm_response(self):
9499
events = [
95100
self._create_event(1.0, 'Hello', 'user'),
96101
]
97-
mock_llm_response = Mock(content=None)
102+
llm_response = LlmResponse(content=None, usage_metadata=None)
98103

99104
async def async_gen():
100-
yield mock_llm_response
105+
yield llm_response
101106

102107
self.mock_llm.generate_content_async.return_value = async_gen()
103108

104109
compacted_event = await self.compactor.maybe_summarize_events(events=events)
105110
self.assertIsNone(compacted_event)
106111

112+
async def test_maybe_compact_events_includes_usage_metadata(self):
113+
events = [
114+
self._create_event(1.0, 'Hello', 'user'),
115+
self._create_event(2.0, 'Hi there!', 'model'),
116+
]
117+
usage_metadata = types.GenerateContentResponseUsageMetadata(
118+
prompt_token_count=10,
119+
candidates_token_count=5,
120+
)
121+
llm_response = LlmResponse(
122+
content=Content(parts=[Part(text='Summary')]),
123+
usage_metadata=usage_metadata,
124+
)
125+
126+
async def async_gen():
127+
yield llm_response
128+
129+
self.mock_llm.generate_content_async.return_value = async_gen()
130+
131+
compacted_event = await self.compactor.maybe_summarize_events(events=events)
132+
133+
self.assertIsNotNone(compacted_event)
134+
self.assertEqual(compacted_event.usage_metadata, usage_metadata)
135+
self.assertEqual(compacted_event.usage_metadata.prompt_token_count, 10)
136+
self.assertEqual(compacted_event.usage_metadata.candidates_token_count, 5)
137+
107138
async def test_maybe_compact_events_empty_input(self):
108139
compacted_event = await self.compactor.maybe_summarize_events(events=[])
109140
self.assertIsNone(compacted_event)

0 commit comments

Comments
 (0)