Merge pull request #3863 from pipecat-ai/filipi/manual_summarization

Manual context summarization
This commit is contained in:
Filipi da Silva Fuchter
2026-02-27 16:46:37 -05:00
committed by GitHub
14 changed files with 728 additions and 119 deletions

View File

@@ -0,0 +1 @@
- Added `LLMContextSummaryConfig` (summary generation params: `target_context_tokens`, `min_messages_after_summary`, `summarization_prompt`) and `LLMAutoContextSummarizationConfig` (auto-trigger thresholds: `max_context_tokens`, `max_unsummarized_messages`, plus a nested `summary_config`). These replace the monolithic `LLMContextSummarizationConfig`.

1
changelog/3863.added.md Normal file
View File

@@ -0,0 +1 @@
- Added `LLMSummarizeContextFrame` to trigger on-demand context summarization from anywhere in the pipeline (e.g. a function call tool). Accepts an optional `config: LLMContextSummaryConfig` to override summary generation settings per request.

View File

@@ -0,0 +1 @@
- ⚠️ Renamed `LLMAssistantAggregatorParams` fields: `enable_context_summarization``enable_auto_context_summarization` and `context_summarization_config``auto_context_summarization_config` (now accepts `LLMAutoContextSummarizationConfig`). The old names still work with a `DeprecationWarning` for one release cycle.

View File

@@ -0,0 +1 @@
- Deprecated `LLMContextSummarizationConfig`. Use `LLMAutoContextSummarizationConfig` with a nested `LLMContextSummaryConfig` instead. The old class emits a `DeprecationWarning`.

View File

@@ -41,7 +41,10 @@ from pipecat.services.openai.llm import OpenAILLMService
from pipecat.transports.base_transport import BaseTransport, TransportParams
from pipecat.transports.daily.transport import DailyParams
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
from pipecat.utils.context.llm_context_summarization import LLMContextSummarizationConfig
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummaryConfig,
)
load_dotenv(override=True)
@@ -120,14 +123,16 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
vad_analyzer=SileroVADAnalyzer(),
),
assistant_params=LLMAssistantAggregatorParams(
enable_context_summarization=True,
enable_auto_context_summarization=True,
# Optional: customize context summarization behavior
# Using low limits to demonstrate the feature quickly
context_summarization_config=LLMContextSummarizationConfig(
auto_context_summarization_config=LLMAutoContextSummarizationConfig(
max_context_tokens=1000, # Trigger summarization at 1000 tokens
target_context_tokens=800, # Target context size for the summarization
max_unsummarized_messages=10, # Or when 10 new messages accumulate
min_messages_after_summary=2, # Keep last 2 messages uncompressed
summary_config=LLMContextSummaryConfig(
target_context_tokens=800, # Target context size for the summarization
min_messages_after_summary=2, # Keep last 2 messages uncompressed
),
),
),
)

View File

@@ -41,7 +41,10 @@ from pipecat.services.llm_service import FunctionCallParams
from pipecat.transports.base_transport import BaseTransport, TransportParams
from pipecat.transports.daily.transport import DailyParams
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
from pipecat.utils.context.llm_context_summarization import LLMContextSummarizationConfig
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummaryConfig,
)
load_dotenv(override=True)
@@ -120,14 +123,16 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
vad_analyzer=SileroVADAnalyzer(),
),
assistant_params=LLMAssistantAggregatorParams(
enable_context_summarization=True,
enable_auto_context_summarization=True,
# Optional: customize context summarization behavior
# Using low limits to demonstrate the feature quickly
context_summarization_config=LLMContextSummarizationConfig(
auto_context_summarization_config=LLMAutoContextSummarizationConfig(
max_context_tokens=1000, # Trigger summarization at 1000 tokens
target_context_tokens=800, # Target context size for the summarization
max_unsummarized_messages=10, # Or when 10 new messages accumulate
min_messages_after_summary=2, # Keep last 2 messages uncompressed
summary_config=LLMContextSummaryConfig(
target_context_tokens=800, # Target context size for the summarization
min_messages_after_summary=2, # Keep last 2 messages uncompressed
),
),
),
)

View File

