diff --git a/sentry_sdk/_types.py b/sentry_sdk/_types.py index 7043bbc2ee..ecb8abcd10 100644 --- a/sentry_sdk/_types.py +++ b/sentry_sdk/_types.py @@ -359,3 +359,7 @@ class SDKInfo(TypedDict): ) HttpStatusCodeRange = Union[int, Container[int]] + + class TextPart(TypedDict): + type: Literal["text"] + content: str 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/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index c3a3a04dc9..fc9c9d4b00 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -18,6 +18,17 @@ import agents from typing import Any, Optional + from sentry_sdk._types import TextPart + + +def _transform_system_instruction(system_instructions: "str") -> "list[TextPart]": + return [ + { + "type": "text", + "content": system_instructions, + } + ] + def invoke_agent_span( context: "agents.RunContextWrapper", agent: "agents.Agent", kwargs: "dict[str, Any]" @@ -35,16 +46,16 @@ def invoke_agent_span( if should_send_default_pii(): messages = [] if agent.instructions: - message = ( + system_instruction = ( agent.instructions if isinstance(agent.instructions, str) else safe_serialize(agent.instructions) ) - messages.append( - { - "content": [{"text": message, "type": "text"}], - "role": "system", - } + set_data_normalized( + span, + SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, + _transform_system_instruction(system_instruction), + unpack=False, ) original_input = kwargs.get("original_input") diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 9d463f8de5..a3ae50d5f1 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -151,8 +151,12 @@ def test_agent_custom_model(): @pytest.mark.asyncio +@pytest.mark.parametrize( + "send_default_pii", + (True, False), +) async def test_agent_invocation_span( - sentry_init, capture_events, test_agent, mock_model_response + sentry_init, capture_events, test_agent, mock_model_response, send_default_pii ): """ Test that the integration creates spans for agent invocations. @@ -167,7 +171,7 @@ async def test_agent_invocation_span( sentry_init( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, - send_default_pii=True, + send_default_pii=send_default_pii, ) events = capture_events() @@ -187,21 +191,27 @@ async def test_agent_invocation_span( assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" assert invoke_agent_span["description"] == "invoke_agent test_agent" - assert invoke_agent_span["data"]["gen_ai.request.messages"] == safe_serialize( - [ - { - "content": [ - {"text": "You are a helpful test assistant.", "type": "text"} - ], - "role": "system", - }, - {"content": [{"text": "Test input", "type": "text"}], "role": "user"}, - ] - ) - assert ( - invoke_agent_span["data"]["gen_ai.response.text"] - == "Hello, how can I help you?" - ) + + if send_default_pii: + assert invoke_agent_span["data"][ + SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS + ] == safe_serialize( + [{"type": "text", "content": "You are a helpful test assistant."}] + ) + assert invoke_agent_span["data"]["gen_ai.request.messages"] == safe_serialize( + [ + {"content": [{"text": "Test input", "type": "text"}], "role": "user"}, + ] + ) + assert ( + invoke_agent_span["data"]["gen_ai.response.text"] + == "Hello, how can I help you?" + ) + else: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["data"] + assert "gen_ai.request.messages" not in invoke_agent_span["data"] + assert "gen_ai.response.text" not in invoke_agent_span["data"] + assert invoke_agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" assert invoke_agent_span["data"]["gen_ai.system"] == "openai" assert invoke_agent_span["data"]["gen_ai.agent.name"] == "test_agent"