Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 16 additions & 14 deletions sentry_sdk/integrations/pydantic_ai/spans/ai_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@
BinaryContent = None


def _set_system_instruction(span: "sentry_sdk.tracing.Span", messages: "Any") -> None:
for msg in messages:
for part in msg.parts:
if SystemPromptPart and isinstance(part, SystemPromptPart):
system_prompt = part.content
set_data_normalized(
span,
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
system_prompt,
unpack=False,
)
return


def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> None:
"""Set input messages data on a span."""
if not _should_send_prompts():
Expand All @@ -58,27 +72,14 @@ def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> Non

try:
formatted_messages = []
system_prompt = None

# Extract system prompt from any ModelRequest with instructions
for msg in messages:
if hasattr(msg, "instructions") and msg.instructions:
system_prompt = msg.instructions
break

# Add system prompt as first message if present
if system_prompt:
formatted_messages.append(
{"role": "system", "content": [{"type": "text", "text": system_prompt}]}
)

for msg in messages:
if hasattr(msg, "parts"):
for part in msg.parts:
role = "user"
# Use isinstance checks with proper base classes
if SystemPromptPart and isinstance(part, SystemPromptPart):
role = "system"
continue
elif (
(TextPart and isinstance(part, TextPart))
or (ThinkingPart and isinstance(part, ThinkingPart))
Expand Down Expand Up @@ -235,6 +236,7 @@ def ai_client_span(

# Set input messages (full conversation history)
if messages:
_set_system_instruction(span, messages)
_set_input_messages(span, messages)

return span
Expand Down
25 changes: 11 additions & 14 deletions tests/integrations/pydantic_ai/test_pydantic_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ async def test_model_settings(sentry_init, capture_events, test_agent_with_setti


@pytest.mark.asyncio
async def test_system_prompt_in_messages(sentry_init, capture_events):
async def test_system_prompt_attribute(sentry_init, capture_events):
"""
Test that system prompts are included as the first message.
"""
Expand Down Expand Up @@ -542,12 +542,8 @@ async def test_system_prompt_in_messages(sentry_init, capture_events):
assert len(chat_spans) >= 1

chat_span = chat_spans[0]
messages_str = chat_span["data"]["gen_ai.request.messages"]

# Messages is serialized as a string
# Should contain system role and helpful assistant text
assert "system" in messages_str
assert "helpful assistant" in messages_str
system_instruction = chat_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS]
assert "helpful assistant" in system_instruction


@pytest.mark.asyncio
Expand Down Expand Up @@ -1211,14 +1207,15 @@ async def test_invoke_agent_with_instructions(sentry_init, capture_events):
await agent.run("Test input")

(transaction,) = events
spans = transaction["spans"]

# Check that the invoke_agent transaction has messages data
if "gen_ai.request.messages" in transaction["contexts"]["trace"]["data"]:
messages_str = transaction["contexts"]["trace"]["data"][
"gen_ai.request.messages"
]
# Should contain both instructions and system prompts
assert "Instruction" in messages_str or "System prompt" in messages_str
# The transaction IS the invoke_agent span, check for messages in chat spans instead
chat_spans = [s for s in spans if s["op"] == "gen_ai.chat"]
assert len(chat_spans) >= 1

chat_span = chat_spans[0]
system_instruction = chat_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS]
assert "System prompt" in system_instruction


@pytest.mark.asyncio
Expand Down
Loading