@@ -0,0 +1,179 @@
#
# Copyright (c) 2024-2026, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
"""Example demonstrating manual context summarization via a function call.
This example shows how to trigger context summarization on demand rather than
automatically. The user can ask the bot to "summarize the conversation" and the
bot will call a function that pushes an LLMSummarizeContextFrame into the
pipeline, causing the LLM service to compress the conversation history.
Unlike example 54, automatic summarization is NOT enabled here. Summarization
only happens when the user explicitly requests it through the function call.
"""
import os
from dotenv import load_dotenv
from loguru import logger
from pipecat.adapters.schemas.function_schema import FunctionSchema
from pipecat.adapters.schemas.tools_schema import ToolsSchema
from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.audio.vad.vad_analyzer import VADParams
from pipecat.frames.frames import LLMRunFrame, LLMSummarizeContextFrame
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.llm_response_universal import (
LLMContextAggregatorPair,
LLMUserAggregatorParams,
)
from pipecat.runner.types import RunnerArguments
from pipecat.runner.utils import create_transport
from pipecat.services.cartesia.tts import CartesiaTTSService
from pipecat.services.deepgram.stt import DeepgramSTTService
from pipecat.services.llm_service import FunctionCallParams
from pipecat.services.openai.llm import OpenAILLMService
from pipecat.transports.base_transport import BaseTransport, TransportParams
from pipecat.transports.daily.transport import DailyParams
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
from pipecat.turns.user_stop import TurnAnalyzerUserTurnStopStrategy
from pipecat.turns.user_turn_strategies import UserTurnStrategies
load_dotenv(override=True)
# We use lambdas to defer transport parameter creation until the transport
# type is selected at runtime.
transport_params = {
"daily": lambda: DailyParams(
audio_in_enabled=True,
audio_out_enabled=True,
),
"twilio": lambda: FastAPIWebsocketParams(
audio_in_enabled=True,
audio_out_enabled=True,
),
"webrtc": lambda: TransportParams(
audio_in_enabled=True,
audio_out_enabled=True,
),
}
async def summarize_conversation(params: FunctionCallParams):
"""Trigger manual context summarization via a pipeline frame."""
logger.info("Tool called: summarize_conversation")
await params.result_callback({"status": "summarization_requested"})
await params.llm.queue_frame(LLMSummarizeContextFrame())
async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
logger.info("Starting bot")
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"),
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
)
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
llm.register_function("summarize_conversation", summarize_conversation)
summarize_function = FunctionSchema(
name="summarize_conversation",
description=(
"Summarize and compress the conversation history. "
"Call this when the user asks you to summarize the conversation "
"or when you want to free up context space."
),
properties={},
required=[],
)
tools = ToolsSchema(standard_tools=[summarize_function])
messages = [
{
"role": "system",
"content": (
"You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your "
"capabilities in a succinct way. Your output will be spoken aloud, so avoid "
"special characters that can't easily be spoken, such as emojis or bullet points. "
"Respond to what the user said in a creative and helpful way. "
"If the user asks you to summarize the conversation, call the "
"summarize_conversation function. After summarization, briefly acknowledge "
"that the conversation history has been compressed."
),
},
]
context = LLMContext(messages, tools=tools)
# Automatic summarization is NOT enabled here (enable_auto_context_summarization
# defaults to False). The summarizer is still created internally so that
# LLMSummarizeContextFrame frames pushed via the function call are handled.
user_aggregator, assistant_aggregator = LLMContextAggregatorPair(
context,
user_params=LLMUserAggregatorParams(
user_turn_strategies=UserTurnStrategies(
stop=[TurnAnalyzerUserTurnStopStrategy(turn_analyzer=LocalSmartTurnAnalyzerV3())]
),
vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)),
),
)
pipeline = Pipeline(
[
transport.input(), # Transport user input
stt,
user_aggregator, # User responses
llm, # LLM
tts, # TTS
transport.output(), # Transport bot output
assistant_aggregator, # Assistant spoken responses
]
)
task = PipelineTask(
pipeline,
params=PipelineParams(
enable_metrics=True,
enable_usage_metrics=True,
),
idle_timeout_secs=runner_args.pipeline_idle_timeout_secs,
)
@transport.event_handler("on_client_connected")
async def on_client_connected(transport, client):
logger.info("Client connected")
# Kick off the conversation.
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")
async def on_client_disconnected(transport, client):
logger.info("Client disconnected")
await task.cancel()
runner = PipelineRunner(handle_sigint=runner_args.handle_sigint)
await runner.run(task)
async def bot(runner_args: RunnerArguments):
"""Main bot entry point compatible with Pipecat Cloud."""
transport = await create_transport(runner_args, transport_params)
await run_bot(transport, runner_args)
if __name__ == "__main__":
from pipecat.runner.run import main
main()

View File

@@ -44,7 +44,10 @@ from pipecat.services.openai.llm import OpenAILLMService
from pipecat.transports.base_transport import BaseTransport, TransportParams
from pipecat.transports.daily.transport import DailyParams
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
from pipecat.utils.context.llm_context_summarization import LLMContextSummarizationConfig
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummaryConfig,
)
load_dotenv(override=True)
@@ -147,23 +150,25 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
vad_analyzer=SileroVADAnalyzer(),
),
assistant_params=LLMAssistantAggregatorParams(
enable_context_summarization=True,
context_summarization_config=LLMContextSummarizationConfig(
enable_auto_context_summarization=True,
auto_context_summarization_config=LLMAutoContextSummarizationConfig(
# Trigger thresholds (low values to demonstrate quickly)
max_context_tokens=1000,
max_unsummarized_messages=10,
# Summary generation
target_context_tokens=800,
min_messages_after_summary=2,
summarization_prompt=CUSTOM_SUMMARIZATION_PROMPT,
# Custom summary format - wrap in XML tags so the system
# prompt can identify summaries vs. live conversation
summary_message_template="<context_summary>\n{summary}\n</context_summary>",
# Use a dedicated cheap LLM for summarization instead of
# the primary conversation model
llm=summarization_llm,
# Cancel summarization if it takes longer than 60 seconds
summarization_timeout=60.0,
summary_config=LLMContextSummaryConfig(
# Summary generation
target_context_tokens=800,
min_messages_after_summary=2,
summarization_prompt=CUSTOM_SUMMARIZATION_PROMPT,
# Custom summary format - wrap in XML tags so the system
# prompt can identify summaries vs. live conversation
summary_message_template="<context_summary>\n{summary}\n</context_summary>",
# Use a dedicated cheap LLM for summarization instead of
# the primary conversation model
llm=summarization_llm,
# Cancel summarization if it takes longer than 60 seconds
summarization_timeout=60.0,
),
),
),
)

View File

@@ -43,6 +43,7 @@ if TYPE_CHECKING:
from pipecat.processors.aggregators.llm_context import LLMContext, NotGiven
from pipecat.processors.frame_processor import FrameProcessor
from pipecat.services.settings import ServiceSettings
from pipecat.utils.context.llm_context_summarization import LLMContextSummaryConfig
from pipecat.utils.tracing.tracing_context import TracingContext
@@ -2000,6 +2001,22 @@ class LLMAssistantPushAggregationFrame(ControlFrame):
"""
@dataclass
class LLMSummarizeContextFrame(ControlFrame):
"""Frame requesting on-demand context summarization.
Push this frame into the pipeline to trigger a manual context summarization.
Parameters:
config: Optional per-request override for summary generation settings
(prompt, token budget, messages to keep). If ``None``, the
summarizer's default :class:`~pipecat.utils.context.llm_context_summarization.LLMContextSummaryConfig`
is used.
"""
config: Optional["LLMContextSummaryConfig"] = None
@dataclass
class LLMContextSummaryRequestFrame(ControlFrame):
"""Frame requesting context summarization from an LLM service.

