Skip to content

Commit 2e70dc6

Browse files
Merge branch 'master' into webb/openai-agents-prompt-message
2 parents ad91dd1 + 26cf31d commit 2e70dc6

6 files changed

Lines changed: 294 additions & 106 deletions

File tree

sentry_sdk/integrations/anthropic.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@
3939
from anthropic.resources import AsyncMessages, Messages
4040

4141
if TYPE_CHECKING:
42-
from anthropic.types import MessageStreamEvent
42+
from anthropic.types import MessageStreamEvent, TextBlockParam
4343
except ImportError:
4444
raise DidNotEnable("Anthropic not installed")
4545

4646
if TYPE_CHECKING:
4747
from typing import Any, AsyncIterator, Iterator, List, Optional, Union
4848
from sentry_sdk.tracing import Span
49+
from sentry_sdk._types import TextPart
4950

5051

5152
class AnthropicIntegration(Integration):
@@ -177,44 +178,53 @@ def _transform_anthropic_content_block(
177178
return result if result is not None else content_block
178179

179180

181+
def _transform_system_instructions(
182+
system_instructions: "Union[str, Iterable[TextBlockParam]]",
183+
) -> "list[TextPart]":
184+
if isinstance(system_instructions, str):
185+
return [
186+
{
187+
"type": "text",
188+
"content": system_instructions,
189+
}
190+
]
191+
192+
return [
193+
{
194+
"type": "text",
195+
"content": instruction["text"],
196+
}
197+
for instruction in system_instructions
198+
if isinstance(instruction, dict) and "text" in instruction
199+
]
200+
201+
180202
def _set_input_data(
181203
span: "Span", kwargs: "dict[str, Any]", integration: "AnthropicIntegration"
182204
) -> None:
183205
"""
184206
Set input data for the span based on the provided keyword arguments for the anthropic message creation.
185207
"""
186208
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
187-
system_prompt = kwargs.get("system")
209+
system_instructions: "Union[str, Iterable[TextBlockParam]]" = kwargs.get("system") # type: ignore
188210
messages = kwargs.get("messages")
189211
if (
190212
messages is not None
191213
and len(messages) > 0
192214
and should_send_default_pii()
193215
and integration.include_prompts
194216
):
195-
normalized_messages = []
196-
if system_prompt:
197-
system_prompt_content: "Optional[Union[str, List[dict[str, Any]]]]" = None
198-
if isinstance(system_prompt, str):
199-
system_prompt_content = system_prompt
200-
elif isinstance(system_prompt, Iterable):
201-
system_prompt_content = []
202-
for item in system_prompt:
203-
if (
204-
isinstance(item, dict)
205-
and item.get("type") == "text"
206-
and item.get("text")
207-
):
208-
system_prompt_content.append(item.copy())
209-
210-
if system_prompt_content:
211-
normalized_messages.append(
212-
{
213-
"role": GEN_AI_ALLOWED_MESSAGE_ROLES.SYSTEM,
214-
"content": system_prompt_content,
215-
}
216-
)
217+
if isinstance(system_instructions, str) or isinstance(
218+
system_instructions, Iterable
219+
):
220+
set_data_normalized(
221+
span,
222+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
223+
_transform_system_instructions(system_instructions),
224+
unpack=False,
225+
)
217226

227+
normalized_messages = []
218228
for message in messages:
219229
if (
220230
message.get("role") == GEN_AI_ALLOWED_MESSAGE_ROLES.USER

sentry_sdk/integrations/langchain.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from uuid import UUID
3737

3838
from sentry_sdk.tracing import Span
39+
from sentry_sdk._types import TextPart
3940

4041

4142
try:
@@ -189,6 +190,40 @@ def _get_current_agent() -> "Optional[str]":
189190
return None
190191

191192

193+
def _get_system_instructions(messages: "List[List[BaseMessage]]") -> "List[str]":
194+
system_instructions = []
195+
196+
for list_ in messages:
197+
for message in list_:
198+
# type of content: str | list[str | dict] | None
199+
if message.type == "system" and isinstance(message.content, str):
200+
system_instructions.append(message.content)
201+
202+
elif message.type == "system" and isinstance(message.content, list):
203+
for item in message.content:
204+
if isinstance(item, str):
205+
system_instructions.append(item)
206+
207+
elif isinstance(item, dict) and item.get("type") == "text":
208+
instruction = item.get("text")
209+
if isinstance(instruction, str):
210+
system_instructions.append(instruction)
211+
212+
return system_instructions
213+
214+
215+
def _transform_system_instructions(
216+
system_instructions: "List[str]",
217+
) -> "List[TextPart]":
218+
return [
219+
{
220+
"type": "text",
221+
"content": instruction,
222+
}
223+
for instruction in system_instructions
224+
]
225+
226+
192227
class LangchainIntegration(Integration):
193228
identifier = "langchain"
194229
origin = f"auto.ai.{identifier}"
@@ -430,9 +465,21 @@ def on_chat_model_start(
430465
_set_tools_on_span(span, all_params.get("tools"))
431466

432467
if should_send_default_pii() and self.include_prompts:
468+
system_instructions = _get_system_instructions(messages)
469+
if len(system_instructions) > 0:
470+
set_data_normalized(
471+
span,
472+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
473+
_transform_system_instructions(system_instructions),
474+
unpack=False,
475+
)
476+
433477
normalized_messages = []
434478
for list_ in messages:
435479
for message in list_:
480+
if message.type == "system":
481+
continue
482+
436483
normalized_messages.append(
437484
self._normalize_langchain_message(message)
438485
)

sentry_sdk/integrations/pydantic_ai/spans/ai_client.py

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
if TYPE_CHECKING:
2727
from typing import Any, List, Dict
2828
from pydantic_ai.usage import RequestUsage # type: ignore
29+
from pydantic_ai.messages import ModelMessage, SystemPromptPart # type: ignore
30+
from sentry_sdk._types import TextPart as SentryTextPart
2931

3032
try:
31-
from pydantic_ai.messages import ( # type: ignore
33+
from pydantic_ai.messages import (
3234
BaseToolCallPart,
3335
BaseToolReturnPart,
3436
SystemPromptPart,
@@ -48,6 +50,47 @@
4850
BinaryContent = None
4951

5052

53+
def _transform_system_instructions(
54+
permanent_instructions: "list[SystemPromptPart]",
55+
current_instructions: "list[str]",
56+
) -> "list[SentryTextPart]":
57+
text_parts: "list[SentryTextPart]" = [
58+
{
59+
"type": "text",
60+
"content": instruction.content,
61+
}
62+
for instruction in permanent_instructions
63+
]
64+
65+
text_parts.extend(
66+
{
67+
"type": "text",
68+
"content": instruction,
69+
}
70+
for instruction in current_instructions
71+
)
72+
73+
return text_parts
74+
75+
76+
def _get_system_instructions(
77+
messages: "list[ModelMessage]",
78+
) -> "tuple[list[SystemPromptPart], list[str]]":
79+
permanent_instructions = []
80+
current_instructions = []
81+
82+
for msg in messages:
83+
if hasattr(msg, "parts"):
84+
for part in msg.parts:
85+
if SystemPromptPart and isinstance(part, SystemPromptPart):
86+
permanent_instructions.append(part)
87+
88+
if hasattr(msg, "instructions") and msg.instructions is not None:
89+
current_instructions.append(msg.instructions)
90+
91+
return permanent_instructions, current_instructions
92+
93+
5194
def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> None:
5295
"""Set input messages data on a span."""
5396
if not _should_send_prompts():
@@ -56,29 +99,27 @@ def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> Non
5699
if not messages:
57100
return
58101

102+
permanent_instructions, current_instructions = _get_system_instructions(messages)
103+
if len(permanent_instructions) > 0 or len(current_instructions) > 0:
104+
set_data_normalized(
105+
span,
106+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
107+
_transform_system_instructions(
108+
permanent_instructions, current_instructions
109+
),
110+
unpack=False,
111+
)
112+
59113
try:
60114
formatted_messages = []
61-
system_prompt = None
62-
63-
# Extract system prompt from any ModelRequest with instructions
64-
for msg in messages:
65-
if hasattr(msg, "instructions") and msg.instructions:
66-
system_prompt = msg.instructions
67-
break
68-
69-
# Add system prompt as first message if present
70-
if system_prompt:
71-
formatted_messages.append(
72-
{"role": "system", "content": [{"type": "text", "text": system_prompt}]}
73-
)
74115

75116
for msg in messages:
76117
if hasattr(msg, "parts"):
77118
for part in msg.parts:
78119
role = "user"
79120
# Use isinstance checks with proper base classes
80121
if SystemPromptPart and isinstance(part, SystemPromptPart):
81-
role = "system"
122+
continue
82123
elif (
83124
(TextPart and isinstance(part, TextPart))
84125
or (ThinkingPart and isinstance(part, ThinkingPart))

0 commit comments

Comments
 (0)