From ac5734d0ede6eeaddc32d7d70eeb71f5f12ccb86 Mon Sep 17 00:00:00 2001 From: Paul Kompfner Date: Thu, 30 Oct 2025 15:48:15 -0400 Subject: [PATCH] Deprecate `expect_stripped_words` option from `LLMAssistantAggregatorParams`, when used with the newer `LLMAssistantAggregator`, which now handles word spacing automatically. This commit does not change how it works in the older `LLMAssistantContextAggregator`. --- CHANGELOG.md | 20 +++----- examples/foundational/19-openai-realtime.py | 7 +-- examples/foundational/19a-azure-realtime.py | 7 +-- .../26a-gemini-live-transcription.py | 7 +-- .../26b-gemini-live-function-calling.py | 7 +-- .../foundational/26c-gemini-live-video.py | 7 +-- .../26e-gemini-live-google-search.py | 7 +-- .../foundational/26f-gemini-live-files-api.py | 7 +-- .../26g-gemini-live-groundingMetadata.py | 7 +-- ...26h-gemini-live-vertex-function-calling.py | 7 +-- .../26i-gemini-live-graceful-end.py | 7 +-- examples/foundational/46-video-processing.py | 7 +-- .../processors/aggregators/llm_response.py | 4 +- .../aggregators/llm_response_universal.py | 49 +++++++++++++------ .../processors/transcript_processor.py | 25 +--------- src/pipecat/utils/string.py | 39 ++++++++++++++- tests/test_context_aggregators.py | 45 +++++++++++++---- tests/test_langchain.py | 4 +- 18 files changed, 130 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ec6ad556..96d14646e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,13 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ```python context = LLMContext(messages, tools) - context_aggregator = LLMContextAggregatorPair( - context, - # This part is `OpenAIRealtimeLLMService`-specific. - # `expect_stripped_words=False` needed when OpenAI Realtime used with - # "audio" modality (the default). - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) ``` (Note that even though `OpenAIRealtimeLLMService` now supports the universal @@ -116,13 +110,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ```python context = LLMContext(messages, tools) - context_aggregator = LLMContextAggregatorPair( - context, - # This part is `GeminiLiveLLMService`-specific. - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default). - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) ``` (Note that even though `GeminiLiveLLMService` now supports the universal @@ -202,6 +190,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated +- The `expect_stripped_words` parameter of `LLMAssistantAggregatorParams` is + ignored when used with the newer `LLMAssistantAggregator`, which now handles + word spacing automatically. + - `LLMService.request_image_frame()` is deprecated, push a `UserImageRequestFrame` instead. diff --git a/examples/foundational/19-openai-realtime.py b/examples/foundational/19-openai-realtime.py index 6907ec196..b5edc0ff2 100644 --- a/examples/foundational/19-openai-realtime.py +++ b/examples/foundational/19-openai-realtime.py @@ -187,12 +187,7 @@ Remember, your responses should be short. Just one or two sentences, usually. Re tools, ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when OpenAI Realtime used with - # "audio" modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/19a-azure-realtime.py b/examples/foundational/19a-azure-realtime.py index 7d9cf1b4b..b56c05af9 100644 --- a/examples/foundational/19a-azure-realtime.py +++ b/examples/foundational/19a-azure-realtime.py @@ -175,12 +175,7 @@ Remember, your responses should be short. Just one or two sentences, usually. Re tools, ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when OpenAI Realtime used with - # "audio" modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/26a-gemini-live-transcription.py b/examples/foundational/26a-gemini-live-transcription.py index 9ac22a814..2aed3e650 100644 --- a/examples/foundational/26a-gemini-live-transcription.py +++ b/examples/foundational/26a-gemini-live-transcription.py @@ -92,12 +92,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): # }, ], ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) transcript = TranscriptProcessor() diff --git a/examples/foundational/26b-gemini-live-function-calling.py b/examples/foundational/26b-gemini-live-function-calling.py index fd2b36ab1..19a23e798 100644 --- a/examples/foundational/26b-gemini-live-function-calling.py +++ b/examples/foundational/26b-gemini-live-function-calling.py @@ -144,12 +144,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): context = LLMContext( [{"role": "user", "content": "Say hello."}], ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/26c-gemini-live-video.py b/examples/foundational/26c-gemini-live-video.py index be036a557..3b68a650a 100644 --- a/examples/foundational/26c-gemini-live-video.py +++ b/examples/foundational/26c-gemini-live-video.py @@ -75,12 +75,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): }, ], ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/26e-gemini-live-google-search.py b/examples/foundational/26e-gemini-live-google-search.py index e80ed4536..f5a3fd675 100644 --- a/examples/foundational/26e-gemini-live-google-search.py +++ b/examples/foundational/26e-gemini-live-google-search.py @@ -100,12 +100,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): } ], ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/26f-gemini-live-files-api.py b/examples/foundational/26f-gemini-live-files-api.py index 0091c01e6..e8b38ba6d 100644 --- a/examples/foundational/26f-gemini-live-files-api.py +++ b/examples/foundational/26f-gemini-live-files-api.py @@ -164,12 +164,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): ) # Create context aggregator - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) # Build the pipeline pipeline = Pipeline( diff --git a/examples/foundational/26g-gemini-live-groundingMetadata.py b/examples/foundational/26g-gemini-live-groundingMetadata.py index df86094da..553539ab2 100644 --- a/examples/foundational/26g-gemini-live-groundingMetadata.py +++ b/examples/foundational/26g-gemini-live-groundingMetadata.py @@ -127,12 +127,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): # Set up conversation context and management context = LLMContext(messages) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/26h-gemini-live-vertex-function-calling.py b/examples/foundational/26h-gemini-live-vertex-function-calling.py index 126d85ad7..4d1534829 100644 --- a/examples/foundational/26h-gemini-live-vertex-function-calling.py +++ b/examples/foundational/26h-gemini-live-vertex-function-calling.py @@ -140,12 +140,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): llm.register_function("get_restaurant_recommendation", fetch_restaurant_recommendation) context = LLMContext([{"role": "user", "content": "Say hello."}]) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/26i-gemini-live-graceful-end.py b/examples/foundational/26i-gemini-live-graceful-end.py index 2865dbed4..9d3628777 100644 --- a/examples/foundational/26i-gemini-live-graceful-end.py +++ b/examples/foundational/26i-gemini-live-graceful-end.py @@ -157,12 +157,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): context = LLMContext( [{"role": "user", "content": "Say hello."}], ) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ diff --git a/examples/foundational/46-video-processing.py b/examples/foundational/46-video-processing.py index 5f92139bc..159da270a 100644 --- a/examples/foundational/46-video-processing.py +++ b/examples/foundational/46-video-processing.py @@ -111,12 +111,7 @@ async def run_bot(pipecat_transport): ] context = LLMContext(messages) - context_aggregator = LLMContextAggregatorPair( - context, - # `expect_stripped_words=False` needed when Gemini Live used with AUDIO - # modality (the default) - assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False), - ) + context_aggregator = LLMContextAggregatorPair(context) # RTVI events for Pipecat client UI rtvi = RTVIProcessor() diff --git a/src/pipecat/processors/aggregators/llm_response.py b/src/pipecat/processors/aggregators/llm_response.py index ace7b94fd..afc091d5a 100644 --- a/src/pipecat/processors/aggregators/llm_response.py +++ b/src/pipecat/processors/aggregators/llm_response.py @@ -89,7 +89,9 @@ class LLMAssistantAggregatorParams: Parameters: expect_stripped_words: Whether to expect and handle stripped words - in text frames by adding spaces between tokens. + in text frames by adding spaces between tokens. This parameter is + ignored when used with the newer LLMAssistantAggregator, which + handles word spacing automatically. """ expect_stripped_words: bool = True diff --git a/src/pipecat/processors/aggregators/llm_response_universal.py b/src/pipecat/processors/aggregators/llm_response_universal.py index 44c534b9b..e29b5b2dd 100644 --- a/src/pipecat/processors/aggregators/llm_response_universal.py +++ b/src/pipecat/processors/aggregators/llm_response_universal.py @@ -13,6 +13,7 @@ LLM processing, and text-to-speech components in conversational AI pipelines. import asyncio import json +import warnings from abc import abstractmethod from typing import Any, Dict, List, Literal, Optional, Set @@ -65,6 +66,7 @@ from pipecat.processors.aggregators.llm_response import ( LLMUserAggregatorParams, ) from pipecat.processors.frame_processor import FrameDirection, FrameProcessor +from pipecat.utils.string import concatenate_aggregated_text from pipecat.utils.time import time_now_iso8601 @@ -88,7 +90,7 @@ class LLMContextAggregator(FrameProcessor): self._context = context self._role = role - self._aggregation: str = "" + self._aggregation: List[str] = [] @property def messages(self) -> List[LLMContextMessage]: @@ -168,13 +170,21 @@ class LLMContextAggregator(FrameProcessor): async def reset(self): """Reset the aggregation state.""" - self._aggregation = "" + self._aggregation = [] @abstractmethod async def push_aggregation(self): """Push the current aggregation downstream.""" pass + def aggregation_string(self) -> str: + """Get the current aggregation as a string. + + Returns: + The concatenated aggregation string. + """ + return concatenate_aggregated_text(self._aggregation) + class LLMUserAggregator(LLMContextAggregator): """User LLM aggregator that processes speech-to-text transcriptions. @@ -212,8 +222,6 @@ class LLMUserAggregator(LLMContextAggregator): self._turn_params: Optional[SmartTurnParams] = None if "aggregation_timeout" in kwargs: - import warnings - with warnings.catch_warnings(): warnings.simplefilter("always") warnings.warn( @@ -307,7 +315,7 @@ class LLMUserAggregator(LLMContextAggregator): async def _process_aggregation(self): """Process the current aggregation and push it downstream.""" - aggregation = self._aggregation + aggregation = self.aggregation_string() await self.reset() self._context.add_message({"role": self.role, "content": aggregation}) frame = LLMContextFrame(self._context) @@ -355,7 +363,7 @@ class LLMUserAggregator(LLMContextAggregator): """ async def should_interrupt(strategy: BaseInterruptionStrategy): - await strategy.append_text(self._aggregation) + await strategy.append_text(self.aggregation_string()) return await strategy.should_interrupt() return any([await should_interrupt(s) for s in self._interruption_strategies]) @@ -425,7 +433,7 @@ class LLMUserAggregator(LLMContextAggregator): if not text.strip(): return - self._aggregation += f" {text}" if self._aggregation else text + self._aggregation.append(text) # We just got a final result, so let's reset interim results. self._seen_interim_results = False # Reset aggregation timer. @@ -550,23 +558,31 @@ class LLMAssistantAggregator(LLMContextAggregator): Args: context: The OpenAI LLM context for conversation storage. params: Configuration parameters for aggregation behavior. - **kwargs: Additional arguments. Supports deprecated 'expect_stripped_words'. + **kwargs: Additional arguments. """ super().__init__(context=context, role="assistant", **kwargs) self._params = params or LLMAssistantAggregatorParams() if "expect_stripped_words" in kwargs: - import warnings - with warnings.catch_warnings(): warnings.simplefilter("always") warnings.warn( - "Parameter 'expect_stripped_words' is deprecated, use 'params' instead.", + "Parameter 'expect_stripped_words' is deprecated. " + "LLMAssistantAggregator now handles word spacing automatically.", DeprecationWarning, ) self._params.expect_stripped_words = kwargs["expect_stripped_words"] + if params and not params.expect_stripped_words: + with warnings.catch_warnings(): + warnings.simplefilter("always") + warnings.warn( + "params.expect_stripped_words is deprecated. " + "LLMAssistantAggregator now handles word spacing automatically.", + DeprecationWarning, + ) + self._started = 0 self._function_calls_in_progress: Dict[str, Optional[FunctionCallInProgressFrame]] = {} self._context_updated_tasks: Set[asyncio.Task] = set() @@ -629,7 +645,7 @@ class LLMAssistantAggregator(LLMContextAggregator): if not self._aggregation: return - aggregation = self._aggregation.strip() + aggregation = self.aggregation_string() await self.reset() if aggregation: @@ -793,10 +809,11 @@ class LLMAssistantAggregator(LLMContextAggregator): if not self._started: return - if self._params.expect_stripped_words: - self._aggregation += f" {frame.text}" if self._aggregation else frame.text - else: - self._aggregation += frame.text + # Make sure we really have text (spaces count, too!) + if len(frame.text) == 0: + return + + self._aggregation.append(frame.text) def _context_updated_task_finished(self, task: asyncio.Task): self._context_updated_tasks.discard(task) diff --git a/src/pipecat/processors/transcript_processor.py b/src/pipecat/processors/transcript_processor.py index 5900ffc7f..13b2bb97f 100644 --- a/src/pipecat/processors/transcript_processor.py +++ b/src/pipecat/processors/transcript_processor.py @@ -26,6 +26,7 @@ from pipecat.frames.frames import ( TTSTextFrame, ) from pipecat.processors.frame_processor import FrameDirection, FrameProcessor +from pipecat.utils.string import concatenate_aggregated_text from pipecat.utils.time import time_now_iso8601 @@ -140,29 +141,7 @@ class AssistantTranscriptProcessor(BaseTranscriptProcessor): Result: "Hello there how are you" """ if self._current_text_parts and self._aggregation_start_time: - # Check specifically for space characters, previously isspace() was used - # but that includes all whitespace characters (e.g. \n), not just spaces. - has_leading_spaces = any( - part and part[0] == " " for part in self._current_text_parts[1:] - ) - has_trailing_spaces = any( - part and part[-1] == " " for part in self._current_text_parts[:-1] - ) - - # If there are embedded spaces in the fragments, use direct concatenation - contains_spacing_between_fragments = has_leading_spaces or has_trailing_spaces - - # Apply corresponding joining method - if contains_spacing_between_fragments: - # Fragments already have spacing - just concatenate - content = "".join(self._current_text_parts) - else: - # Word-by-word fragments - join with spaces - content = " ".join(self._current_text_parts) - - # Clean up any excessive whitespace - content = content.strip() - + content = concatenate_aggregated_text(self._current_text_parts) if content: logger.trace(f"Emitting aggregated assistant message: {content}") message = TranscriptionMessage( diff --git a/src/pipecat/utils/string.py b/src/pipecat/utils/string.py index 11964d23f..25ce6afd5 100644 --- a/src/pipecat/utils/string.py +++ b/src/pipecat/utils/string.py @@ -18,7 +18,7 @@ Dependencies: """ import re -from typing import FrozenSet, Optional, Sequence, Tuple +from typing import FrozenSet, List, Optional, Sequence, Tuple import nltk from loguru import logger @@ -196,3 +196,40 @@ def parse_start_end_tags( return (None, len(text)) return (None, current_tag_index) + + +def concatenate_aggregated_text(text_parts: List[str]) -> str: + """Concatenate a list of text parts into a single string. + + This function joins the provided list of text parts into a single string, + taking into account whether or not the parts already contain spacing. + + This function is useful for aggregating text segments received from LLMs or + transcription services. + + Args: + text_parts: A list of strings representing parts of text to concatenate. + + Returns: + A single concatenated string. + """ + # Check specifically for space characters, previously isspace() was used + # but that includes all whitespace characters (e.g. \n), not just spaces. + has_leading_spaces = any(part and part[0] == " " for part in text_parts[1:]) + has_trailing_spaces = any(part and part[-1] == " " for part in text_parts[:-1]) + + # If there are embedded spaces in the fragments, use direct concatenation + contains_spacing_between_fragments = has_leading_spaces or has_trailing_spaces + + # Apply corresponding joining method + if contains_spacing_between_fragments: + # Fragments already have spacing - just concatenate + result = "".join(text_parts) + else: + # Word-by-word fragments - join with spaces + result = " ".join(text_parts) + + # Clean up any excessive whitespace + result = result.strip() + + return result diff --git a/tests/test_context_aggregators.py b/tests/test_context_aggregators.py index 77b6acc87..6196032a3 100644 --- a/tests/test_context_aggregators.py +++ b/tests/test_context_aggregators.py @@ -6,7 +6,7 @@ import json import unittest -from typing import Any +from typing import Any, Optional from pipecat.audio.interruptions.min_words_interruption_strategy import MinWordsInterruptionStrategy from pipecat.audio.turn.smart_turn.base_smart_turn import SmartTurnParams @@ -22,6 +22,8 @@ from pipecat.frames.frames import ( InterimTranscriptionFrame, InterruptionFrame, InterruptionTaskFrame, + LLMContextAssistantTimestampFrame, + LLMContextFrame, LLMFullResponseEndFrame, LLMFullResponseStartFrame, OpenAILLMContextAssistantTimestampFrame, @@ -38,6 +40,7 @@ from pipecat.processors.aggregators.llm_response import ( LLMUserAggregatorParams, LLMUserContextAggregator, ) +from pipecat.processors.aggregators.llm_response_universal import LLMAssistantAggregator from pipecat.processors.aggregators.openai_llm_context import ( OpenAILLMContext, OpenAILLMContextFrame, @@ -586,11 +589,16 @@ class BaseTestUserContextAggregator: assert context_processor.context_received -class BaseTestAssistantContextAggreagator: +class BaseTestAssistantContextAggregator: CONTEXT_CLASS = None # To be set in subclasses AGGREGATOR_CLASS = None # To be set in subclasses EXPECTED_CONTEXT_FRAMES = None # To be set in subclasses + def create_assistant_aggregator_params( + self, **kwargs + ) -> Optional[LLMAssistantAggregatorParams]: + return LLMAssistantAggregatorParams(**kwargs) + def check_message_content(self, context: OpenAILLMContext, index: int, content: str): assert context.messages[index]["content"] == content @@ -641,7 +649,7 @@ class BaseTestAssistantContextAggreagator: context = self.CONTEXT_CLASS() aggregator = self.AGGREGATOR_CLASS( - context, params=LLMAssistantAggregatorParams(expect_stripped_words=False) + context, params=self.create_assistant_aggregator_params(expect_stripped_words=False) ) frames_to_send = [ LLMFullResponseStartFrame(), @@ -687,7 +695,7 @@ class BaseTestAssistantContextAggreagator: context = self.CONTEXT_CLASS() aggregator = self.AGGREGATOR_CLASS( - context, params=LLMAssistantAggregatorParams(expect_stripped_words=False) + context, params=self.create_assistant_aggregator_params(expect_stripped_words=False) ) frames_to_send = [ LLMFullResponseStartFrame(), @@ -714,7 +722,7 @@ class BaseTestAssistantContextAggreagator: context = self.CONTEXT_CLASS() aggregator = self.AGGREGATOR_CLASS( - context, params=LLMAssistantAggregatorParams(expect_stripped_words=False) + context, params=self.create_assistant_aggregator_params(expect_stripped_words=False) ) frames_to_send = [ LLMFullResponseStartFrame(), @@ -838,7 +846,7 @@ class TestAnthropicUserContextAggregator( class TestAnthropicAssistantContextAggregator( - BaseTestAssistantContextAggreagator, unittest.IsolatedAsyncioTestCase + BaseTestAssistantContextAggregator, unittest.IsolatedAsyncioTestCase ): CONTEXT_CLASS = AnthropicLLMContext AGGREGATOR_CLASS = AnthropicAssistantContextAggregator @@ -873,7 +881,7 @@ class TestAWSBedrockUserContextAggregator( class TestAWSBedrockAssistantContextAggregator( - BaseTestAssistantContextAggreagator, unittest.IsolatedAsyncioTestCase + BaseTestAssistantContextAggregator, unittest.IsolatedAsyncioTestCase ): CONTEXT_CLASS = AWSBedrockLLMContext AGGREGATOR_CLASS = AWSBedrockAssistantContextAggregator @@ -914,7 +922,7 @@ class TestGoogleUserContextAggregator( class TestGoogleAssistantContextAggregator( - BaseTestAssistantContextAggreagator, unittest.IsolatedAsyncioTestCase + BaseTestAssistantContextAggregator, unittest.IsolatedAsyncioTestCase ): CONTEXT_CLASS = GoogleLLMContext AGGREGATOR_CLASS = GoogleAssistantContextAggregator @@ -948,8 +956,27 @@ class TestOpenAIUserContextAggregator( class TestOpenAIAssistantContextAggregator( - BaseTestAssistantContextAggreagator, unittest.IsolatedAsyncioTestCase + BaseTestAssistantContextAggregator, unittest.IsolatedAsyncioTestCase ): CONTEXT_CLASS = OpenAILLMContext AGGREGATOR_CLASS = OpenAIAssistantContextAggregator EXPECTED_CONTEXT_FRAMES = [OpenAILLMContextFrame, OpenAILLMContextAssistantTimestampFrame] + + +# +# Universal +# +class TestLLMAssistantAggregator( + BaseTestAssistantContextAggregator, unittest.IsolatedAsyncioTestCase +): + CONTEXT_CLASS = OpenAILLMContext + AGGREGATOR_CLASS = LLMAssistantAggregator + EXPECTED_CONTEXT_FRAMES = [LLMContextFrame, LLMContextAssistantTimestampFrame] + + # Override to remove 'expect_stripped_words' parameter, which is deprecated + # for LLMAssistantAggregator + def create_assistant_aggregator_params( + self, **kwargs + ) -> Optional[LLMAssistantAggregatorParams]: + kwargs.pop("expect_stripped_words", None) + return LLMAssistantAggregatorParams(**kwargs) if kwargs else None diff --git a/tests/test_langchain.py b/tests/test_langchain.py index dd7f9ccef..4e197b2aa 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -65,9 +65,7 @@ class TestLangchain(unittest.IsolatedAsyncioTestCase): self.mock_proc = self.MockProcessor("token_collector") context = LLMContext() - context_aggregator = LLMContextAggregatorPair( - context, assistant_params=LLMAssistantAggregatorParams(expect_stripped_words=False) - ) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [context_aggregator.user(), proc, self.mock_proc, context_aggregator.assistant()]