View File

@@ -19,14 +19,16 @@ from pipecat.frames.frames import (
LLMContextSummaryRequestFrame,
LLMContextSummaryResultFrame,
LLMFullResponseStartFrame,
LLMSummarizeContextFrame,
)
from pipecat.processors.aggregators.llm_context import LLMContext, LLMSpecificMessage
from pipecat.utils.asyncio.task_manager import BaseTaskManager
from pipecat.utils.base_object import BaseObject
from pipecat.utils.context.llm_context_summarization import (
DEFAULT_SUMMARIZATION_TIMEOUT,
LLMContextSummarizationConfig,
LLMAutoContextSummarizationConfig,
LLMContextSummarizationUtil,
LLMContextSummaryConfig,
)
if TYPE_CHECKING:
@@ -55,9 +57,20 @@ class SummaryAppliedEvent:
class LLMContextSummarizer(BaseObject):
"""Summarizer for managing LLM context summarization.
This class manages automatic context summarization when token or message
limits are reached. It monitors the LLM context size, triggers
summarization requests, and applies the results to compress conversation history.
This class manages context summarization, either automatically when token or
message limits are reached, or on-demand when an ``LLMSummarizeContextFrame``
is received. It monitors the LLM context size, triggers summarization requests,
and applies the results to compress conversation history.
When ``auto_trigger=True`` (the default), summarization is triggered
automatically based on the configured thresholds in
``LLMAutoContextSummarizationConfig``. When ``auto_trigger=False``,
threshold checks are skipped and summarization only happens when an
``LLMSummarizeContextFrame`` is explicitly pushed into the pipeline.
Both modes can coexist: set ``auto_trigger=True`` and also push
``LLMSummarizeContextFrame`` at any time to force an immediate summarization
(subject to the ``_summarization_in_progress`` guard).
Event handlers available:
@@ -88,18 +101,26 @@ class LLMContextSummarizer(BaseObject):
self,
*,
context: LLMContext,
config: Optional[LLMContextSummarizationConfig] = None,
config: Optional[LLMAutoContextSummarizationConfig] = None,
auto_trigger: bool = True,
):
"""Initialize the context summarizer.
Args:
context: The LLM context to monitor and summarize.
config: Configuration for summarization behavior. If None, uses default config.
config: Auto-summarization configuration controlling both trigger
thresholds and default summary generation parameters. If None,
uses default ``LLMAutoContextSummarizationConfig`` values.
auto_trigger: Whether to automatically trigger summarization when
thresholds are reached. When False, summarization only happens
when an ``LLMSummarizeContextFrame`` is pushed into the pipeline.
Defaults to True.
"""
super().__init__()
self._context = context
self._config = config or LLMContextSummarizationConfig()
self._auto_config = config or LLMAutoContextSummarizationConfig()
self._auto_trigger = auto_trigger
self._task_manager: Optional[BaseTaskManager] = None
@@ -137,6 +158,8 @@ class LLMContextSummarizer(BaseObject):
"""
if isinstance(frame, LLMFullResponseStartFrame):
await self._handle_llm_response_start(frame)
elif isinstance(frame, LLMSummarizeContextFrame):
await self._handle_manual_summarization_request(frame)
elif isinstance(frame, LLMContextSummaryResultFrame):
await self._handle_summary_result(frame)
elif isinstance(frame, InterruptionFrame):
@@ -151,12 +174,24 @@ class LLMContextSummarizer(BaseObject):
if self._should_summarize():
await self._request_summarization()
async def _handle_interruption(self):
"""Handle interruption by canceling summarization in progress.
async def _handle_manual_summarization_request(self, frame: LLMSummarizeContextFrame):
"""Handle an explicit on-demand summarization request.
Reuses the same ``_request_summarization()`` code path as auto mode,
so bookkeeping (``_summarization_in_progress``,
``_pending_summary_request_id``) is always updated correctly.
Args:
frame: The interruption frame.
frame: The manual summarization request frame, optionally carrying
a per-request :class:`~pipecat.utils.context.llm_context_summarization.LLMContextSummaryConfig`.
"""
if self._summarization_in_progress:
logger.debug(f"{self}: Summarization already in progress, ignoring manual request")
return
await self._request_summarization(config_override=frame.config)
async def _handle_interruption(self):
"""Handle interruption by canceling summarization in progress."""
# Reset summarization state to allow new requests. This is necessary because
# the request frame (LLMContextSummaryRequestFrame) may have been cancelled
# during interruption. We preserve _pending_summary_request_id to handle the
@@ -179,13 +214,17 @@ class LLMContextSummarizer(BaseObject):
Returns:
True if all conditions are met:
- ``auto_trigger`` is enabled
- No summarization currently in progress
- AND either:
- Token count exceeds max_context_tokens
- OR message count exceeds max_unsummarized_messages since last summary
- Token count exceeds ``max_context_tokens``
- OR message count exceeds ``max_unsummarized_messages`` since last summary
"""
logger.trace(f"{self}: Checking if context summarization is needed")
if not self._auto_trigger:
return False
if self._summarization_in_progress:
logger.debug(f"{self}: Summarization already in progress")
return False
@@ -195,20 +234,20 @@ class LLMContextSummarizer(BaseObject):
num_messages = len(self._context.messages)
# Check if we've reached the token limit
token_limit = self._config.max_context_tokens
token_limit = self._auto_config.max_context_tokens
token_limit_exceeded = total_tokens >= token_limit
# Check if we've exceeded max unsummarized messages
messages_since_summary = len(self._context.messages) - 1
message_threshold_exceeded = (
messages_since_summary >= self._config.max_unsummarized_messages
messages_since_summary >= self._auto_config.max_unsummarized_messages
)
logger.trace(
f"{self}: Context has {num_messages} messages, "
f"~{total_tokens} tokens (limit: {token_limit}), "
f"{messages_since_summary} messages since last summary "
f"(message threshold: {self._config.max_unsummarized_messages})"
f"(message threshold: {self._auto_config.max_unsummarized_messages})"
)
# Trigger if either limit is exceeded
@@ -223,23 +262,30 @@ class LLMContextSummarizer(BaseObject):
reason.append(f"~{total_tokens} tokens (>={token_limit} limit)")
if message_threshold_exceeded:
reason.append(
f"{messages_since_summary} messages (>={self._config.max_unsummarized_messages} threshold)"
f"{messages_since_summary} messages (>={self._auto_config.max_unsummarized_messages} threshold)"
)
logger.debug(f"{self}: ✓ Summarization needed - {', '.join(reason)}")
return True
async def _request_summarization(self):
async def _request_summarization(
self, config_override: Optional[LLMContextSummaryConfig] = None
):
"""Request context summarization from LLM service.
Creates a summarization request frame and either handles it directly
using a dedicated LLM (if configured) or emits it via event handler
for the pipeline's primary LLM. Tracks the request ID to match async
responses and prevent race conditions.
for the pipeline's primary LLM.
Tracks the request ID to match async responses and prevent race conditions.
Args:
config_override: Optional per-request summary configuration. If provided,
overrides the default summary generation settings from
``self._auto_config.summary_config``.
"""
# Generate unique request ID
request_id = str(uuid.uuid4())
min_keep = self._config.min_messages_after_summary
summary_config = config_override or self._auto_config.summary_config
# Mark summarization in progress
self._summarization_in_progress = True
@@ -251,16 +297,16 @@ class LLMContextSummarizer(BaseObject):
request_frame = LLMContextSummaryRequestFrame(
request_id=request_id,
context=self._context,
min_messages_to_keep=min_keep,
target_context_tokens=self._config.target_context_tokens,
summarization_prompt=self._config.summary_prompt,
summarization_timeout=self._config.summarization_timeout,
min_messages_to_keep=summary_config.min_messages_after_summary,
target_context_tokens=summary_config.target_context_tokens,
summarization_prompt=summary_config.summary_prompt,
summarization_timeout=summary_config.summarization_timeout,
)
if self._config.llm:
if summary_config.llm:
# Use dedicated LLM directly — no need to involve the pipeline
self.task_manager.create_task(
self._generate_summary_with_dedicated_llm(self._config.llm, request_frame),
self._generate_summary_with_dedicated_llm(summary_config.llm, request_frame),
f"{self}-dedicated-llm-summary",
)
else:
@@ -323,7 +369,9 @@ class LLMContextSummarizer(BaseObject):
"""
logger.debug(f"{self}: Received summary result (request_id={frame.request_id})")
# Check if this is the result we're waiting for
# Check if this is the result we're waiting for. Both auto and manual
# summarization set _pending_summary_request_id via _request_summarization(),
# so this check always applies.
if frame.request_id != self._pending_summary_request_id:
logger.debug(f"{self}: Ignoring stale summary result (request_id={frame.request_id})")
return
@@ -360,7 +408,7 @@ class LLMContextSummarizer(BaseObject):
if last_summarized_index >= len(self._context.messages):
return False
min_keep = self._config.min_messages_after_summary
min_keep = self._auto_config.summary_config.min_messages_after_summary
remaining = len(self._context.messages) - 1 - last_summarized_index
if remaining < min_keep:
return False
@@ -377,6 +425,7 @@ class LLMContextSummarizer(BaseObject):
summary: The generated summary text.
last_summarized_index: Index of the last message that was summarized.
"""
config = self._auto_config.summary_config
messages = self._context.messages
# Find the first system message to preserve. LLMSpecificMessage instances are excluded
@@ -397,7 +446,7 @@ class LLMContextSummarizer(BaseObject):
# Create summary message as a user message (the summary is context
# provided *to* the assistant, not something the assistant said)
summary_content = self._config.summary_message_template.format(summary=summary)
summary_content = config.summary_message_template.format(summary=summary)
summary_message = {"role": "user", "content": summary_content}
# Reconstruct context

