diff --git a/changelog/4272.added.md b/changelog/4272.added.md index 4323bfb2b..af278e770 100644 --- a/changelog/4272.added.md +++ b/changelog/4272.added.md @@ -1 +1 @@ -- Added `elide_large_values` parameter to `LLMContext.get_messages()`. When `True`, returns compact deep copies of messages with binary data (base64 images, audio) fully elided and long string values in LLM-specific messages recursively truncated. Useful for serialization, logging, and debugging tools. +- Added `truncate_large_values` parameter to `LLMContext.get_messages()`. When `True`, returns compact deep copies of messages with binary data (base64 images, audio) fully truncated and long string values in LLM-specific messages recursively truncated. Useful for serialization, logging, and debugging tools. diff --git a/src/pipecat/adapters/base_llm_adapter.py b/src/pipecat/adapters/base_llm_adapter.py index 54b10b926..9c6747766 100644 --- a/src/pipecat/adapters/base_llm_adapter.py +++ b/src/pipecat/adapters/base_llm_adapter.py @@ -126,20 +126,20 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]): return LLMSpecificMessage(llm=self.id_for_llm_specific_messages, message=message) def get_messages( - self, context: LLMContext, *, elide_large_values: bool = False + self, context: LLMContext, *, truncate_large_values: bool = False ) -> List[LLMContextMessage]: """Get messages from the LLM context, including standard and LLM-specific messages. Args: context: The LLM context containing messages. - elide_large_values: If True, return deep copies of messages with + truncate_large_values: If True, return deep copies of messages with large values replaced by short placeholders. Returns: List of messages including standard and LLM-specific messages. """ return context.get_messages( - self.id_for_llm_specific_messages, elide_large_values=elide_large_values + self.id_for_llm_specific_messages, truncate_large_values=truncate_large_values ) def from_standard_tools(self, tools: Any) -> List[Any] | NotGiven: diff --git a/src/pipecat/adapters/services/grok_realtime_adapter.py b/src/pipecat/adapters/services/grok_realtime_adapter.py index 59347f2cd..cc98887f8 100644 --- a/src/pipecat/adapters/services/grok_realtime_adapter.py +++ b/src/pipecat/adapters/services/grok_realtime_adapter.py @@ -85,7 +85,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter): Returns: List of messages with sensitive data redacted. """ - return self.get_messages(context, elide_large_values=True) + return self.get_messages(context, truncate_large_values=True) @dataclass class ConvertedMessages: diff --git a/src/pipecat/adapters/services/inworld_realtime_adapter.py b/src/pipecat/adapters/services/inworld_realtime_adapter.py index f612f849b..b022afe6b 100644 --- a/src/pipecat/adapters/services/inworld_realtime_adapter.py +++ b/src/pipecat/adapters/services/inworld_realtime_adapter.py @@ -85,7 +85,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter): Returns: List of messages with sensitive data redacted. """ - return self.get_messages(context, elide_large_values=True) + return self.get_messages(context, truncate_large_values=True) @dataclass class ConvertedMessages: diff --git a/src/pipecat/adapters/services/open_ai_adapter.py b/src/pipecat/adapters/services/open_ai_adapter.py index 6fa1a493b..a52fb84a6 100644 --- a/src/pipecat/adapters/services/open_ai_adapter.py +++ b/src/pipecat/adapters/services/open_ai_adapter.py @@ -126,7 +126,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]): Returns: List of messages in a format ready for logging about OpenAI. """ - return self.get_messages(context, elide_large_values=True) + return self.get_messages(context, truncate_large_values=True) def _from_universal_context_messages( self, diff --git a/src/pipecat/adapters/services/open_ai_realtime_adapter.py b/src/pipecat/adapters/services/open_ai_realtime_adapter.py index 5a7b96c07..41f3ce89d 100644 --- a/src/pipecat/adapters/services/open_ai_realtime_adapter.py +++ b/src/pipecat/adapters/services/open_ai_realtime_adapter.py @@ -81,7 +81,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter): Returns: List of messages in a format ready for logging about OpenAI Realtime. """ - return self.get_messages(context, elide_large_values=True) + return self.get_messages(context, truncate_large_values=True) @dataclass class ConvertedMessages: diff --git a/src/pipecat/adapters/services/open_ai_responses_adapter.py b/src/pipecat/adapters/services/open_ai_responses_adapter.py index c30ebdb5d..f3dd67e03 100644 --- a/src/pipecat/adapters/services/open_ai_responses_adapter.py +++ b/src/pipecat/adapters/services/open_ai_responses_adapter.py @@ -143,7 +143,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam Returns: List of messages in a format ready for logging. """ - return self.get_messages(context, elide_large_values=True) + return self.get_messages(context, truncate_large_values=True) def _convert_messages_to_input( self, messages: List[LLMContextMessage] diff --git a/src/pipecat/processors/aggregators/llm_context.py b/src/pipecat/processors/aggregators/llm_context.py index 2d2470848..00ec8f178 100644 --- a/src/pipecat/processors/aggregators/llm_context.py +++ b/src/pipecat/processors/aggregators/llm_context.py @@ -203,7 +203,7 @@ class LLMContext: self, llm_specific_filter: Optional[str] = None, *, - elide_large_values: bool = False, + truncate_large_values: bool = False, ) -> List[LLMContextMessage]: """Get the current messages list. @@ -213,10 +213,10 @@ class LLMContext: messages. If messages end up being filtered, an error will be logged; this is intended to catch accidental use of incompatible LLM-specific messages. - elide_large_values: If True, return deep copies of messages with + truncate_large_values: If True, return deep copies of messages with large values replaced by short placeholders. For standard messages, known binary data (base64-encoded images, audio) is - fully elided. For LLM-specific messages, long string values + fully truncated. For LLM-specific messages, long string values are truncated. Returns: @@ -235,19 +235,19 @@ class LLMContext: f"Attempted to use incompatible LLMSpecificMessages with LLM '{llm_specific_filter}'." ) - if elide_large_values: - messages = LLMContext._elide_large_values_from_messages(messages) + if truncate_large_values: + messages = LLMContext._truncate_large_values_from_messages(messages) return messages @staticmethod - def _elide_large_values_from_messages( + def _truncate_large_values_from_messages( messages: List[LLMContextMessage], ) -> List[LLMContextMessage]: """Return deep copies of messages with large values replaced by placeholders. For standard (universal-format) messages, the following known binary - patterns are fully elided: + patterns are fully truncated: - ``image_url`` items with ``data:image/...`` base64 URLs - ``input_audio`` items with ``input_audio.data`` or ``audio`` fields diff --git a/tests/test_llm_context.py b/tests/test_llm_context.py index bdece771a..e9b0c4d8a 100644 --- a/tests/test_llm_context.py +++ b/tests/test_llm_context.py @@ -15,13 +15,13 @@ from pipecat.processors.aggregators.llm_context import ( ) -class TestGetMessagesElideLargeValues(unittest.TestCase): - """Tests for LLMContext.get_messages(elide_large_values=True).""" +class TestGetMessagesTruncateLargeValues(unittest.TestCase): + """Tests for LLMContext.get_messages(truncate_large_values=True).""" # -- Standard messages: binary elision ----------------------------------- def test_default_preserves_all_data(self): - """elide_large_values defaults to False, preserving all data.""" + """truncate_large_values defaults to False, preserving all data.""" messages = [ { "role": "user", @@ -57,7 +57,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["content"][0]["text"], "Describe this image") self.assertEqual(result[0]["content"][1]["image_url"]["url"], "data:image/...") @@ -76,7 +76,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual( result[0]["content"][0]["image_url"]["url"], @@ -98,7 +98,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["content"][1]["input_audio"]["data"], "...") self.assertEqual(result[0]["content"][1]["input_audio"]["format"], "wav") @@ -115,7 +115,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["content"][0]["audio"], "...") self.assertEqual(result[0]["content"][1]["audio"], "...") @@ -130,7 +130,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["data"], "...") self.assertEqual(result[0]["mime_type"], "image/png") @@ -154,7 +154,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["content"][0]["text"], "Here is an image and audio") self.assertEqual(result[0]["content"][1]["image_url"]["url"], "data:image/...") @@ -168,7 +168,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): {"role": "assistant", "content": "Hi there!"}, ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result, messages) @@ -187,7 +187,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - _ = context.get_messages(elide_large_values=True) + _ = context.get_messages(truncate_large_values=True) self.assertEqual( context.get_messages()[0]["content"][0]["image_url"]["url"], @@ -216,7 +216,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): } ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["content"][0]["image_url"]["url"], "data:image/...") self.assertEqual(result[0]["content"][1]["image_url"]["url"], "data:image/...") @@ -226,7 +226,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): ) def test_works_with_llm_specific_filter(self): - """elide_large_values works together with llm_specific_filter.""" + """truncate_large_values works together with llm_specific_filter.""" adapter = OpenAILLMAdapter() std_msg = { "role": "user", @@ -242,7 +242,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): ) context = LLMContext(messages=[std_msg, specific_msg]) - result = context.get_messages("openai", elide_large_values=True) + result = context.get_messages("openai", truncate_large_values=True) self.assertEqual(len(result), 2) self.assertEqual(result[0]["content"][0]["image_url"]["url"], "data:image/...") @@ -253,7 +253,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): {"role": "user", "content": "Just a string"}, ] context = LLMContext(messages=messages) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertEqual(result[0]["content"], "Just a string") @@ -265,7 +265,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): specific_msg = LLMSpecificMessage(llm="anthropic", message=inner) context = LLMContext(messages=[specific_msg]) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) self.assertIsInstance(result[0], LLMSpecificMessage) self.assertEqual(result[0].message["type"], "thought") @@ -278,7 +278,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): specific_msg = LLMSpecificMessage(llm="anthropic", message=inner) context = LLMContext(messages=[specific_msg]) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) msg = result[0].message self.assertEqual(msg["type"], "thought") @@ -298,7 +298,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): specific_msg = LLMSpecificMessage(llm="google", message=inner) context = LLMContext(messages=[specific_msg]) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) msg = result[0].message self.assertEqual(msg["type"], "thought_signature") @@ -311,7 +311,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): specific_msg = LLMSpecificMessage(llm="test", message=inner) context = LLMContext(messages=[specific_msg]) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) msg = result[0].message self.assertEqual(msg["items"][0], "short") @@ -323,7 +323,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): specific_msg = LLMSpecificMessage(llm="test", message=inner) context = LLMContext(messages=[specific_msg]) - result = context.get_messages(elide_large_values=True) + result = context.get_messages(truncate_large_values=True) msg = result[0].message self.assertEqual(msg["count"], 42) @@ -337,7 +337,7 @@ class TestGetMessagesElideLargeValues(unittest.TestCase): specific_msg = LLMSpecificMessage(llm="anthropic", message=inner) context = LLMContext(messages=[specific_msg]) - _ = context.get_messages(elide_large_values=True) + _ = context.get_messages(truncate_large_values=True) self.assertEqual(specific_msg.message["signature"], long_sig)