mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
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:
committed by
Copybara-Service
parent
238472d083
commit
3f4bd67b49
@@ -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
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user