View File

@@ -79,7 +79,10 @@ from pipecat.turns.user_stop import BaseUserTurnStopStrategy, UserTurnStoppedPar
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionConfig
from pipecat.turns.user_turn_controller import UserTurnController
from pipecat.turns.user_turn_strategies import ExternalUserTurnStrategies, UserTurnStrategies
from pipecat.utils.context.llm_context_summarization import LLMContextSummarizationConfig
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummarizationConfig,
)
from pipecat.utils.string import TextPartForConcatenation, concatenate_aggregated_text
from pipecat.utils.time import time_now_iso8601
@@ -125,18 +128,54 @@ class LLMAssistantAggregatorParams:
in text frames by adding spaces between tokens. This parameter is
ignored when used with the newer LLMAssistantAggregator, which
handles word spacing automatically.
enable_context_summarization: Enable automatic context summarization when token
limits are reached (disabled by default). When enabled, older conversation
messages are automatically compressed into summaries to manage context size.
context_summarization_config: Configuration for context summarization behavior.
Controls thresholds, message preservation, and summarization prompts. If None
and summarization is enabled, uses default configuration values.
enable_auto_context_summarization: Enable automatic context summarization when token
or message-count limits are reached (disabled by default). When enabled,
older conversation messages are automatically compressed into summaries to
manage context size.
auto_context_summarization_config: Configuration for automatic context
summarization. Controls trigger thresholds, message preservation, and
summarization prompts. If None, uses default
``LLMAutoContextSummarizationConfig`` values.
"""
expect_stripped_words: bool = True
enable_context_summarization: bool = False
enable_auto_context_summarization: bool = False
auto_context_summarization_config: Optional[LLMAutoContextSummarizationConfig] = None
# ---------------------------------------------------------------------------
# Deprecated field names — kept for backward compatibility.
# Use enable_auto_context_summarization and auto_context_summarization_config instead.
# ---------------------------------------------------------------------------
enable_context_summarization: Optional[bool] = None
context_summarization_config: Optional[LLMContextSummarizationConfig] = None
def __post_init__(self):
if self.enable_context_summarization is not None:
warnings.warn(
"LLMAssistantAggregatorParams.enable_context_summarization is deprecated. "
"Use enable_auto_context_summarization instead.",
DeprecationWarning,
stacklevel=2,
)
self.enable_auto_context_summarization = self.enable_context_summarization
self.enable_context_summarization = None
if self.context_summarization_config is not None:
warnings.warn(
"LLMAssistantAggregatorParams.context_summarization_config is deprecated. "
"Use auto_context_summarization_config (LLMAutoContextSummarizationConfig) instead.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(self.context_summarization_config, LLMContextSummarizationConfig):
self.auto_context_summarization_config = (
self.context_summarization_config.to_auto_config()
)
else:
# Accept LLMAutoContextSummarizationConfig passed to the deprecated field
self.auto_context_summarization_config = self.context_summarization_config # type: ignore[assignment]
self.context_summarization_config = None
@dataclass
class UserTurnStoppedMessage:
@@ -825,16 +864,18 @@ class LLMAssistantAggregator(LLMContextAggregator):
self._thought_aggregation: List[TextPartForConcatenation] = []
self._thought_start_time: str = ""
# Context summarization
self._summarizer: Optional[LLMContextSummarizer] = None
if self._params.enable_context_summarization:
self._summarizer = LLMContextSummarizer(
context=self._context,
config=self._params.context_summarization_config,
)
self._summarizer.add_event_handler(
"on_request_summarization", self._on_request_summarization
)
# Context summarization — always create the summarizer so that manually
# pushed LLMSummarizeContextFrame frames are always handled.
# Auto-triggering based on thresholds is only enabled when
# enable_auto_context_summarization is True.
self._summarizer: Optional[LLMContextSummarizer] = LLMContextSummarizer(
context=self._context,
config=self._params.auto_context_summarization_config,
auto_trigger=self._params.enable_auto_context_summarization,
)
self._summarizer.add_event_handler(
"on_request_summarization", self._on_request_summarization
)
self._register_event_handler("on_assistant_turn_started")
self._register_event_handler("on_assistant_turn_stopped")

View File

@@ -10,7 +10,8 @@ This module provides reusable functionality for automatically compressing conver
context when token limits are reached, enabling efficient long-running conversations.
"""
from dataclasses import dataclass
import warnings
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List, Optional
if TYPE_CHECKING:
@@ -54,26 +55,18 @@ The conversation transcript follows. Generate only the summary, no other text.""
@dataclass
class LLMContextSummarizationConfig:
"""Configuration for context summarization behavior.
class LLMContextSummaryConfig:
"""Configuration for summary generation parameters.
Controls when and how conversation context is automatically compressed
to manage token limits in long-running conversations.
Contains settings that control how a summary is generated. Used by both
automatic and manual summarization modes.
Parameters:
max_context_tokens: Maximum allowed context size in tokens. When this
limit is reached, summarization is triggered to compress the context.
The tokens are calculated using the industry-standard approximation
of 1 token ≈ 4 characters.
target_context_tokens: Maximum token size for the generated summary.
This value is passed directly to the LLM as the max_tokens parameter
when generating the summary. Should be sized appropriately to allow
the summary plus recent preserved messages to fit within reasonable
context limits.
max_unsummarized_messages: Maximum number of new messages that can
accumulate since the last summary before triggering a new
summarization. This ensures regular compression even if token
limits are not reached.
min_messages_after_summary: Number of recent messages to preserve
uncompressed after each summarization. These messages maintain
immediate conversational context.
@@ -94,6 +87,94 @@ class LLMContextSummarizationConfig:
is aborted with an error and future summarizations are unblocked.
"""
target_context_tokens: int = 6000
min_messages_after_summary: int = 4
summarization_prompt: Optional[str] = None
summary_message_template: str = "Conversation summary: {summary}"
llm: Optional["LLMService"] = None
summarization_timeout: float = DEFAULT_SUMMARIZATION_TIMEOUT
def __post_init__(self):
"""Validate configuration parameters."""
if self.target_context_tokens <= 0:
raise ValueError("target_context_tokens must be positive")
if self.min_messages_after_summary < 0:
raise ValueError("min_messages_after_summary must be non-negative")
@property
def summary_prompt(self) -> str:
"""Get the summarization prompt to use.
Returns:
The custom prompt if set, otherwise the default summarization prompt.
"""
return self.summarization_prompt or DEFAULT_SUMMARIZATION_PROMPT
@dataclass
class LLMAutoContextSummarizationConfig:
"""Configuration for automatic context summarization.
Controls when conversation context is automatically compressed and how
that summary is generated. Summarization is triggered when either the
token limit or the unsummarized message count threshold is exceeded.
Parameters:
max_context_tokens: Maximum allowed context size in tokens. When this
limit is reached, summarization is triggered to compress the context.
The tokens are calculated using the industry-standard approximation
of 1 token ≈ 4 characters.
max_unsummarized_messages: Maximum number of new messages that can
accumulate since the last summary before triggering a new
summarization. This ensures regular compression even if token
limits are not reached.
summary_config: Configuration for summary generation parameters
(prompt, token budget, messages to keep). If not provided, uses
default ``LLMContextSummaryConfig`` values.
"""
max_context_tokens: int = 8000
max_unsummarized_messages: int = 20
summary_config: LLMContextSummaryConfig = field(default_factory=LLMContextSummaryConfig)
def __post_init__(self):
"""Validate configuration parameters."""
if self.max_context_tokens <= 0:
raise ValueError("max_context_tokens must be positive")
if self.max_unsummarized_messages < 1:
raise ValueError("max_unsummarized_messages must be at least 1")
# Auto-adjust target_context_tokens if it exceeds max_context_tokens
if self.summary_config.target_context_tokens > self.max_context_tokens:
# Use 80% of max_context_tokens as a reasonable default
self.summary_config.target_context_tokens = int(self.max_context_tokens * 0.8)
@dataclass
class LLMContextSummarizationConfig:
"""Configuration for context summarization behavior.
.. deprecated::
Use :class:`LLMAutoContextSummarizationConfig` with a nested
:class:`LLMContextSummaryConfig` instead::
LLMAutoContextSummarizationConfig(
max_context_tokens=8000,
max_unsummarized_messages=20,
summary_config=LLMContextSummaryConfig(
target_context_tokens=6000,
min_messages_after_summary=4,
),
)
Parameters:
max_context_tokens: Maximum allowed context size in tokens.
target_context_tokens: Maximum token size for the generated summary.
max_unsummarized_messages: Maximum new messages before triggering summarization.
min_messages_after_summary: Number of recent messages to preserve.
summarization_prompt: Custom prompt for summary generation.
"""
max_context_tokens: int = 8000
target_context_tokens: int = 6000
max_unsummarized_messages: int = 20
@@ -105,6 +186,12 @@ class LLMContextSummarizationConfig:
def __post_init__(self):
"""Validate configuration parameters."""
warnings.warn(
"LLMContextSummarizationConfig is deprecated. "
"Use LLMAutoContextSummarizationConfig with a nested LLMContextSummaryConfig instead.",
DeprecationWarning,
stacklevel=2,
)
if self.max_context_tokens <= 0:
raise ValueError("max_context_tokens must be positive")
if self.target_context_tokens <= 0:
@@ -129,6 +216,25 @@ class LLMContextSummarizationConfig:
"""
return self.summarization_prompt or DEFAULT_SUMMARIZATION_PROMPT
def to_auto_config(self) -> LLMAutoContextSummarizationConfig:
"""Convert to the new :class:`LLMAutoContextSummarizationConfig`.
Returns:
An equivalent ``LLMAutoContextSummarizationConfig`` instance.
"""
return LLMAutoContextSummarizationConfig(
max_context_tokens=self.max_context_tokens,
max_unsummarized_messages=self.max_unsummarized_messages,
summary_config=LLMContextSummaryConfig(
target_context_tokens=self.target_context_tokens,
min_messages_after_summary=self.min_messages_after_summary,
summarization_prompt=self.summarization_prompt,
summary_message_template=self.summary_message_template,
llm=self.llm,
summarization_timeout=self.summarization_timeout,
),
)
@dataclass
class LLMMessagesToSummarize:

