Skip to content
Merged
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
9 changes: 3 additions & 6 deletions sentry_sdk/integrations/pydantic_ai/spans/ai_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from typing import TYPE_CHECKING

import sentry_sdk
from sentry_sdk.ai.utils import (
normalize_message_roles,
set_data_normalized,
Expand All @@ -15,8 +16,6 @@
_set_agent_data,
_set_available_tools,
_set_model_data,
_should_send_inputs,
_should_send_outputs,
)
from .utils import (
_serialize_binary_content_item,
Expand Down Expand Up @@ -101,7 +100,7 @@ def _get_system_instructions(

def _set_input_messages(span: "Span", messages: "list[ModelMessage]") -> None:
"""Set input messages data on a span."""
if not _should_send_inputs():
if not sentry_sdk.get_client().options["data_collection"]["gen_ai"]["inputs"]:
return

if not messages:
Expand Down Expand Up @@ -216,14 +215,12 @@ def _set_output_data(
response: "Optional[ModelResponse]",
) -> None:
"""Set output data on a span."""
record_outputs = _should_send_outputs()

if not response:
return

span.set_attribute(SPANDATA.GEN_AI_RESPONSE_MODEL, response.model_name) # type: ignore[arg-type]

if not record_outputs:
if not sentry_sdk.get_client().options["data_collection"]["gen_ai"]["outputs"]:
return

try:
Expand Down
16 changes: 12 additions & 4 deletions sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sentry_sdk.utils import safe_serialize

from ..consts import SPAN_ORIGIN
from ..utils import _set_agent_data, _should_send_inputs, _should_send_outputs
from ..utils import _set_agent_data

if TYPE_CHECKING:
from typing import Any, Optional
Expand Down Expand Up @@ -47,8 +47,13 @@ def execute_tool_span(

_set_agent_data(span, agent)

if _should_send_inputs() and tool_args is not None:
span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_args))
if (
not sentry_sdk.get_client().options["data_collection"]["gen_ai"]["inputs"]
or tool_args is None
):
return span

span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_args))

return span

Expand All @@ -58,7 +63,10 @@ def update_execute_tool_span(span: "Span", result: "Any") -> None:
if not span:
return

if not _should_send_outputs() or result is None:
if (
not sentry_sdk.get_client().options["data_collection"]["gen_ai"]["outputs"]
or result is None
):
return

