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.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 ..utils.feature_decorator import experimental
@@ -56,11 +56,13 @@ class EventsCompactionConfig(BaseModel):
extra="forbid",
)
compactor: BaseEventsCompactor
"""The event compactor strategy for the application."""
summarizer: Optional[BaseEventsSummarizer] = None
"""The event summarizer to use for compaction."""
compaction_interval: int
"""The number of *new* user-initiated invocations that, once
fully represented in the session's events, will trigger a compaction."""
overlap_size: int
"""The number of preceding invocations to include from the
end of the last compacted range. This creates an overlap between consecutive
@@ -23,11 +23,11 @@ from ..utils.feature_decorator import experimental
@experimental
class BaseEventsCompactor(abc.ABC):
class BaseEventsSummarizer(abc.ABC):
"""Base interface for compacting events."""
@abc.abstractmethod
async def maybe_compact_events(
async def maybe_summarize_events(
self, *, events: list[Event]
) -> Optional[Event]:
"""Compact a list of events into a single event.
+7 -1
View File
@@ -17,6 +17,7 @@ from __future__ import annotations
import logging
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.session import Session
@@ -180,8 +181,13 @@ async def _run_compaction_for_sliding_window(
if not events_to_compact:
return None
if not app.events_compaction_config.summarizer:
app.events_compaction_config.summarizer = LlmEventSummarizer(
llm=app.root_agent.canonical_model
)
compaction_event = (
await app.events_compaction_config.compactor.maybe_compact_events(
await app.events_compaction_config.summarizer.maybe_summarize_events(
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 Part
from ..apps.base_events_summarizer import BaseEventsSummarizer
from ..events.event import Event
from ..events.event_actions import EventActions
from ..events.event_actions import EventCompaction
from ..models.base_llm import BaseLlm
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.
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}')
return '\\n'.join(formatted_history)
async def maybe_compact_events(
async def maybe_summarize_events(
self, *, events: list[Event]
) -> Optional[Event]:
"""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(
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()
async def test_run_compaction_for_sliding_window_not_enough_new_invocations(
@@ -82,7 +82,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test',
root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor,
summarizer=self.mock_compactor,
compaction_interval=3,
overlap_size=1,
),
@@ -100,7 +100,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
await _run_compaction_for_sliding_window(
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()
async def test_run_compaction_for_sliding_window_first_compaction(self):
@@ -108,7 +108,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test',
root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor,
summarizer=self.mock_compactor,
compaction_interval=2,
overlap_size=1,
),
@@ -124,14 +124,16 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
mock_compacted_event = self._create_compacted_event(
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(
app, session, self.mock_session_service
)
# 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
]['events']
self.assertEqual(
@@ -147,7 +149,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test',
root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor,
summarizer=self.mock_compactor,
compaction_interval=2,
overlap_size=1,
),
@@ -174,7 +176,9 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
mock_compacted_event = self._create_compacted_event(
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(
app, session, self.mock_session_service
@@ -183,7 +187,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
# New invocations are inv3, inv4, inv5 (3 new) > threshold (2).
# Overlap size is 1, so start from 1 inv before inv3, which is inv2.
# 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
]['events']
self.assertEqual(
@@ -201,7 +205,7 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
name='test',
root_agent=Mock(spec=BaseAgent),
events_compaction_config=EventsCompactionConfig(
compactor=self.mock_compactor,
summarizer=self.mock_compactor,
compaction_interval=1,
overlap_size=0,
),
@@ -209,11 +213,11 @@ class TestCompaction(unittest.IsolatedAsyncioTestCase):
events = [self._create_event(1.0, 'inv1', 'e1')]
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(
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()
@@ -64,7 +64,7 @@ class TestLlmEventSummarizer(unittest.IsolatedAsyncioTestCase):
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.assertEqual(
@@ -101,11 +101,11 @@ class TestLlmEventSummarizer(unittest.IsolatedAsyncioTestCase):
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)
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.mock_llm.generate_content_async.assert_not_called()