View File

@@ -14,8 +14,10 @@ from pipecat.frames.frames import LLMContextSummaryRequestFrame, LLMContextSumma
from pipecat.processors.aggregators.llm_context import LLMContext, LLMSpecificMessage
from pipecat.services.llm_service import LLMService
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummarizationConfig,
LLMContextSummarizationUtil,
LLMContextSummaryConfig,
)
@@ -167,43 +169,109 @@ class TestContextSummarizationMixin(unittest.TestCase):
self.assertIn("USER: First part Second part", transcript)
class TestLLMContextSummarizationConfig(unittest.TestCase):
"""Tests for LLMContextSummarizationConfig."""
class TestLLMContextSummaryConfig(unittest.TestCase):
"""Tests for LLMContextSummaryConfig."""
def test_default_config(self):
"""Test default configuration values."""
config = LLMContextSummarizationConfig()
config = LLMContextSummaryConfig()
self.assertEqual(config.max_context_tokens, 8000)
self.assertEqual(config.max_unsummarized_messages, 20)
self.assertEqual(config.target_context_tokens, 6000)
self.assertEqual(config.min_messages_after_summary, 4)
self.assertIsNone(config.summarization_prompt)
def test_custom_config(self):
"""Test custom configuration."""
config = LLMContextSummarizationConfig(
max_context_tokens=2500,
config = LLMContextSummaryConfig(
target_context_tokens=2000,
max_unsummarized_messages=15,
min_messages_after_summary=4,
summarization_prompt="Custom prompt",
)
self.assertEqual(config.max_context_tokens, 2500)
self.assertEqual(config.target_context_tokens, 2000)
self.assertEqual(config.max_unsummarized_messages, 15)
self.assertEqual(config.min_messages_after_summary, 4)
self.assertEqual(config.summary_prompt, "Custom prompt")
def test_summary_prompt_property(self):
"""Test summary_prompt property uses default when None."""
config = LLMContextSummarizationConfig()
config = LLMContextSummaryConfig()
self.assertIn("summarizing a conversation", config.summary_prompt.lower())
config_with_custom = LLMContextSummarizationConfig(summarization_prompt="Custom")
config_with_custom = LLMContextSummaryConfig(summarization_prompt="Custom")
self.assertEqual(config_with_custom.summary_prompt, "Custom")
class TestLLMAutoContextSummarizationConfig(unittest.TestCase):
"""Tests for LLMAutoContextSummarizationConfig."""
def test_default_config(self):
"""Test default configuration values."""
config = LLMAutoContextSummarizationConfig()
self.assertEqual(config.max_context_tokens, 8000)
self.assertEqual(config.max_unsummarized_messages, 20)
self.assertEqual(config.summary_config.target_context_tokens, 6000)
self.assertEqual(config.summary_config.min_messages_after_summary, 4)
def test_custom_config(self):
"""Test custom configuration."""
config = LLMAutoContextSummarizationConfig(
max_context_tokens=2500,
max_unsummarized_messages=15,
summary_config=LLMContextSummaryConfig(
target_context_tokens=2000,
min_messages_after_summary=4,
summarization_prompt="Custom prompt",
),
)
self.assertEqual(config.max_context_tokens, 2500)
self.assertEqual(config.max_unsummarized_messages, 15)
self.assertEqual(config.summary_config.target_context_tokens, 2000)
self.assertEqual(config.summary_config.min_messages_after_summary, 4)
self.assertEqual(config.summary_config.summary_prompt, "Custom prompt")
def test_target_tokens_auto_adjusted(self):
"""Test that target_context_tokens is auto-adjusted when it exceeds max."""
config = LLMAutoContextSummarizationConfig(
max_context_tokens=1000,
summary_config=LLMContextSummaryConfig(target_context_tokens=9000),
)
self.assertLessEqual(config.summary_config.target_context_tokens, config.max_context_tokens)
class TestLLMContextSummarizationConfigDeprecated(unittest.TestCase):
"""Tests for deprecated LLMContextSummarizationConfig."""
def test_emits_deprecation_warning(self):
"""Test that instantiating the deprecated config emits a DeprecationWarning."""
with self.assertWarns(DeprecationWarning):
LLMContextSummarizationConfig()
def test_to_auto_config(self):
"""Test conversion to the new LLMAutoContextSummarizationConfig."""
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
old_config = LLMContextSummarizationConfig(
max_context_tokens=2500,
target_context_tokens=2000,
max_unsummarized_messages=15,
min_messages_after_summary=4,
summarization_prompt="Custom",
)
new_config = old_config.to_auto_config()
self.assertIsInstance(new_config, LLMAutoContextSummarizationConfig)
self.assertEqual(new_config.max_context_tokens, 2500)
self.assertEqual(new_config.max_unsummarized_messages, 15)
self.assertEqual(new_config.summary_config.target_context_tokens, 2000)
self.assertEqual(new_config.summary_config.min_messages_after_summary, 4)
self.assertEqual(new_config.summary_config.summarization_prompt, "Custom")
class TestFunctionCallHandling(unittest.TestCase):
"""Tests for function call handling in summarization."""
@@ -670,10 +738,12 @@ class TestDedicatedLLMSummarization(unittest.IsolatedAsyncioTestCase):
{"role": "user", "content": f"Test message {i} that adds tokens to context."}
)
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50, # Very low to trigger easily
llm=dedicated_llm,
summarization_timeout=5.0,
summary_config=LLMContextSummaryConfig(
llm=dedicated_llm,
summarization_timeout=5.0,
),
)
return context, config
@@ -736,7 +806,7 @@ class TestDedicatedLLMSummarization(unittest.IsolatedAsyncioTestCase):
dedicated_llm._generate_summary = slow_summary
context, config = self._create_context_and_config(dedicated_llm)
config.summarization_timeout = 0.1 # Very short timeout
config.summary_config.summarization_timeout = 0.1 # Very short timeout
summarizer = LLMContextSummarizer(context=context, config=config)
await summarizer.setup(self.task_manager)
@@ -826,7 +896,7 @@ class TestDedicatedLLMSummarization(unittest.IsolatedAsyncioTestCase):
{"role": "user", "content": f"Test message {i} that adds tokens to context."}
)
config = LLMContextSummarizationConfig(max_context_tokens=50)
config = LLMAutoContextSummarizationConfig(max_context_tokens=50)
summarizer = LLMContextSummarizer(context=context, config=config)
await summarizer.setup(self.task_manager)