span.set_attribute(SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result))
137 changes: 70 additions & 67 deletions sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from ..utils import (
_set_agent_data,
_set_model_data,
_should_send_inputs,
_should_send_outputs,
)
from .utils import (
_serialize_binary_content_item,
Expand Down Expand Up @@ -60,76 +58,78 @@ def invoke_agent_span(
_set_model_data(span, agent, model, model_settings)

# Add user prompt and system prompts if available and prompts are enabled
if _should_send_inputs():
messages = []

# Add system prompts (both instructions and system_prompt)
system_texts = []

if agent:
# Check for system_prompt
system_prompts = getattr(agent, "_system_prompts", None) or []
for prompt in system_prompts:
if isinstance(prompt, str):
system_texts.append(prompt)

# Check for instructions (stored in _instructions)
instructions = getattr(agent, "_instructions", None)
if instructions:
if isinstance(instructions, str):
system_texts.append(instructions)
elif isinstance(instructions, (list, tuple)):
for instr in instructions:
if isinstance(instr, str):
system_texts.append(instr)
elif callable(instr):
# Skip dynamic/callable instructions
pass

# Add all system texts as system messages
for system_text in system_texts:
if not sentry_sdk.get_client().options["data_collection"]["gen_ai"]["inputs"]:
return span

messages = []

# Add system prompts (both instructions and system_prompt)
system_texts = []

if agent:
# Check for system_prompt
system_prompts = getattr(agent, "_system_prompts", None) or []
for prompt in system_prompts:
if isinstance(prompt, str):
system_texts.append(prompt)

# Check for instructions (stored in _instructions)
instructions = getattr(agent, "_instructions", None)
if instructions:
if isinstance(instructions, str):
system_texts.append(instructions)
elif isinstance(instructions, (list, tuple)):
for instr in instructions:
if isinstance(instr, str):
system_texts.append(instr)
elif callable(instr):
# Skip dynamic/callable instructions
pass

# Add all system texts as system messages
for system_text in system_texts:
messages.append(
{
"content": [{"text": system_text, "type": "text"}],
"role": "system",
}
)

# Add user prompt
if user_prompt:
if isinstance(user_prompt, str):
messages.append(
{
"content": [{"text": system_text, "type": "text"}],
"role": "system",
"content": [{"text": user_prompt, "type": "text"}],
"role": "user",
}
)

# Add user prompt
if user_prompt:
if isinstance(user_prompt, str):
elif isinstance(user_prompt, list):
# Handle list of user content
content = []
for item in user_prompt:
if isinstance(item, str):
content.append({"text": item, "type": "text"})
elif ImageUrl is not None and isinstance(item, ImageUrl):
content.append(_serialize_image_url_item(item))
elif BinaryContent is not None and isinstance(item, BinaryContent):
content.append(_serialize_binary_content_item(item))
if content:
messages.append(
{
"content": [{"text": user_prompt, "type": "text"}],
"content": content,
"role": "user",
}
)
elif isinstance(user_prompt, list):
# Handle list of user content
content = []
for item in user_prompt:
if isinstance(item, str):
content.append({"text": item, "type": "text"})
elif ImageUrl is not None and isinstance(item, ImageUrl):
content.append(_serialize_image_url_item(item))
elif BinaryContent is not None and isinstance(item, BinaryContent):
content.append(_serialize_binary_content_item(item))
if content:
messages.append(
{
"content": content,
"role": "user",
}
)

if messages:
normalized_messages = normalize_message_roles(messages)
set_data_normalized(
span,
SPANDATA.GEN_AI_REQUEST_MESSAGES,
normalized_messages,
unpack=False,
)

if messages:
normalized_messages = normalize_message_roles(messages)
set_data_normalized(
span,
SPANDATA.GEN_AI_REQUEST_MESSAGES,
normalized_messages,
unpack=False,
)

return span

Expand All @@ -146,7 +146,10 @@ def update_invoke_agent_span(
output = getattr(result, "output", None)

# Set response text if prompts are enabled
if _should_send_outputs() and output:
set_data_normalized(
span, SPANDATA.GEN_AI_RESPONSE_TEXT, str(output), unpack=False
)
if (
not sentry_sdk.get_client().options["data_collection"]["gen_ai"]["outputs"]
or not output
):
return

set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, str(output), unpack=False)
23 changes: 2 additions & 21 deletions sentry_sdk/integrations/pydantic_ai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

import sentry_sdk
from sentry_sdk.consts import SPANDATA
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.traces import Span
from sentry_sdk.utils import (
event_from_exception,
has_data_collection_enabled,
safe_serialize,
)

Expand All @@ -19,22 +17,6 @@
from pydantic_ai.settings import ModelSettings


def _should_send_inputs() -> bool:
client = sentry_sdk.get_client()
if has_data_collection_enabled(client.options):
return bool(client.options["data_collection"]["gen_ai"]["inputs"])

return should_send_default_pii()


def _should_send_outputs() -> bool:
client = sentry_sdk.get_client()
if has_data_collection_enabled(client.options):
return bool(client.options["data_collection"]["gen_ai"]["outputs"])

return should_send_default_pii()


def _set_agent_data(span: "Span", agent: "Optional[Agent]") -> None:
"""Set agent-related data on a span.

Expand Down Expand Up @@ -141,9 +123,8 @@ def _set_available_tools(span: "Span", agent: "Optional[Agent[Any, Any]]") -> No
return

client_options = sentry_sdk.get_client().options
if has_data_collection_enabled(client_options):
if not client_options["data_collection"]["gen_ai"]["inputs"]:
return
if not client_options["data_collection"]["gen_ai"]["inputs"]:
return
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Resolved

try:
tools = []
Expand Down
Loading
Loading