Skip to content

Commit 4572922

Browse files
ref(openai): Revert input truncation
1 parent cd49c4d commit 4572922

4 files changed

Lines changed: 17 additions & 652 deletions

File tree

sentry_sdk/ai/utils.py

Lines changed: 1 addition & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import inspect
22
import json
3-
from copy import deepcopy
43
from typing import TYPE_CHECKING
54

65

76
if TYPE_CHECKING:
8-
from typing import Any, Callable, Dict, List, Optional, Tuple
7+
from typing import Any, Callable, Dict, Tuple
98

109
from sentry_sdk.tracing import Span
1110

@@ -14,10 +13,6 @@
1413
from sentry_sdk.traces import StreamedSpan
1514
from sentry_sdk.tracing_utils import has_span_streaming_enabled
1615

17-
MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB
18-
# Maximum characters when only a single message is left after bytes truncation
19-
MAX_SINGLE_MESSAGE_CONTENT_CHARS = 10_000
20-
2116

2217
class GEN_AI_ALLOWED_MESSAGE_ROLES:
2318
SYSTEM = "system"
@@ -180,92 +175,6 @@ def _truncate_single_message_content_if_present(
180175
return message
181176

182177

183-
def _find_truncation_index(messages: "List[Dict[str, Any]]", max_bytes: int) -> int:
184-
"""
185-
Find the index of the first message that would exceed the max bytes limit.
186-
Compute the individual message sizes, and return the index of the first message from the back
187-
of the list that would exceed the max bytes limit.
188-
"""
189-
running_sum = 0
190-
for idx in range(len(messages) - 1, -1, -1):
191-
size = len(json.dumps(messages[idx], separators=(",", ":")).encode("utf-8"))
192-
running_sum += size
193-
if running_sum > max_bytes:
194-
return idx + 1
195-
196-
return 0
197-
198-
199-
def truncate_messages_by_size(
200-
messages: "List[Dict[str, Any]]",
201-
max_bytes: int = MAX_GEN_AI_MESSAGE_BYTES,
202-
max_single_message_chars: int = MAX_SINGLE_MESSAGE_CONTENT_CHARS,
203-
) -> "Tuple[List[Dict[str, Any]], int]":
204-
"""
205-
Returns a truncated messages list, consisting of
206-
- the last message, with its content truncated to `max_single_message_chars` characters,
207-
if the last message's size exceeds `max_bytes` bytes; otherwise,
208-
- the maximum number of messages, starting from the end of the `messages` list, whose total
209-
serialized size does not exceed `max_bytes` bytes.
210-
211-
In the single message case, the serialized message size may exceed `max_bytes`, because
212-
truncation is based only on character count in that case.
213-
"""
214-
serialized_json = json.dumps(messages, separators=(",", ":"))
215-
current_size = len(serialized_json.encode("utf-8"))
216-
217-
if current_size <= max_bytes:
218-
return messages, 0
219-
220-
truncation_index = _find_truncation_index(messages, max_bytes)
221-
if truncation_index < len(messages):
222-
truncated_messages = messages[truncation_index:]
223-
else:
224-
truncation_index = len(messages) - 1
225-
truncated_messages = messages[-1:]
226-
227-
if len(truncated_messages) == 1:
228-
truncated_messages[0] = _truncate_single_message_content_if_present(
229-
deepcopy(truncated_messages[0]), max_chars=max_single_message_chars
230-
)
231-
232-
return truncated_messages, truncation_index
233-
234-
235-
def truncate_and_annotate_messages(
236-
messages: "Optional[List[Dict[str, Any]]]",
237-
span: "Any",
238-
scope: "Any",
239-
max_single_message_chars: int = MAX_SINGLE_MESSAGE_CONTENT_CHARS,
240-
) -> "Optional[List[Dict[str, Any]]]":
241-
if not messages:
242-
return None
243-
244-
truncated_message = _truncate_single_message_content_if_present(
245-
deepcopy(messages[-1]), max_chars=max_single_message_chars
246-
)
247-
if len(messages) > 1:
248-
scope._gen_ai_original_message_count[span.span_id] = len(messages)
249-
250-
return [truncated_message]
251-
252-
253-
def truncate_and_annotate_embedding_inputs(
254-
messages: "Optional[List[Dict[str, Any]]]",
255-
span: "Any",
256-
scope: "Any",
257-
max_bytes: int = MAX_GEN_AI_MESSAGE_BYTES,
258-
) -> "Optional[List[Dict[str, Any]]]":
259-
if not messages:
260-
return None
261-
262-
truncated_messages, removed_count = truncate_messages_by_size(messages, max_bytes)
263-
if removed_count > 0:
264-
scope._gen_ai_original_message_count[span.span_id] = len(messages)
265-
266-
return truncated_messages
267-
268-
269178
def set_conversation_id(conversation_id: str) -> None:
270179
"""
271180
Set the conversation_id in the scope.

sentry_sdk/integrations/openai.py

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
from sentry_sdk.ai.utils import (
1111
set_data_normalized,
1212
normalize_message_roles,
13-
truncate_and_annotate_messages,
14-
truncate_and_annotate_embedding_inputs,
1513
)
1614
from sentry_sdk.ai._openai_completions_api import (
1715
_is_system_instruction as _is_system_instruction_completions,
@@ -397,12 +395,9 @@ def _set_responses_api_input_data(
397395

398396
if isinstance(messages, str):
399397
normalized_messages = normalize_message_roles([messages]) # type: ignore
400-
scope = sentry_sdk.get_current_scope()
401-
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
402-
if messages_data is not None:
403-
set_data_normalized(
404-
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
405-
)
398+
set_data_normalized(
399+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, normalized_messages, unpack=False
400+
)
406401

407402
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
408403
return
@@ -412,12 +407,9 @@ def _set_responses_api_input_data(
412407
]
413408
if len(non_system_messages) > 0:
414409
normalized_messages = normalize_message_roles(non_system_messages)
415-
scope = sentry_sdk.get_current_scope()
416-
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
417-
if messages_data is not None:
418-
set_data_normalized(
419-
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
420-
)
410+
set_data_normalized(
411+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, normalized_messages, unpack=False
412+
)
421413

422414
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
423415

@@ -471,12 +463,9 @@ def _set_completions_api_input_data(
471463

472464
if isinstance(messages, str):
473465
normalized_messages = normalize_message_roles([messages]) # type: ignore
474-
scope = sentry_sdk.get_current_scope()
475-
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
476-
if messages_data is not None:
477-
set_data_normalized(
478-
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
479-
)
466+
set_data_normalized(
467+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, normalized_messages, unpack=False
468+
)
480469
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
481470
return
482471

@@ -502,12 +491,9 @@ def _set_completions_api_input_data(
502491
]
503492
if len(non_system_messages) > 0:
504493
normalized_messages = normalize_message_roles(non_system_messages)
505-
scope = sentry_sdk.get_current_scope()
506-
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
507-
if messages_data is not None:
508-
set_data_normalized(
509-
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
510-
)
494+
set_data_normalized(
495+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, normalized_messages, unpack=False
496+
)
511497

512498
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
513499

@@ -538,14 +524,9 @@ def _set_embeddings_input_data(
538524
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings")
539525

540526
normalized_messages = normalize_message_roles([messages]) # type: ignore
541-
scope = sentry_sdk.get_current_scope()
542-
messages_data = truncate_and_annotate_embedding_inputs(
543-
normalized_messages, span, scope
527+
set_data_normalized(
528+
span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, normalized_messages, unpack=False
544529
)
545-
if messages_data is not None:
546-
set_data_normalized(
547-
span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, messages_data, unpack=False
548-
)
549530

550531
return
551532

@@ -559,14 +540,9 @@ def _set_embeddings_input_data(
559540

560541
if len(messages) > 0:
561542
normalized_messages = normalize_message_roles(messages)
562-
scope = sentry_sdk.get_current_scope()
563-
messages_data = truncate_and_annotate_embedding_inputs(
564-
normalized_messages, span, scope
543+
set_data_normalized(
544+
span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, normalized_messages, unpack=False
565545
)
566-
if messages_data is not None:
567-
set_data_normalized(
568-
span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, messages_data, unpack=False
569-
)
570546

571547
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings")
572548

tests/integrations/openai/test_openai.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3724,51 +3724,6 @@ def test_openai_message_role_mapping(
37243724
assert stored_messages[0]["role"] == expected_role
37253725

37263726

3727-
def test_openai_message_truncation(sentry_init, capture_items):
3728-
"""Test that large messages are truncated properly in OpenAI integration."""
3729-
sentry_init(
3730-
integrations=[OpenAIIntegration(include_prompts=True)],
3731-
traces_sample_rate=1.0,
3732-
send_default_pii=True,
3733-
)
3734-
items = capture_items("transaction", "span")
3735-
3736-
client = OpenAI(api_key="z")
3737-
client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION)
3738-
3739-
large_content = (
3740-
"This is a very long message that will exceed our size limits. " * 1000
3741-
)
3742-
large_messages = [
3743-
{"role": "system", "content": "You are a helpful assistant."},
3744-
{"role": "user", "content": large_content},
3745-
{"role": "assistant", "content": large_content},
3746-
{"role": "user", "content": large_content},
3747-
]
3748-
3749-
with start_transaction(name="openai tx"):
3750-
client.chat.completions.create(
3751-
model="some-model",
3752-
messages=large_messages,
3753-
)
3754-
3755-
span = next(item.payload for item in items if item.type == "span")
3756-
assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["attributes"]
3757-
3758-
messages_data = span["attributes"][SPANDATA.GEN_AI_REQUEST_MESSAGES]
3759-
assert isinstance(messages_data, str)
3760-
3761-
parsed_messages = json.loads(messages_data)
3762-
assert isinstance(parsed_messages, list)
3763-
assert len(parsed_messages) <= len(large_messages)
3764-
3765-
(event,) = (item.payload for item in items if item.type == "transaction")
3766-
meta_path = event["_meta"]
3767-
span_meta = meta_path["spans"]["0"]["data"]
3768-
messages_meta = span_meta[SPANDATA.GEN_AI_REQUEST_MESSAGES]
3769-
assert "len" in messages_meta.get("", {})
3770-
3771-
37723727
# noinspection PyTypeChecker
37733728
def test_streaming_chat_completion_ttft(
37743729
sentry_init, capture_items, get_model_response, server_side_event_chunks

0 commit comments

Comments
 (0)