View File

@@ -12,6 +12,7 @@ from pipecat.frames.frames import (
LLMContextSummaryRequestFrame,
LLMContextSummaryResultFrame,
LLMFullResponseStartFrame,
LLMSummarizeContextFrame,
)
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.llm_context_summarizer import (
@@ -19,7 +20,10 @@ from pipecat.processors.aggregators.llm_context_summarizer import (
SummaryAppliedEvent,
)
from pipecat.utils.asyncio.task_manager import TaskManager, TaskManagerParams
from pipecat.utils.context.llm_context_summarization import LLMContextSummarizationConfig
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummaryConfig,
)
class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
@@ -35,7 +39,7 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summarization_triggered_by_token_limit(self):
"""Test that summarization is triggered when token limit is reached."""
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=100, # Very low to trigger easily
max_unsummarized_messages=100, # High so it doesn't trigger by message count
)
@@ -71,7 +75,7 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summarization_triggered_by_message_count(self):
"""Test that summarization is triggered when message count threshold is reached."""
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=100000, # Very high so it doesn't trigger by tokens
max_unsummarized_messages=5, # Low to trigger easily
)
@@ -101,7 +105,7 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summarization_not_triggered_below_thresholds(self):
"""Test that summarization is not triggered when below thresholds."""
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=10000,
max_unsummarized_messages=20,
)
@@ -130,7 +134,7 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summarization_in_progress_prevents_duplicate(self):
"""Test that a summarization in progress prevents triggering another."""
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50, # Very low
max_unsummarized_messages=100,
)
@@ -161,7 +165,10 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summary_result_handling(self):
"""Test that summary results are processed and applied correctly."""
config = LLMContextSummarizationConfig(max_context_tokens=50, min_messages_after_summary=2)
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summary_config=LLMContextSummaryConfig(min_messages_after_summary=2),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -208,7 +215,7 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_interruption_cancels_summarization(self):
"""Test that an interruption cancels pending summarization."""
config = LLMContextSummarizationConfig(max_context_tokens=50)
config = LLMAutoContextSummarizationConfig(max_context_tokens=50)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -238,7 +245,10 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_stale_summary_result_ignored(self):
"""Test that stale summary results are ignored."""
config = LLMContextSummarizationConfig(max_context_tokens=50, min_messages_after_summary=2)
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summary_config=LLMContextSummaryConfig(min_messages_after_summary=2),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -294,9 +304,116 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
await summarizer.cleanup()
async def test_manual_summarization_via_frame(self):
"""Test that LLMSummarizeContextFrame triggers summarization on demand."""
config = LLMAutoContextSummarizationConfig(
max_context_tokens=100000, # High — auto trigger would never fire
max_unsummarized_messages=100,
)
summarizer = LLMContextSummarizer(
context=self.context,
config=config,
auto_trigger=False, # Disable auto; only manual requests should work
)
await summarizer.setup(self.task_manager)
request_frame = None
@summarizer.event_handler("on_request_summarization")
async def on_request_summarization(summarizer, frame):
nonlocal request_frame
request_frame = frame
# Add messages
for i in range(5):
self.context.add_message({"role": "user", "content": f"Message {i}"})
# Auto-trigger should NOT fire even on LLMFullResponseStartFrame
await summarizer.process_frame(LLMFullResponseStartFrame())
self.assertIsNone(request_frame)
# Manual trigger via LLMSummarizeContextFrame should fire
await summarizer.process_frame(LLMSummarizeContextFrame())
self.assertIsNotNone(request_frame)
self.assertIsInstance(request_frame, LLMContextSummaryRequestFrame)
# The request must have a valid request_id and carry the current context
self.assertTrue(request_frame.request_id)
self.assertEqual(request_frame.context, self.context)
await summarizer.cleanup()
async def test_manual_summarization_with_config_override(self):
"""Test that LLMSummarizeContextFrame can override default summary config."""
config = LLMAutoContextSummarizationConfig(
max_context_tokens=100000,
summary_config=LLMContextSummaryConfig(
target_context_tokens=6000,
min_messages_after_summary=4,
),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
request_frame = None
@summarizer.event_handler("on_request_summarization")
async def on_request_summarization(summarizer, frame):
nonlocal request_frame
request_frame = frame
for i in range(5):
self.context.add_message({"role": "user", "content": f"Message {i}"})
# Push a manual frame with custom config overrides
custom_config = LLMContextSummaryConfig(
target_context_tokens=500,
min_messages_after_summary=1,
)
await summarizer.process_frame(LLMSummarizeContextFrame(config=custom_config))
self.assertIsNotNone(request_frame)
# The request should use the overridden values
self.assertEqual(request_frame.target_context_tokens, 500)
self.assertEqual(request_frame.min_messages_to_keep, 1)
await summarizer.cleanup()
async def test_manual_summarization_blocked_when_in_progress(self):
"""Test that a second LLMSummarizeContextFrame is ignored while one is in progress."""
config = LLMAutoContextSummarizationConfig(max_context_tokens=100000)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
request_count = 0
@summarizer.event_handler("on_request_summarization")
async def on_request_summarization(summarizer, frame):
nonlocal request_count
request_count += 1
for i in range(5):
self.context.add_message({"role": "user", "content": f"Message {i}"})
# First manual request
await summarizer.process_frame(LLMSummarizeContextFrame())
self.assertEqual(request_count, 1)
# Second manual request while first is in progress — should be ignored
await summarizer.process_frame(LLMSummarizeContextFrame())
self.assertEqual(request_count, 1)
await summarizer.cleanup()
async def test_summary_message_role_is_user(self):
"""Test that the summary message uses the user role."""
config = LLMContextSummarizationConfig(max_context_tokens=50, min_messages_after_summary=2)
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summary_config=LLMContextSummaryConfig(min_messages_after_summary=2),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -335,7 +452,10 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summary_message_default_template(self):
"""Test that the default summary_message_template is used."""
config = LLMContextSummarizationConfig(max_context_tokens=50, min_messages_after_summary=2)
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summary_config=LLMContextSummaryConfig(min_messages_after_summary=2),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -377,10 +497,12 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_summary_message_custom_template(self):
"""Test that a custom summary_message_template is applied."""
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
min_messages_after_summary=2,
summary_message_template="<context_summary>\n{summary}\n</context_summary>",
summary_config=LLMContextSummaryConfig(
min_messages_after_summary=2,
summary_message_template="<context_summary>\n{summary}\n</context_summary>",
),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
@@ -420,7 +542,10 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_on_summary_applied_event(self):
"""Test that on_summary_applied event fires with correct data."""
config = LLMContextSummarizationConfig(max_context_tokens=50, min_messages_after_summary=2)
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summary_config=LLMContextSummaryConfig(min_messages_after_summary=2),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -474,7 +599,10 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_on_summary_applied_not_fired_on_error(self):
"""Test that on_summary_applied event is NOT fired when summarization fails."""
config = LLMContextSummarizationConfig(max_context_tokens=50, min_messages_after_summary=2)
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summary_config=LLMContextSummaryConfig(min_messages_after_summary=2),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)
await summarizer.setup(self.task_manager)
@@ -515,9 +643,9 @@ class TestLLMContextSummarizer(unittest.IsolatedAsyncioTestCase):
async def test_request_frame_includes_timeout(self):
"""Test that the request frame includes the configured summarization_timeout."""
config = LLMContextSummarizationConfig(
config = LLMAutoContextSummarizationConfig(
max_context_tokens=50,
summarization_timeout=60.0,
summary_config=LLMContextSummaryConfig(summarization_timeout=60.0),
)
summarizer = LLMContextSummarizer(context=self.context, config=config)