From 881e2fdd20a92a8d3ee6a7de34591f3e3c9e9e39 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 9 Sep 2026 12:52:00 +0200 Subject: [PATCH 1/4] chore: Remove get_start_span_function() --- sentry_sdk/ai/utils.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index 7b1ca9324b..bf02b55548 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -7,7 +7,7 @@ from sentry_sdk.ai.consts import DATA_URL_BASE64_REGEX if TYPE_CHECKING: - from typing import Any, Callable, Dict, List, Optional, Tuple, Union + from typing import Any, Dict, List, Optional, Tuple, Union from sentry_sdk.tracing import Span @@ -536,15 +536,6 @@ def normalize_message_roles(messages: "list[dict[str, Any]]") -> "list[dict[str, return normalized_messages -def get_start_span_function() -> "Callable[..., Any]": - current_span = sentry_sdk.get_current_span() - - transaction_exists = ( - current_span is not None and current_span.containing_transaction is not None - ) - return sentry_sdk.start_span if transaction_exists else sentry_sdk.start_transaction - - def _truncate_single_message_content_if_present( message: "Dict[str, Any]", max_chars: int ) -> "Dict[str, Any]": From d5fd313d4adbe01016ff19c06bdbaec0bdf2869b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 9 Sep 2026 13:03:52 +0200 Subject: [PATCH 2/4] chore: Remove truncation functions and helpers --- sentry_sdk/ai/utils.py | 267 +----- sentry_sdk/integrations/langchain.py | 42 +- sentry_sdk/integrations/langgraph.py | 28 +- sentry_sdk/integrations/litellm.py | 25 +- .../openai_agents/spans/invoke_agent.py | 15 +- .../integrations/openai_agents/utils.py | 12 +- .../integrations/anthropic/test_anthropic.py | 35 +- tests/test_ai_monitoring.py | 846 ------------------ 8 files changed, 22 insertions(+), 1248 deletions(-) diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index bf02b55548..7dc8d99c40 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -1,13 +1,9 @@ import inspect import json -from copy import deepcopy from typing import TYPE_CHECKING -from sentry_sdk._types import BLOB_DATA_SUBSTITUTE -from sentry_sdk.ai.consts import DATA_URL_BASE64_REGEX - if TYPE_CHECKING: - from typing import Any, Dict, List, Optional, Tuple, Union + from typing import Any, Dict, Optional, Tuple, Union from sentry_sdk.tracing import Span @@ -431,38 +427,6 @@ def transform_content_part( return None -def transform_message_content(content: "Any") -> "Any": - """ - Transform message content, handling both string content and list of content blocks. - - For list content, each item is transformed using transform_content_part(). - Items that cannot be transformed (return None) are kept as-is. - - Args: - content: Message content - can be a string, list of content blocks, or other - - Returns: - - String content: returned as-is - - List content: list with each transformable item converted to standardized format - - Other: returned as-is - """ - if isinstance(content, str): - return content - - if isinstance(content, (list, tuple)): - transformed = [] - for item in content: - if isinstance(item, dict): - result = transform_content_part(item) - # If transformation succeeded, use the result; otherwise keep original - transformed.append(result if result is not None else item) - else: - transformed.append(item) - return transformed - - return content - - def _normalize_data(data: "Any", unpack: bool = True) -> "Any": # convert pydantic data (e.g. OpenAI v1+) to json compatible format if hasattr(data, "model_dump"): @@ -536,235 +500,6 @@ def normalize_message_roles(messages: "list[dict[str, Any]]") -> "list[dict[str, return normalized_messages -def _truncate_single_message_content_if_present( - message: "Dict[str, Any]", max_chars: int -) -> "Dict[str, Any]": - """ - Truncate a message's content to at most `max_chars` characters and append an - ellipsis if truncation occurs. - """ - if not isinstance(message, dict) or "content" not in message: - return message - content = message["content"] - - if isinstance(content, str): - if len(content) <= max_chars: - return message - message["content"] = content[:max_chars] + "..." - return message - - if isinstance(content, list): - remaining = max_chars - for item in content: - if isinstance(item, dict) and "text" in item: - text = item["text"] - if isinstance(text, str): - if len(text) > remaining: - item["text"] = text[:remaining] + "..." - remaining = 0 - else: - remaining -= len(text) - return message - - return message - - -def _find_truncation_index(messages: "List[Dict[str, Any]]", max_bytes: int) -> int: - """ - Find the index of the first message that would exceed the max bytes limit. - Compute the individual message sizes, and return the index of the first message from the back - of the list that would exceed the max bytes limit. - """ - running_sum = 0 - for idx in range(len(messages) - 1, -1, -1): - size = len(json.dumps(messages[idx], separators=(",", ":")).encode("utf-8")) - running_sum += size - if running_sum > max_bytes: - return idx + 1 - - return 0 - - -def _is_image_type_with_blob_content(item: "Dict[str, Any]") -> bool: - """ - Some content blocks contain an image_url property with base64 content as its value. - This is used to identify those while not leading to unnecessary copying of data when the image URL does not contain base64 content. - """ - if item.get("type") != "image_url": - return False - - image_url_val = item.get("image_url") - image_url = ( - image_url_val.get("url", "") - if isinstance(image_url_val, dict) - else (image_url_val or "") - ) - data_url_match = DATA_URL_BASE64_REGEX.match(image_url) - - return bool(data_url_match) - - -def redact_blob_message_parts( - messages: "List[Dict[str, Any]]", -) -> "List[Dict[str, Any]]": - """ - Redact blob message parts from the messages by replacing blob content with "[Filtered]". - - This function creates a deep copy of messages that contain blob content to avoid - mutating the original message dictionaries. Messages without blob content are - returned as-is to minimize copying overhead. - - e.g: - { - "role": "user", - "content": [ - { - "text": "How many ponies do you see in the image?", - "type": "text" - }, - { - "type": "blob", - "modality": "image", - "mime_type": "image/jpeg", - "content": "data:image/jpeg;base64,..." - } - ] - } - becomes: - { - "role": "user", - "content": [ - { - "text": "How many ponies do you see in the image?", - "type": "text" - }, - { - "type": "blob", - "modality": "image", - "mime_type": "image/jpeg", - "content": "[Filtered]" - } - ] - } - """ - - # First pass: check if any message contains blob content - has_blobs = False - for message in messages: - if not isinstance(message, dict): - continue - content = message.get("content") - if isinstance(content, list): - for item in content: - if isinstance(item, dict) and ( - item.get("type") == "blob" or _is_image_type_with_blob_content(item) - ): - has_blobs = True - break - if has_blobs: - break - - # If no blobs found, return original messages to avoid unnecessary copying - if not has_blobs: - return messages - - # Deep copy messages to avoid mutating the original - messages_copy = deepcopy(messages) - - # Second pass: redact blob content in the copy - for message in messages_copy: - if not isinstance(message, dict): - continue - - content = message.get("content") - if isinstance(content, list): - for item in content: - if isinstance(item, dict): - if item.get("type") == "blob": - item["content"] = BLOB_DATA_SUBSTITUTE - elif _is_image_type_with_blob_content(item): - if isinstance(item["image_url"], dict): - item["image_url"]["url"] = BLOB_DATA_SUBSTITUTE - else: - item["image_url"] = BLOB_DATA_SUBSTITUTE - - return messages_copy - - -def truncate_messages_by_size( - messages: "List[Dict[str, Any]]", - max_bytes: int = MAX_GEN_AI_MESSAGE_BYTES, - max_single_message_chars: int = MAX_SINGLE_MESSAGE_CONTENT_CHARS, -) -> "Tuple[List[Dict[str, Any]], int]": - """ - Returns a truncated messages list, consisting of - - the last message, with its content truncated to `max_single_message_chars` characters, - if the last message's size exceeds `max_bytes` bytes; otherwise, - - the maximum number of messages, starting from the end of the `messages` list, whose total - serialized size does not exceed `max_bytes` bytes. - - In the single message case, the serialized message size may exceed `max_bytes`, because - truncation is based only on character count in that case. - """ - serialized_json = json.dumps(messages, separators=(",", ":")) - current_size = len(serialized_json.encode("utf-8")) - - if current_size <= max_bytes: - return messages, 0 - - truncation_index = _find_truncation_index(messages, max_bytes) - if truncation_index < len(messages): - truncated_messages = messages[truncation_index:] - else: - truncation_index = len(messages) - 1 - truncated_messages = messages[-1:] - - if len(truncated_messages) == 1: - truncated_messages[0] = _truncate_single_message_content_if_present( - deepcopy(truncated_messages[0]), max_chars=max_single_message_chars - ) - - return truncated_messages, truncation_index - - -def truncate_and_annotate_messages( - messages: "Optional[List[Dict[str, Any]]]", - span: "Any", - scope: "Any", - max_single_message_chars: int = MAX_SINGLE_MESSAGE_CONTENT_CHARS, -) -> "Optional[List[Dict[str, Any]]]": - if not messages: - return None - - messages = redact_blob_message_parts(messages) - - truncated_message = _truncate_single_message_content_if_present( - deepcopy(messages[-1]), max_chars=max_single_message_chars - ) - if len(messages) > 1: - scope._gen_ai_original_message_count[span.span_id] = len(messages) - - return [truncated_message] - - -def truncate_and_annotate_embedding_inputs( - messages: "Optional[List[Dict[str, Any]]]", - span: "Any", - scope: "Any", - max_bytes: int = MAX_GEN_AI_MESSAGE_BYTES, -) -> "Optional[List[Dict[str, Any]]]": - if not messages: - return None - - messages = redact_blob_message_parts(messages) - - truncated_messages, removed_count = truncate_messages_by_size(messages, max_bytes) - if removed_count > 0: - scope._gen_ai_original_message_count[span.span_id] = len(messages) - - return truncated_messages - - def set_conversation_id(conversation_id: str) -> None: """ Set the conversation_id in the scope. diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 29691c0e5a..53d933bbd9 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -11,7 +11,6 @@ normalize_message_roles, set_data_normalized, transform_content_part, - truncate_and_annotate_messages, ) from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version @@ -19,7 +18,6 @@ from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing_utils import ( _get_value, - has_span_streaming_enabled, ) from sentry_sdk.utils import ( capture_internal_exceptions, @@ -425,17 +423,11 @@ def on_llm_start( for prompt in prompts ] - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if not has_span_streaming_enabled(client.options) - else normalized_messages - ) - if messages_data is not None: + if normalized_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_messages, unpack=False, ) @@ -537,17 +529,11 @@ def on_chat_model_start( ) normalized_messages = normalize_message_roles(normalized_messages) - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if not has_span_streaming_enabled(client.options) - else normalized_messages - ) - if messages_data is not None: + if normalized_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_messages, unpack=False, ) @@ -1158,17 +1144,11 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if input is not None and record_inputs: normalized_messages = normalize_message_roles([input]) - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if not has_span_streaming_enabled(client.options) - else normalized_messages - ) - if messages_data is not None: + if normalized_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_messages, unpack=False, ) @@ -1220,17 +1200,11 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if input is not None and record_inputs: normalized_messages = normalize_message_roles([input]) - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if not has_span_streaming_enabled(client.options) - else normalized_messages - ) - if messages_data is not None: + if normalized_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_messages, unpack=False, ) diff --git a/sentry_sdk/integrations/langgraph.py b/sentry_sdk/integrations/langgraph.py index 172721b39c..f8d2ffc9db 100644 --- a/sentry_sdk/integrations/langgraph.py +++ b/sentry_sdk/integrations/langgraph.py @@ -5,7 +5,6 @@ from sentry_sdk.ai.utils import ( normalize_message_roles, set_data_normalized, - truncate_and_annotate_messages, ) from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version @@ -14,9 +13,6 @@ from sentry_sdk.integrations.langchain import LangchainIntegration from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan -from sentry_sdk.tracing_utils import ( - has_span_streaming_enabled, -) from sentry_sdk.utils import ( has_data_collection_enabled, package_version, @@ -164,19 +160,11 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if input_messages and _should_record_inputs(integration): normalized_input_messages = normalize_message_roles(input_messages) - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages( - normalized_input_messages, span, scope - ) - if not has_span_streaming_enabled(client.options) - else normalized_input_messages - ) - if messages_data is not None: + if normalized_input_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_input_messages, unpack=False, ) @@ -217,19 +205,11 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if input_messages and _should_record_inputs(integration): normalized_input_messages = normalize_message_roles(input_messages) - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages( - normalized_input_messages, span, scope - ) - if not has_span_streaming_enabled(client.options) - else normalized_input_messages - ) - if messages_data is not None: + if normalized_input_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_input_messages, unpack=False, ) diff --git a/sentry_sdk/integrations/litellm.py b/sentry_sdk/integrations/litellm.py index 2ef9433ac5..1d70f63095 100644 --- a/sentry_sdk/integrations/litellm.py +++ b/sentry_sdk/integrations/litellm.py @@ -7,15 +7,10 @@ from sentry_sdk.ai.utils import ( set_data_normalized, transform_openai_content_part, - truncate_and_annotate_embedding_inputs, - truncate_and_annotate_messages, ) from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.tracing_utils import ( - has_span_streaming_enabled, -) from sentry_sdk.utils import ( event_from_exception, has_data_collection_enabled, @@ -130,41 +125,29 @@ def _input_callback(kwargs: "Dict[str, Any]") -> None: # For embeddings, look for the 'input' parameter embedding_input = kwargs.get("input") if embedding_input: - scope = sentry_sdk.get_current_scope() # Normalize to list format input_list = ( embedding_input if isinstance(embedding_input, list) else [embedding_input] ) - messages_data = ( - truncate_and_annotate_embedding_inputs(input_list, span, scope) - if not has_span_streaming_enabled(client.options) - else input_list - ) - if messages_data is not None: + if input_list is not None: set_data_normalized( span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, - messages_data, + input_list, unpack=False, ) else: # For chat, look for the 'messages' parameter messages = kwargs.get("messages", []) if messages: - scope = sentry_sdk.get_current_scope() messages = _convert_message_parts(messages) - messages_data = ( - truncate_and_annotate_messages(messages, span, scope) - if not has_span_streaming_enabled(client.options) - else messages - ) - if messages_data is not None: + if messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + messages, unpack=False, ) diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index a7dc68e571..1383a864ff 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -4,14 +4,10 @@ from sentry_sdk.ai.utils import ( normalize_message_roles, set_data_normalized, - truncate_and_annotate_messages, ) from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan -from sentry_sdk.tracing_utils import ( - has_span_streaming_enabled, -) from sentry_sdk.utils import has_data_collection_enabled, safe_serialize from ..consts import SPAN_ORIGIN @@ -75,18 +71,11 @@ def invoke_agent_span( if len(messages) > 0: normalized_messages = normalize_message_roles(messages) - client = sentry_sdk.get_client() - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if not has_span_streaming_enabled(client.options) - else normalized_messages - ) - if messages_data is not None: + if normalized_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_messages, unpack=False, ) diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 2969364777..a01b6e28db 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -12,13 +12,11 @@ normalize_message_role, normalize_message_roles, set_data_normalized, - truncate_and_annotate_messages, ) from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( event_from_exception, has_data_collection_enabled, @@ -176,17 +174,11 @@ def _set_input_data( ) normalized_messages = normalize_message_roles(request_messages) - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if not has_span_streaming_enabled(client.options) - else normalized_messages - ) - if messages_data is not None: + if normalized_messages is not None: set_data_normalized( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, + normalized_messages, unpack=False, ) diff --git a/tests/integrations/anthropic/test_anthropic.py b/tests/integrations/anthropic/test_anthropic.py index 5d4c640c8c..eef7771359 100644 --- a/tests/integrations/anthropic/test_anthropic.py +++ b/tests/integrations/anthropic/test_anthropic.py @@ -57,7 +57,7 @@ async def __call__(self, *args, **kwargs): except ImportError: from anthropic.types.content_block import ContentBlock as TextBlock -from sentry_sdk.ai.utils import transform_content_part, transform_message_content +from sentry_sdk.ai.utils import transform_content_part from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.anthropic import ( AnthropicIntegration, @@ -4624,39 +4624,6 @@ def test_transform_content_part_text_block(): assert result is None -def test_transform_message_content_string(): - """Test that string content is returned as-is.""" - result = transform_message_content("Hello, world!") - assert result == "Hello, world!" - - -def test_transform_message_content_list_anthropic(): - """Test that list content with Anthropic format is transformed correctly.""" - content = [ - {"type": "text", "text": "Hello!"}, - { - "type": "image", - "source": { - "type": "base64", - "media_type": "image/png", - "data": "base64data...", - }, - }, - ] - - result = transform_message_content(content) - - assert len(result) == 2 - # Text block stays as-is (transform returns None, keeps original) - assert result[0] == {"type": "text", "text": "Hello!"} - assert result[1] == { - "type": "blob", - "modality": "image", - "mime_type": "image/png", - "content": "base64data...", - } - - # Integration tests for binary data in messages diff --git a/tests/test_ai_monitoring.py b/tests/test_ai_monitoring.py index 22a88f2214..7094ca82b0 100644 --- a/tests/test_ai_monitoring.py +++ b/tests/test_ai_monitoring.py @@ -1,26 +1,14 @@ import pytest -from sentry_sdk._types import ( - BLOB_DATA_SUBSTITUTE, - AnnotatedValue, -) from sentry_sdk.ai.utils import ( - MAX_GEN_AI_MESSAGE_BYTES, - MAX_SINGLE_MESSAGE_CONTENT_CHARS, - _find_truncation_index, get_modality_from_mime_type, parse_data_uri, - redact_blob_message_parts, transform_anthropic_content_part, transform_content_part, transform_generic_content_part, transform_google_content_part, - transform_message_content, transform_openai_content_part, - truncate_and_annotate_messages, - truncate_messages_by_size, ) -from sentry_sdk.utils import safe_serialize @pytest.fixture @@ -56,731 +44,6 @@ def large_messages(): ] -class TestTruncateMessagesBySize: - def test_no_truncation_needed(self, sample_messages): - """Test that messages under the limit are not truncated""" - result, truncation_index = truncate_messages_by_size( - sample_messages, max_bytes=MAX_GEN_AI_MESSAGE_BYTES - ) - assert len(result) == len(sample_messages) - assert result == sample_messages - assert truncation_index == 0 - - def test_truncation_removes_oldest_first(self, large_messages): - """Test that oldest messages are removed first during truncation""" - small_limit = 3000 - result, truncation_index = truncate_messages_by_size( - large_messages, max_bytes=small_limit - ) - assert len(result) < len(large_messages) - - assert result[-1] == large_messages[-1] - assert truncation_index == len(large_messages) - len(result) - - def test_empty_messages_list(self): - """Test handling of empty messages list""" - result, truncation_index = truncate_messages_by_size( - [], max_bytes=MAX_GEN_AI_MESSAGE_BYTES // 500 - ) - assert result == [] - assert truncation_index == 0 - - def test_find_truncation_index( - self, - ): - """Test that the truncation index is found correctly""" - # when represented in JSON, these are each 7 bytes long - messages = ["A" * 5, "B" * 5, "C" * 5, "D" * 5, "E" * 5] - truncation_index = _find_truncation_index(messages, 20) - assert truncation_index == 3 - assert messages[truncation_index:] == ["D" * 5, "E" * 5] - - messages = ["A" * 5, "B" * 5, "C" * 5, "D" * 5, "E" * 5] - truncation_index = _find_truncation_index(messages, 40) - assert truncation_index == 0 - assert messages[truncation_index:] == [ - "A" * 5, - "B" * 5, - "C" * 5, - "D" * 5, - "E" * 5, - ] - - def test_progressive_truncation(self, large_messages): - """Test that truncation works progressively with different limits""" - limits = [ - MAX_GEN_AI_MESSAGE_BYTES // 5, - MAX_GEN_AI_MESSAGE_BYTES // 10, - MAX_GEN_AI_MESSAGE_BYTES // 25, - MAX_GEN_AI_MESSAGE_BYTES // 100, - MAX_GEN_AI_MESSAGE_BYTES // 500, - ] - prev_count = len(large_messages) - - for limit in limits: - result = truncate_messages_by_size(large_messages, max_bytes=limit) - current_count = len(result) - - assert current_count <= prev_count - assert current_count >= 1 - prev_count = current_count - - def test_single_message_truncation(self): - large_content = "This is a very long message. " * 10_000 - - messages = [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": large_content}, - ] - - result, truncation_index = truncate_messages_by_size( - messages, max_single_message_chars=MAX_SINGLE_MESSAGE_CONTENT_CHARS - ) - - assert len(result) == 1 - assert ( - len(result[0]["content"].rstrip("...")) <= MAX_SINGLE_MESSAGE_CONTENT_CHARS - ) - - # If the last message is too large, the system message is not present - system_msgs = [m for m in result if m.get("role") == "system"] - assert len(system_msgs) == 0 - - # Confirm the user message is truncated with '...' - user_msgs = [m for m in result if m.get("role") == "user"] - assert len(user_msgs) == 1 - assert user_msgs[0]["content"].endswith("...") - assert len(user_msgs[0]["content"]) < len(large_content) - - def test_single_message_truncation_list_content_exceeds_limit(self): - """Test that list-based content (e.g. pydantic-ai multimodal format) is truncated.""" - large_text = "A" * 200_000 - - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": large_text}, - ], - }, - ] - - result, _ = truncate_messages_by_size(messages) - - text_part = result[0]["content"][0] - assert text_part["text"].endswith("...") - assert len(text_part["text"]) == MAX_SINGLE_MESSAGE_CONTENT_CHARS + 3 - - def test_single_message_truncation_list_content_under_limit(self): - """Test that small text parts are preserved when non-text parts push size over byte limit.""" - short_text = "Hello world" - large_data_url = "data:image/png;base64," + "A" * 200_000 - - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": short_text}, - {"type": "image_url", "image_url": {"url": large_data_url}}, - ], - }, - ] - - result, _ = truncate_messages_by_size(messages) - - text_part = result[0]["content"][0] - assert text_part["text"] == short_text - - def test_single_message_truncation_list_content_mixed_parts(self): - """Test truncation with mixed content types (text + non-text parts).""" - max_chars = 50 - large_data_url = "data:image/png;base64," + "X" * 200_000 - - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": "A" * 30}, - {"type": "image_url", "image_url": {"url": large_data_url}}, - {"type": "text", "text": "B" * 30}, - ], - }, - ] - - result, _ = truncate_messages_by_size( - messages, max_single_message_chars=max_chars - ) - - parts = result[0]["content"] - # First text part uses 30 chars of the 50 budget - assert parts[0]["text"] == "A" * 30 - # Image part is unchanged - assert parts[1]["type"] == "image_url" - # Second text part is truncated to remaining 20 chars - assert parts[2]["text"] == "B" * 20 + "..." - - def test_single_message_truncation_list_content_multiple_text_parts(self): - """Test that budget is distributed across multiple text parts.""" - max_chars = 10 - # Two large text parts that together exceed 128KB byte limit - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": "A" * 100_000}, - {"type": "text", "text": "B" * 100_000}, - ], - }, - ] - - result, _ = truncate_messages_by_size( - messages, max_single_message_chars=max_chars - ) - - parts = result[0]["content"] - # First part is truncated to the full budget - assert parts[0]["text"] == "A" * 10 + "..." - # Second part gets truncated to 0 chars + ellipsis - assert parts[1]["text"] == "..." - - @pytest.mark.parametrize("content", [None, 42, True]) - def test_single_message_truncation_non_str_non_list_content(self, content): - messages = [{"role": "user", "content": content}] - - result, _ = truncate_messages_by_size(messages) - - assert result[0]["content"] is content - - -class TestTruncateAndAnnotateMessages: - def test_only_keeps_last_message(self, sample_messages): - class MockSpan: - def __init__(self): - self.span_id = "test_span_id" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - span = MockSpan() - scope = MockScope() - result = truncate_and_annotate_messages(sample_messages, span, scope) - - assert isinstance(result, list) - assert not isinstance(result, AnnotatedValue) - assert len(result) == 1 - assert result[0] == sample_messages[-1] - - def test_truncation_sets_metadata_on_scope(self, large_messages): - class MockSpan: - def __init__(self): - self.span_id = "test_span_id" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - small_limit = 3000 - span = MockSpan() - scope = MockScope() - original_count = len(large_messages) - result = truncate_and_annotate_messages( - large_messages, span, scope, max_single_message_chars=small_limit - ) - - assert isinstance(result, list) - assert not isinstance(result, AnnotatedValue) - assert len(result) < len(large_messages) - assert scope._gen_ai_original_message_count[span.span_id] == original_count - - def test_scope_tracks_original_message_count(self, large_messages): - class MockSpan: - def __init__(self): - self.span_id = "test_span_id" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - small_limit = 3000 - original_count = len(large_messages) - span = MockSpan() - scope = MockScope() - - result = truncate_and_annotate_messages( - large_messages, span, scope, max_single_message_chars=small_limit - ) - - assert scope._gen_ai_original_message_count[span.span_id] == original_count - assert len(result) == 1 - - def test_empty_messages_returns_none(self): - class MockSpan: - def __init__(self): - self.span_id = "test_span_id" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - span = MockSpan() - scope = MockScope() - result = truncate_and_annotate_messages([], span, scope) - assert result is None - - result = truncate_and_annotate_messages(None, span, scope) - assert result is None - - def test_truncated_messages_newest_first(self, large_messages): - class MockSpan: - def __init__(self): - self.span_id = "test_span_id" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - small_limit = 3000 - span = MockSpan() - scope = MockScope() - result = truncate_and_annotate_messages( - large_messages, span, scope, max_single_message_chars=small_limit - ) - - assert isinstance(result, list) - assert result[0] == large_messages[-len(result)] - - def test_preserves_original_messages_with_blobs(self): - """Test that truncate_and_annotate_messages doesn't mutate the original messages""" - - class MockSpan: - def __init__(self): - self.span_id = "test_span_id" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - messages = [ - { - "role": "user", - "content": [ - {"text": "What's in this image?", "type": "text"}, - { - "type": "blob", - "modality": "image", - "content": "data:image/jpeg;base64,original_content", - }, - ], - } - ] - - original_blob_content = messages[0]["content"][1]["content"] - - span = MockSpan() - scope = MockScope() - - # This should NOT mutate the original messages - result = truncate_and_annotate_messages(messages, span, scope) - - # Verify original is unchanged - assert messages[0]["content"][1]["content"] == original_blob_content - - # Verify result has redacted content - assert result[0]["content"][1]["content"] == BLOB_DATA_SUBSTITUTE - - -class TestClientAnnotation: - def test_client_wraps_truncated_messages_in_annotated_value(self, large_messages): - """Test that client.py properly wraps truncated messages in AnnotatedValue using scope data""" - from sentry_sdk._types import AnnotatedValue - from sentry_sdk.consts import SPANDATA - - class MockSpan: - def __init__(self): - self.span_id = "test_span_123" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - small_limit = 3000 - span = MockSpan() - scope = MockScope() - original_count = len(large_messages) - - # Simulate what integrations do - truncated_messages = truncate_and_annotate_messages( - large_messages, span, scope, max_single_message_chars=small_limit - ) - span.set_data(SPANDATA.GEN_AI_REQUEST_MESSAGES, truncated_messages) - - # Verify metadata was set on scope - assert span.span_id in scope._gen_ai_original_message_count - assert scope._gen_ai_original_message_count[span.span_id] > 0 - - # Simulate what client.py does - event = {"spans": [{"span_id": span.span_id, "data": span.data.copy()}]} - - # Mimic client.py logic - using scope to get the original length - for event_span in event["spans"]: - span_id = event_span.get("span_id") - span_data = event_span.get("data", {}) - if ( - span_id - and span_id in scope._gen_ai_original_message_count - and SPANDATA.GEN_AI_REQUEST_MESSAGES in span_data - ): - messages = span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES] - n_original_count = scope._gen_ai_original_message_count[span_id] - - span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES] = AnnotatedValue( - safe_serialize(messages), - {"len": n_original_count}, - ) - - # Verify the annotation happened - messages_value = event["spans"][0]["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES] - assert isinstance(messages_value, AnnotatedValue) - assert messages_value.metadata["len"] == original_count - assert isinstance(messages_value.value, str) - - def test_annotated_value_shows_correct_original_length(self, large_messages): - """Test that the annotated value correctly shows the original message count before truncation""" - from sentry_sdk.consts import SPANDATA - - class MockSpan: - def __init__(self): - self.span_id = "test_span_456" - self.data = {} - - def set_data(self, key, value): - self.data[key] = value - - class MockScope: - def __init__(self): - self._gen_ai_original_message_count = {} - - small_limit = 3000 - span = MockSpan() - scope = MockScope() - original_message_count = len(large_messages) - - truncated_messages = truncate_and_annotate_messages( - large_messages, span, scope, max_single_message_chars=small_limit - ) - - assert len(truncated_messages) < original_message_count - - assert span.span_id in scope._gen_ai_original_message_count - stored_original_length = scope._gen_ai_original_message_count[span.span_id] - assert stored_original_length == original_message_count - - event = { - "spans": [ - { - "span_id": span.span_id, - "data": {SPANDATA.GEN_AI_REQUEST_MESSAGES: truncated_messages}, - } - ] - } - - for event_span in event["spans"]: - span_id = event_span.get("span_id") - span_data = event_span.get("data", {}) - if ( - span_id - and span_id in scope._gen_ai_original_message_count - and SPANDATA.GEN_AI_REQUEST_MESSAGES in span_data - ): - span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES] = AnnotatedValue( - span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES], - {"len": scope._gen_ai_original_message_count[span_id]}, - ) - - messages_value = event["spans"][0]["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES] - assert isinstance(messages_value, AnnotatedValue) - assert messages_value.metadata["len"] == stored_original_length - assert len(messages_value.value) == len(truncated_messages) - - -class TestRedactBlobMessageParts: - def test_redacts_single_blob_content(self): - """Test that blob content is redacted without mutating original messages""" - messages = [ - { - "role": "user", - "content": [ - { - "text": "How many ponies do you see in the image?", - "type": "text", - }, - { - "type": "blob", - "modality": "image", - "mime_type": "image/jpeg", - "content": "data:image/jpeg;base64,/9j/4AAQSkZJRg==", - }, - ], - } - ] - - # Save original blob content for comparison - original_blob_content = messages[0]["content"][1]["content"] - - result = redact_blob_message_parts(messages) - - # Original messages should be UNCHANGED - assert messages[0]["content"][1]["content"] == original_blob_content - - # Result should have redacted content - assert ( - result[0]["content"][0]["text"] - == "How many ponies do you see in the image?" - ) - assert result[0]["content"][0]["type"] == "text" - assert result[0]["content"][1]["type"] == "blob" - assert result[0]["content"][1]["modality"] == "image" - assert result[0]["content"][1]["mime_type"] == "image/jpeg" - assert result[0]["content"][1]["content"] == BLOB_DATA_SUBSTITUTE - - def test_redacts_multiple_blob_parts(self): - """Test that multiple blob parts are redacted without mutation""" - messages = [ - { - "role": "user", - "content": [ - {"text": "Compare these images", "type": "text"}, - { - "type": "blob", - "modality": "image", - "mime_type": "image/jpeg", - "content": "data:image/jpeg;base64,first_image", - }, - { - "type": "blob", - "modality": "image", - "mime_type": "image/png", - "content": "data:image/png;base64,second_image", - }, - ], - } - ] - - original_first = messages[0]["content"][1]["content"] - original_second = messages[0]["content"][2]["content"] - - result = redact_blob_message_parts(messages) - - # Original should be unchanged - assert messages[0]["content"][1]["content"] == original_first - assert messages[0]["content"][2]["content"] == original_second - - # Result should be redacted - assert result[0]["content"][0]["text"] == "Compare these images" - assert result[0]["content"][1]["content"] == BLOB_DATA_SUBSTITUTE - assert result[0]["content"][2]["content"] == BLOB_DATA_SUBSTITUTE - - def test_redacts_blobs_in_multiple_messages(self): - """Test that blob parts are redacted across multiple messages without mutation""" - messages = [ - { - "role": "user", - "content": [ - {"text": "First message", "type": "text"}, - { - "type": "blob", - "modality": "image", - "content": "data:image/jpeg;base64,first", - }, - ], - }, - { - "role": "assistant", - "content": "I see the image.", - }, - { - "role": "user", - "content": [ - {"text": "Second message", "type": "text"}, - { - "type": "blob", - "modality": "image", - "content": "data:image/jpeg;base64,second", - }, - ], - }, - ] - - original_first = messages[0]["content"][1]["content"] - original_second = messages[2]["content"][1]["content"] - - result = redact_blob_message_parts(messages) - - # Original should be unchanged - assert messages[0]["content"][1]["content"] == original_first - assert messages[2]["content"][1]["content"] == original_second - - # Result should be redacted - assert result[0]["content"][1]["content"] == BLOB_DATA_SUBSTITUTE - assert result[1]["content"] == "I see the image." # Unchanged - assert result[2]["content"][1]["content"] == BLOB_DATA_SUBSTITUTE - - def test_redacts_single_blob_within_image_url_content(self): - messages = [ - { - "role": "user", - "content": [ - { - "text": "How many ponies do you see in the image?", - "type": "text", - }, - { - "type": "image_url", - "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRg=="}, - }, - ], - } - ] - - original_blob_content = messages[0]["content"][1] - - result = redact_blob_message_parts(messages) - - assert messages[0]["content"][1] == original_blob_content - - assert ( - result[0]["content"][0]["text"] - == "How many ponies do you see in the image?" - ) - assert result[0]["content"][0]["type"] == "text" - assert result[0]["content"][1]["type"] == "image_url" - assert result[0]["content"][1]["image_url"]["url"] == BLOB_DATA_SUBSTITUTE - - def test_does_not_redact_image_url_content_with_non_blobs(self): - messages = [ - { - "role": "user", - "content": [ - { - "text": "How many ponies do you see in the image?", - "type": "text", - }, - { - "type": "image_url", - "image_url": {"url": "https://example.com/image.jpg"}, - }, - ], - } - ] - - original_blob_content = messages[0]["content"][1] - - result = redact_blob_message_parts(messages) - - assert messages[0]["content"][1] == original_blob_content - - assert ( - result[0]["content"][0]["text"] - == "How many ponies do you see in the image?" - ) - assert result[0]["content"][0]["type"] == "text" - assert result[0]["content"][1]["type"] == "image_url" - assert ( - result[0]["content"][1]["image_url"]["url"] - == "https://example.com/image.jpg" - ) - - def test_no_blobs_returns_original_list(self): - """Test that messages without blobs are returned as-is (performance optimization)""" - messages = [ - {"role": "user", "content": "Simple text message"}, - {"role": "assistant", "content": "Simple response"}, - ] - - result = redact_blob_message_parts(messages) - - # Should return the same list object when no blobs present - assert result is messages - - def test_handles_non_dict_messages(self): - """Test that non-dict messages are handled gracefully""" - messages = [ - "string message", - {"role": "user", "content": "text"}, - None, - 123, - ] - - result = redact_blob_message_parts(messages) - - # Should return same list since no blobs - assert result is messages - - def test_handles_non_dict_content_items(self): - """Test that non-dict content items in arrays are handled""" - messages = [ - { - "role": "user", - "content": [ - "string item", - {"text": "text item", "type": "text"}, - None, - ], - } - ] - - result = redact_blob_message_parts(messages) - - # Should return same list since no blobs - assert result is messages - - def test_redact_blob_message_parts_image_url_string_shorthand(self): - """image_url as a plain string (OpenAI shorthand) must not raise AttributeError""" - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": "What is in this image?"}, - { - "type": "image_url", - "image_url": "data:image/jpeg;base64,/9j/abc123==", - }, - ], - } - ] - result = redact_blob_message_parts(messages) - assert result[0]["content"][1]["image_url"] == "[Blob substitute]" - - class TestParseDataUri: def test_parses_base64_image_data_uri(self): """Test parsing a standard base64-encoded image data URI""" @@ -1657,112 +920,3 @@ def test_google_file_data_not_dict_returns_none(self): """Test that Google file_data with non-dict value returns None""" content_part = {"file_data": "not_a_dict"} assert transform_content_part(content_part) is None - - -class TestTransformMessageContent: - def test_string_content_returned_as_is(self): - """Test that string content is returned unchanged""" - content = "Hello, world!" - result = transform_message_content(content) - - assert result == "Hello, world!" - - def test_list_with_transformable_items(self): - """Test transforming a list with transformable content parts""" - content = [ - {"type": "text", "text": "What's in this image?"}, - { - "type": "image_url", - "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ"}, - }, - ] - result = transform_message_content(content) - - assert len(result) == 2 - # Text block should be unchanged (transform returns None, so original kept) - assert result[0] == {"type": "text", "text": "What's in this image?"} - # Image should be transformed - assert result[1] == { - "type": "blob", - "modality": "image", - "mime_type": "image/jpeg", - "content": "/9j/4AAQ", - } - - def test_list_with_non_dict_items(self): - """Test that non-dict items in list are kept as-is""" - content = ["text string", 123, {"type": "text", "text": "hi"}] - result = transform_message_content(content) - - assert result == ["text string", 123, {"type": "text", "text": "hi"}] - - def test_tuple_content(self): - """Test that tuple content is also handled""" - content = ( - {"type": "text", "text": "Hello"}, - { - "type": "image_url", - "image_url": {"url": "https://example.com/img.jpg"}, - }, - ) - result = transform_message_content(content) - - assert len(result) == 2 - assert result[0] == {"type": "text", "text": "Hello"} - assert result[1] == { - "type": "uri", - "modality": "image", - "mime_type": "", - "uri": "https://example.com/img.jpg", - } - - def test_other_types_returned_as_is(self): - """Test that other types are returned unchanged""" - assert transform_message_content(123) == 123 - assert transform_message_content(None) is None - assert transform_message_content({"key": "value"}) == {"key": "value"} - - def test_mixed_content_types(self): - """Test transforming mixed content with multiple formats""" - content = [ - {"type": "text", "text": "Look at these:"}, - { - "type": "image_url", - "image_url": {"url": "data:image/png;base64,iVBORw0"}, - }, - { - "type": "image", - "source": { - "type": "base64", - "media_type": "image/jpeg", - "data": "/9j/4AAQ", - }, - }, - {"inline_data": {"mime_type": "audio/wav", "data": "UklGRiQA"}}, - ] - result = transform_message_content(content) - - assert len(result) == 4 - assert result[0] == {"type": "text", "text": "Look at these:"} - assert result[1] == { - "type": "blob", - "modality": "image", - "mime_type": "image/png", - "content": "iVBORw0", - } - assert result[2] == { - "type": "blob", - "modality": "image", - "mime_type": "image/jpeg", - "content": "/9j/4AAQ", - } - assert result[3] == { - "type": "blob", - "modality": "audio", - "mime_type": "audio/wav", - "content": "UklGRiQA", - } - - def test_empty_list(self): - """Test that empty list is returned as empty list""" - assert transform_message_content([]) == [] From 25b9fc9e0bfcfec4331e7e5079a38c259f3f159f Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 9 Sep 2026 13:09:38 +0200 Subject: [PATCH 3/4] remove test_openai_agents_message_truncation --- .../openai_agents/test_openai_agents.py | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 9f64d18d1c..96ea5bb576 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -3180,9 +3180,6 @@ def test_openai_agents_message_role_mapping(sentry_init, test_message, expected_ get_response_kwargs = {"input": [test_message]} - from sentry_sdk import start_span - from sentry_sdk.integrations.openai_agents.utils import _set_input_data - with start_span(op="test") as span: _set_input_data(span, get_response_kwargs) @@ -3935,49 +3932,6 @@ def calculator(a: int, b: int) -> int: ) -def test_openai_agents_message_truncation( - sentry_init, -): - """Test that large messages are truncated properly in OpenAI Agents integration.""" - - large_content = ( - "This is a very long message that will exceed our size limits. " * 1000 - ) - - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - ) - - test_messages = [ - {"role": "user", "content": large_content}, - {"role": "assistant", "content": large_content}, - {"role": "user", "content": "small message 4"}, - {"role": "assistant", "content": "small message 5"}, - ] - - get_response_kwargs = {"input": test_messages} - - with start_span(op="gen_ai.chat") as span: - scope = sentry_sdk.get_current_scope() - _set_input_data(span, get_response_kwargs) - if hasattr(scope, "_gen_ai_original_message_count"): - truncated_count = scope._gen_ai_original_message_count.get(span.span_id) - assert truncated_count == 4, ( - f"Expected 4 original messages, got {truncated_count}" - ) - - assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span._data - messages_data = span._data[SPANDATA.GEN_AI_REQUEST_MESSAGES] - assert isinstance(messages_data, str) - - parsed_messages = json.loads(messages_data) - assert isinstance(parsed_messages, list) - assert len(parsed_messages) == 1 - assert "small message 5" in str(parsed_messages[0]) - - @pytest.mark.asyncio async def test_streaming_span_update_captures_response_data( sentry_init, test_agent, mock_usage From d6c5a555b42f84c1f3d6aec82ace4185f0ca65d3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 9 Sep 2026 13:38:04 +0200 Subject: [PATCH 4/4] remove unused fixtures and constants --- sentry_sdk/ai/utils.py | 4 ---- tests/test_ai_monitoring.py | 33 --------------------------------- 2 files changed, 37 deletions(-) diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index 7dc8d99c40..a158d1e9a8 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -11,10 +11,6 @@ from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import logger -MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB -# Maximum characters when only a single message is left after bytes truncation -MAX_SINGLE_MESSAGE_CONTENT_CHARS = 10_000 - class GEN_AI_ALLOWED_MESSAGE_ROLES: SYSTEM = "system" diff --git a/tests/test_ai_monitoring.py b/tests/test_ai_monitoring.py index 7094ca82b0..e6ea0eef6e 100644 --- a/tests/test_ai_monitoring.py +++ b/tests/test_ai_monitoring.py @@ -11,39 +11,6 @@ ) -@pytest.fixture -def sample_messages(): - """Sample messages similar to what gen_ai integrations would use""" - return [ - {"role": "system", "content": "You are a helpful assistant."}, - { - "role": "user", - "content": "What is the difference between a list and a tuple in Python?", - }, - { - "role": "assistant", - "content": "Lists are mutable and use [], tuples are immutable and use ().", - }, - {"role": "user", "content": "Can you give me some examples?"}, - { - "role": "assistant", - "content": "Sure! Here are examples:\n\n```python\n# List\nmy_list = [1, 2, 3]\nmy_list.append(4)\n\n# Tuple\nmy_tuple = (1, 2, 3)\n# my_tuple.append(4) would error\n```", - }, - ] - - -@pytest.fixture -def large_messages(): - """Messages that will definitely exceed size limits""" - large_content = "This is a very long message. " * 100 - return [ - {"role": "system", "content": large_content}, - {"role": "user", "content": large_content}, - {"role": "assistant", "content": large_content}, - {"role": "user", "content": large_content}, - ] - - class TestParseDataUri: def test_parses_base64_image_data_uri(self): """Test parsing a standard base64-encoded image data URI"""