fix: Make compactor optional in EventsCompactionConfig and add a default

If `EventsCompactionConfig` is provided without a `compactor`, a `SlidingWindowCompactor` is now automatically instantiated using the `root_agent`'s LLM. This simplifies configuration by providing a sensible default.

PiperOrigin-RevId: 816038579
This commit is contained in:
Hangfei Lin
2025-10-06 22:20:49 -07:00
committed by Copybara-Service
parent 238472d083
commit 3f4bd67b49
6 changed files with 36 additions and 24 deletions
+5 -3
View File
@@ -21,7 +21,7 @@ from pydantic import Field
from ..agents.base_agent import BaseAgent from ..agents.base_agent import BaseAgent
from ..agents.context_cache_config import ContextCacheConfig from ..agents.context_cache_config import ContextCacheConfig
from ..apps.base_events_compactor import BaseEventsCompactor from ..apps.base_events_summarizer import BaseEventsSummarizer
from ..plugins.base_plugin import BasePlugin from ..plugins.base_plugin import BasePlugin
from ..utils.feature_decorator import experimental from ..utils.feature_decorator import experimental
@@ -56,11 +56,13 @@ class EventsCompactionConfig(BaseModel):
extra="forbid", extra="forbid",
) )
compactor: BaseEventsCompactor summarizer: Optional[BaseEventsSummarizer] = None
"""The event compactor strategy for the application.""" """The event summarizer to use for compaction."""
compaction_interval: int compaction_interval: int
"""The number of *new* user-initiated invocations that, once """The number of *new* user-initiated invocations that, once
fully represented in the session's events, will trigger a compaction.""" fully represented in the session's events, will trigger a compaction."""
overlap_size: int overlap_size: int
"""The number of preceding invocations to include from the """The number of preceding invocations to include from the
end of the last compacted range. This creates an overlap between consecutive end of the last compacted range. This creates an overlap between consecutive
@@ -23,11 +23,11 @@ from ..utils.feature_decorator import experimental
@experimental @experimental
class BaseEventsCompactor(abc.ABC): class BaseEventsSummarizer(abc.ABC):
"""Base interface for compacting events.""" """Base interface for compacting events."""
@abc.abstractmethod @abc.abstractmethod
async def maybe_compact_events( async def maybe_summarize_events(
self, *, events: list[Event] self, *, events: list[Event]
) -> Optional[Event]: ) -> Optional[Event]:
"""Compact a list of events into a single event. """Compact a list of events into a single event.
+7 -1
View File
@@ -17,6 +17,7 @@ from __future__ import annotations
import logging import logging
from google.adk.apps.app import App from google.adk.apps.app import App
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
from google.adk.sessions.base_session_service import BaseSessionService from google.adk.sessions.base_session_service import BaseSessionService
from google.adk.sessions.session import Session from google.adk.sessions.session import Session
@@ -180,8 +181,13 @@ async def _run_compaction_for_sliding_window(
if not events_to_compact: if not events_to_compact:
return None return None
if not app.events_compaction_config.summarizer:
app.events_compaction_config.summarizer = LlmEventSummarizer(
llm=app.root_agent.canonical_model
)
compaction_event = ( compaction_event = (
await app.events_compaction_config.compactor.maybe_compact_events( await app.events_compaction_config.summarizer.maybe_summarize_events(
events=events_to_compact events=events_to_compact
) )
) )
+3 -3
View File
@@ -19,15 +19,15 @@ from google.genai import types
from google.genai.types import Content from google.genai.types import Content
from google.genai.types import Part from google.genai.types import Part
from ..apps.base_events_summarizer import BaseEventsSummarizer
from ..events.event import Event from ..events.event import Event
from ..events.event_actions import EventActions from ..events.event_actions import EventActions
from ..events.event_actions import EventCompaction from ..events.event_actions import EventCompaction
from ..models.base_llm import BaseLlm from ..models.base_llm import BaseLlm
from ..models.llm_request import LlmRequest from ..models.llm_request import LlmRequest
from .base_events_compactor import BaseEventsCompactor
class LlmEventSummarizer(BaseEventsCompactor): class LlmEventSummarizer(BaseEventsSummarizer):
"""An LLM-based event summarizer for sliding window compaction. """An LLM-based event summarizer for sliding window compaction.
This class is responsible for summarizing a provided list of events into a This class is responsible for summarizing a provided list of events into a
@@ -81,7 +81,7 @@ class LlmEventSummarizer(BaseEventsCompactor):
formatted_history.append(f'{event.author}: {part.text}') formatted_history.append(f'{event.author}: {part.text}')
return '\\n'.join(formatted_history) return '\\n'.join(formatted_history)
async def maybe_compact_events( async def maybe_summarize_events(
self, *, events: list[Event] self, *, events: list[Event]
) -> Optional[Event]: ) -> Optional[Event]:
"""Compacts given events and returns the compacted content. """Compacts given events and returns the compacted content.
+16 -12
View File
@@ -72,7 +72,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
await _run_compaction_for_sliding_window( await _run_compaction_for_sliding_window(
app, session, self.mock_session_service app, session, self.mock_session_service
) )
self.mock_compactor.maybe_compact_events.assert_not_called() self.mock_compactor.maybe_summarize_events.assert_not_called()
self.mock_session_service.append_event.assert_not_called() self.mock_session_service.append_event.assert_not_called()
async def test_run_compaction_for_sliding_window_not_enough_new_invocations( async def test_run_compaction_for_sliding_window_not_enough_new_invocations(
@@ -82,7 +82,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test', name='test',
root_agent=Mock(spec=BaseAgent), root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig( events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor, summarizer=self.mock_compactor,
compaction_interval=3, compaction_interval=3,
overlap_size=1, overlap_size=1,
), ),
@@ -100,7 +100,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
await _run_compaction_for_sliding_window( await _run_compaction_for_sliding_window(
app, session, self.mock_session_service app, session, self.mock_session_service
) )
self.mock_compactor.maybe_compact_events.assert_not_called() self.mock_compactor.maybe_summarize_events.assert_not_called()
self.mock_session_service.append_event.assert_not_called() self.mock_session_service.append_event.assert_not_called()
async def test_run_compaction_for_sliding_window_first_compaction(self): async def test_run_compaction_for_sliding_window_first_compaction(self):
@@ -108,7 +108,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test', name='test',
root_agent=Mock(spec=BaseAgent), root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig( events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor, summarizer=self.mock_compactor,
compaction_interval=2, compaction_interval=2,
overlap_size=1, overlap_size=1,
), ),
@@ -124,14 +124,16 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
mock_compacted_event = self._create_compacted_event( mock_compacted_event = self._create_compacted_event(
1.0, 4.0, 'Summary inv1-inv4' 1.0, 4.0, 'Summary inv1-inv4'
) )
self.mock_compactor.maybe_compact_events.return_value = mock_compacted_event self.mock_compactor.maybe_summarize_events.return_value = (
mock_compacted_event
)
await _run_compaction_for_sliding_window( await _run_compaction_for_sliding_window(
app, session, self.mock_session_service app, session, self.mock_session_service
) )
# Expected events to compact: inv1, inv2, inv3, inv4 # Expected events to compact: inv1, inv2, inv3, inv4
compacted_events_arg = self.mock_compactor.maybe_compact_events.call_args[ compacted_events_arg = self.mock_compactor.maybe_summarize_events.call_args[
1 1
]['events'] ]['events']
self.assertEqual( self.assertEqual(
@@ -147,7 +149,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test', name='test',
root_agent=Mock(spec=BaseAgent), root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig( events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor, summarizer=self.mock_compactor,
compaction_interval=2, compaction_interval=2,
overlap_size=1, overlap_size=1,
), ),
@@ -174,7 +176,9 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
mock_compacted_event = self._create_compacted_event( mock_compacted_event = self._create_compacted_event(
2.0, 5.0, 'Summary inv2-inv5' 2.0, 5.0, 'Summary inv2-inv5'
) )
self.mock_compactor.maybe_compact_events.return_value = mock_compacted_event self.mock_compactor.maybe_summarize_events.return_value = (
mock_compacted_event
)
await _run_compaction_for_sliding_window( await _run_compaction_for_sliding_window(
app, session, self.mock_session_service app, session, self.mock_session_service
@@ -183,7 +187,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
# New invocations are inv3, inv4, inv5 (3 new) > threshold (2). # New invocations are inv3, inv4, inv5 (3 new) > threshold (2).
# Overlap size is 1, so start from 1 inv before inv3, which is inv2. # Overlap size is 1, so start from 1 inv before inv3, which is inv2.
# Compact range: inv2 to inv5. # Compact range: inv2 to inv5.
compacted_events_arg = self.mock_compactor.maybe_compact_events.call_args[ compacted_events_arg = self.mock_compactor.maybe_summarize_events.call_args[
1 1
]['events'] ]['events']
self.assertEqual( self.assertEqual(
@@ -201,7 +205,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test', name='test',
root_agent=Mock(spec=BaseAgent), root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig( events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor, summarizer=self.mock_compactor,
compaction_interval=1, compaction_interval=1,
overlap_size=0, overlap_size=0,
), ),
@@ -209,11 +213,11 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
events = [self._create_event(1.0, 'inv1', 'e1')] events = [self._create_event(1.0, 'inv1', 'e1')]
session = Session(app_name='test', user_id='u1', id='s1', events=events) session = Session(app_name='test', user_id='u1', id='s1', events=events)
self.mock_compactor.maybe_compact_events.return_value = None self.mock_compactor.maybe_summarize_events.return_value = None
await _run_compaction_for_sliding_window( await _run_compaction_for_sliding_window(
app, session, self.mock_session_service app, session, self.mock_session_service
) )
self.mock_compactor.maybe_compact_events.assert_called_once() self.mock_compactor.maybe_summarize_events.assert_called_once()
self.mock_session_service.append_event.assert_not_called() self.mock_session_service.append_event.assert_not_called()
@@ -64,7 +64,7 @@ class TestLlmEventSummarizer(unittest.IsolatedAsyncioTestCase):
self.mock_llm.generate_content_async.return_value = async_gen() self.mock_llm.generate_content_async.return_value = async_gen()
compacted_event = await self.compactor.maybe_compact_events(events=events) compacted_event = await self.compactor.maybe_summarize_events(events=events)
self.assertIsNotNone(compacted_event) self.assertIsNotNone(compacted_event)
self.assertEqual( self.assertEqual(
@@ -101,11 +101,11 @@ class TestLlmEventSummarizer(unittest.IsolatedAsyncioTestCase):
self.mock_llm.generate_content_async.return_value = async_gen() self.mock_llm.generate_content_async.return_value = async_gen()
compacted_event = await self.compactor.maybe_compact_events(events=events) compacted_event = await self.compactor.maybe_summarize_events(events=events)
self.assertIsNone(compacted_event) self.assertIsNone(compacted_event)
async def test_maybe_compact_events_empty_input(self): async def test_maybe_compact_events_empty_input(self):
compacted_event = await self.compactor.maybe_compact_events(events=[]) compacted_event = await self.compactor.maybe_summarize_events(events=[])
self.assertIsNone(compacted_event) self.assertIsNone(compacted_event)
self.mock_llm.generate_content_async.assert_not_called() self.mock_llm.generate_content_async.assert_not_called()