feat: Add intra-invocation compaction and token compaction pre-request

Compact session events before LLM calls when token threshold is exceeded

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 873095899
This commit is contained in:
George Weale
2026-02-20 15:19:45 -08:00
committed by Copybara-Service
parent bbdf0ea257
commit 485fcb84e3
7 changed files with 983 additions and 133 deletions
@@ -24,6 +24,7 @@ from pydantic import ConfigDict
from pydantic import Field
from pydantic import PrivateAttr
from ..apps.app import EventsCompactionConfig
from ..apps.app import ResumabilityConfig
from ..artifacts.base_artifact_service import BaseArtifactService
from ..auth.credential_service.base_credential_service import BaseCredentialService
@@ -200,6 +201,12 @@ class InvocationContext(BaseModel):
resumability_config: Optional[ResumabilityConfig] = None
"""The resumability config that applies to all agents under this invocation."""
events_compaction_config: Optional[EventsCompactionConfig] = None
"""The compaction config for this invocation."""
token_compaction_checked: bool = False
"""Whether token-threshold compaction ran during this invocation."""
plugin_manager: PluginManager = Field(default_factory=PluginManager)
"""The manager for keeping track of plugins in this invocation."""
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Request processor that runs token-threshold event compaction."""
from __future__ import annotations
from typing import AsyncGenerator
from typing import TYPE_CHECKING
from ...apps.compaction import _has_token_threshold_config
from ...apps.compaction import _run_compaction_for_token_threshold_config
from ...events.event import Event
from ._base_llm_processor import BaseLlmRequestProcessor
if TYPE_CHECKING:
from ...agents.invocation_context import InvocationContext
from ...models.llm_request import LlmRequest
class CompactionRequestProcessor(BaseLlmRequestProcessor):
"""Compacts session events before contents are prepared for model calls."""
async def run_async(
self, invocation_context: InvocationContext, llm_request: LlmRequest
) -> AsyncGenerator[Event, None]:
del llm_request
config = invocation_context.events_compaction_config
if not _has_token_threshold_config(config):
return
yield # Required for AsyncGenerator.
token_compacted = await _run_compaction_for_token_threshold_config(
config=config,
session=invocation_context.session,
session_service=invocation_context.session_service,
agent=invocation_context.agent,
agent_name=invocation_context.agent.name,
current_branch=invocation_context.branch,
)
if token_compacted:
invocation_context.token_compaction_checked = True
return
yield # Required for AsyncGenerator.
request_processor = CompactionRequestProcessor()
@@ -22,6 +22,7 @@ from . import _code_execution
from . import _nl_planning
from . import _output_schema_processor
from . import basic
from . import compaction
from . import contents
from . import context_cache_processor
from . import identity
@@ -42,6 +43,9 @@ def _create_request_processors():
request_confirmation.request_processor,
instructions.request_processor,
identity.request_processor,
# Compaction should run before contents so compacted events are reflected
# in the model request context.
compaction.request_processor,
contents.request_processor,
# Context cache processor sets up cache config and finds
# existing cache metadata.
+7 -1
View File
@@ -553,7 +553,10 @@ class Runner:
if self.app and self.app.events_compaction_config:
logger.debug('Running event compactor.')
await _run_compaction_for_sliding_window(
self.app, session, self.session_service
self.app,
session,
self.session_service,
skip_token_compaction=invocation_context.token_compaction_checked,
)
async with Aclosing(_run_with_trace(new_message, invocation_id)) as agen:
@@ -1362,6 +1365,9 @@ class Runner:
credential_service=self.credential_service,
plugin_manager=self.plugin_manager,
context_cache_config=self.context_cache_config,
events_compaction_config=(
self.app.events_compaction_config if self.app else None
),
invocation_id=invocation_id,
agent=self.agent,
session=session,