diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index 5acc501172..684b7323da 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -14,6 +14,7 @@ import sentry_sdk from sentry_sdk.utils import logger +from sentry_sdk.consts import SPANDATA MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB # Maximum characters when only a single message is left after bytes truncation @@ -698,6 +699,8 @@ def truncate_and_annotate_messages( if len(messages) > 1: scope._gen_ai_original_message_count[span.span_id] = len(messages) + span.set_data(SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH, len(messages)) + return [truncated_message] diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 0e7ad18d91..7b569edc0d 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -869,6 +869,14 @@ class SPANDATA: Example: "a1b2c3d4e5f6" """ + META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH = ( + "sentry.sdk_meta.gen_ai.input.messages.original_length" + ) + """ + The original number of input non-system instruction messages, before SDK trimming. + Example: 4 + """ + class SPANSTATUS: """ diff --git a/tests/integrations/anthropic/test_anthropic.py b/tests/integrations/anthropic/test_anthropic.py index d673329eb5..84d773e129 100644 --- a/tests/integrations/anthropic/test_anthropic.py +++ b/tests/integrations/anthropic/test_anthropic.py @@ -1010,6 +1010,7 @@ def test_anthropic_message_truncation(sentry_init, capture_events): assert len(parsed_messages) == 1 assert "small message 5" in str(parsed_messages[0]) + assert chat_span["data"][SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH] == 5 assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5 @@ -1061,6 +1062,7 @@ async def test_anthropic_message_truncation_async(sentry_init, capture_events): assert len(parsed_messages) == 1 assert "small message 5" in str(parsed_messages[0]) + assert chat_span["data"][SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH] == 5 assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5 diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index c643537a05..37ba50420f 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -990,6 +990,7 @@ def test_google_genai_message_truncation( assert parsed_messages[0]["role"] == "user" assert small_content in parsed_messages[0]["content"] + assert invoke_span["data"][SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH] == 2 assert ( event["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 2 ) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 8a8d646113..58cc16cdd7 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -1070,6 +1070,8 @@ def test_langchain_message_truncation(sentry_init, capture_events): assert isinstance(parsed_messages, list) assert len(parsed_messages) == 1 assert "small message 5" in str(parsed_messages[0]) + + assert llm_span["data"][SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH] == 5 assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5 diff --git a/tests/integrations/langgraph/test_langgraph.py b/tests/integrations/langgraph/test_langgraph.py index 2a385d8a78..9ccd84309f 100644 --- a/tests/integrations/langgraph/test_langgraph.py +++ b/tests/integrations/langgraph/test_langgraph.py @@ -1384,4 +1384,6 @@ def original_invoke(self, *args, **kwargs): assert isinstance(parsed_messages, list) assert len(parsed_messages) == 1 assert "small message 5" in str(parsed_messages[0]) + + assert invoke_span["data"][SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH] == 5 assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5 diff --git a/tests/integrations/litellm/test_litellm.py b/tests/integrations/litellm/test_litellm.py index ef129c6cfd..06772342ab 100644 --- a/tests/integrations/litellm/test_litellm.py +++ b/tests/integrations/litellm/test_litellm.py @@ -754,6 +754,8 @@ def test_litellm_message_truncation(sentry_init, capture_events): assert isinstance(parsed_messages, list) assert len(parsed_messages) == 1 assert "small message 5" in str(parsed_messages[0]) + + assert chat_span["data"][SPANDATA.META_GEN_AI_ORIGINAL_INPUT_MESSAGES_LENGTH] == 5 assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5 diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 1ee51d264c..3581a14bd7 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -1607,6 +1607,7 @@ def test_ai_client_span_responses_api( "gen_ai.usage.total_tokens": 30, "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", + "sentry.sdk_meta.gen_ai.input.messages.original_length": 1, "thread.id": mock.ANY, "thread.name": mock.ANY, } @@ -1910,6 +1911,7 @@ async def test_ai_client_span_responses_async_api( "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, "gen_ai.response.text": "the model response", + "sentry.sdk_meta.gen_ai.input.messages.original_length": 1, "thread.id": mock.ANY, "thread.name": mock.ANY, } @@ -2177,6 +2179,7 @@ async def test_ai_client_span_streaming_responses_async_api( "gen_ai.usage.total_tokens": 30, "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", + "sentry.sdk_meta.gen_ai.input.messages.original_length": 1, "thread.id": mock.ANY, "thread.name": mock.ANY, }