diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 93fca6ba3e..4b61a317fb 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -542,6 +542,12 @@ class SPANDATA: Example: 2048 """ + GEN_AI_SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions" + """ + The system instructions passed to the model. + Example: [{"type": "text", "text": "You are a helpful assistant."},{"type": "text", "text": "Be concise and clear."}] + """ + GEN_AI_REQUEST_MESSAGES = "gen_ai.request.messages" """ The messages passed to the model. The "content" can be a string or an array of objects. diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 2bc48e54e3..c20d3cd7cd 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -192,29 +192,12 @@ def _set_input_data( and should_send_default_pii() and integration.include_prompts ): - normalized_messages = [] if system_prompt: - system_prompt_content: "Optional[Union[str, List[dict[str, Any]]]]" = None - if isinstance(system_prompt, str): - system_prompt_content = system_prompt - elif isinstance(system_prompt, Iterable): - system_prompt_content = [] - for item in system_prompt: - if ( - isinstance(item, dict) - and item.get("type") == "text" - and item.get("text") - ): - system_prompt_content.append(item.copy()) - - if system_prompt_content: - normalized_messages.append( - { - "role": GEN_AI_ALLOWED_MESSAGE_ROLES.SYSTEM, - "content": system_prompt_content, - } - ) + set_data_normalized( + span, SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, system_prompt, unpack=False + ) + normalized_messages = [] for message in messages: if ( message.get("role") == GEN_AI_ALLOWED_MESSAGE_ROLES.USER diff --git a/tests/integrations/anthropic/test_anthropic.py b/tests/integrations/anthropic/test_anthropic.py index e8bc4648b6..460995e108 100644 --- a/tests/integrations/anthropic/test_anthropic.py +++ b/tests/integrations/anthropic/test_anthropic.py @@ -1074,17 +1074,18 @@ def test_nonstreaming_create_message_with_system_prompt( assert span["data"][SPANDATA.GEN_AI_REQUEST_MODEL] == "model" if send_default_pii and include_prompts: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS in span["data"] + system_instructions = span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] + assert system_instructions == "You are a helpful assistant." + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["data"] stored_messages = json.loads(span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - assert len(stored_messages) == 2 - # System message should be first - assert stored_messages[0]["role"] == "system" - assert stored_messages[0]["content"] == "You are a helpful assistant." - # User message should be second - assert stored_messages[1]["role"] == "user" - assert stored_messages[1]["content"] == "Hello, Claude" + assert len(stored_messages) == 1 + assert stored_messages[0]["role"] == "user" + assert stored_messages[0]["content"] == "Hello, Claude" assert span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT] == "Hi, I'm Claude." else: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"] @@ -1153,17 +1154,18 @@ async def test_nonstreaming_create_message_with_system_prompt_async( assert span["data"][SPANDATA.GEN_AI_REQUEST_MODEL] == "model" if send_default_pii and include_prompts: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS in span["data"] + system_instructions = span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] + assert system_instructions == "You are a helpful assistant." + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["data"] stored_messages = json.loads(span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - assert len(stored_messages) == 2 - # System message should be first - assert stored_messages[0]["role"] == "system" - assert stored_messages[0]["content"] == "You are a helpful assistant." - # User message should be second - assert stored_messages[1]["role"] == "user" - assert stored_messages[1]["content"] == "Hello, Claude" + assert len(stored_messages) == 1 + assert stored_messages[0]["role"] == "user" + assert stored_messages[0]["content"] == "Hello, Claude" assert span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT] == "Hi, I'm Claude." else: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"] @@ -1264,18 +1266,19 @@ def test_streaming_create_message_with_system_prompt( assert span["data"][SPANDATA.GEN_AI_REQUEST_MODEL] == "model" if send_default_pii and include_prompts: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS in span["data"] + system_instructions = span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] + assert system_instructions == "You are a helpful assistant." + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["data"] stored_messages = json.loads(span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - assert len(stored_messages) == 2 - # System message should be first - assert stored_messages[0]["role"] == "system" - assert stored_messages[0]["content"] == "You are a helpful assistant." - # User message should be second - assert stored_messages[1]["role"] == "user" - assert stored_messages[1]["content"] == "Hello, Claude" + assert len(stored_messages) == 1 + assert stored_messages[0]["role"] == "user" + assert stored_messages[0]["content"] == "Hello, Claude" assert span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT] == "Hi! I'm Claude!" else: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"] @@ -1379,18 +1382,19 @@ async def test_streaming_create_message_with_system_prompt_async( assert span["data"][SPANDATA.GEN_AI_REQUEST_MODEL] == "model" if send_default_pii and include_prompts: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS in span["data"] + system_instructions = span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] + assert system_instructions == "You are a helpful assistant." + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["data"] stored_messages = json.loads(span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - assert len(stored_messages) == 2 - # System message should be first - assert stored_messages[0]["role"] == "system" - assert stored_messages[0]["content"] == "You are a helpful assistant." - # User message should be second - assert stored_messages[1]["role"] == "user" - assert stored_messages[1]["content"] == "Hello, Claude" + assert len(stored_messages) == 1 + assert stored_messages[0]["role"] == "user" + assert stored_messages[0]["content"] == "Hello, Claude" assert span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT] == "Hi! I'm Claude!" else: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"] @@ -1437,21 +1441,23 @@ def test_system_prompt_with_complex_structure(sentry_init, capture_events): (span,) = event["spans"] assert span["data"][SPANDATA.GEN_AI_OPERATION_NAME] == "chat" + + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS in span["data"] + system_instructions = json.loads(span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS]) + + # System content should be a list of text blocks + assert isinstance(system_instructions, list) + assert system_instructions[0]["type"] == "text" + assert system_instructions[0]["text"] == "You are a helpful assistant." + assert system_instructions[1]["type"] == "text" + assert system_instructions[1]["text"] == "Be concise and clear." + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["data"] stored_messages = json.loads(span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - # Should have system message first, then user message - assert len(stored_messages) == 2 - assert stored_messages[0]["role"] == "system" - # System content should be a list of text blocks - assert isinstance(stored_messages[0]["content"], list) - assert len(stored_messages[0]["content"]) == 2 - assert stored_messages[0]["content"][0]["type"] == "text" - assert stored_messages[0]["content"][0]["text"] == "You are a helpful assistant." - assert stored_messages[0]["content"][1]["type"] == "text" - assert stored_messages[0]["content"][1]["text"] == "Be concise and clear." - assert stored_messages[1]["role"] == "user" - assert stored_messages[1]["content"] == "Hello" + assert len(stored_messages) == 1 + assert stored_messages[0]["role"] == "user" + assert stored_messages[0]["content"] == "Hello" # Tests for transform_content_part (shared) and _transform_anthropic_content_block helper functions