Skip to content

Commit 5d10bc6

Browse files
Merge branch 'master' into constantinius/feat/integrations/openai-agents-streaming
2 parents d7e3bfb + 54034dc commit 5d10bc6

14 files changed

Lines changed: 444 additions & 133 deletions

File tree

.github/workflows/ai-integration-test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ jobs:
3434
token: ${{ secrets.GITHUB_TOKEN }}
3535

3636
- name: Run Python SDK Tests
37-
uses: getsentry/testing-ai-sdk-integrations@1c6853a3a46ff1217248bf2b61e3ca5c4fcafdca
37+
uses: getsentry/testing-ai-sdk-integrations@dba21cbfb57482556338983d8c35e6b09b534667
3838
env:
3939
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4040
with:
4141
language: py
42+
sentry-python-path: ${{ github.workspace }}
4243
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
4344
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
4445
google-api-key: ${{ secrets.GOOGLE_API_KEY }}

CHANGELOG.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,96 @@
11
# Changelog
22

3+
## 2.50.0
4+
5+
### New Features ✨
6+
7+
#### Ai
8+
9+
- feat(ai): add cache writes for gen_ai by @shellmayr in [#5319](https://github.com/getsentry/sentry-python/pull/5319)
10+
- feat(ai): add parse_data_uri function to parse a data URI by @constantinius in [#5311](https://github.com/getsentry/sentry-python/pull/5311)
11+
12+
#### Other
13+
14+
- feat(asyncio): Add on-demand way to enable AsyncioIntegration by @sentrivana in [#5288](https://github.com/getsentry/sentry-python/pull/5288)
15+
16+
You can now enable the `AsyncioIntegration` on demand, after calling `sentry_sdk.init()`. This is useful in scenarios where you don't have
17+
the event loop running early on, or when you need to instrument multiple event loops.
18+
19+
```python
20+
import sentry_sdk
21+
from sentry_sdk.integrations.asyncio import enable_asyncio_integration
22+
23+
# Initializing the SDK as early as possible, when there is no event loop yet
24+
sentry_sdk.init(
25+
...
26+
# No AsyncioIntegration in explicitly provided `integrations`
27+
)
28+
29+
async def main():
30+
enable_asyncio_integration() # instruments the current event loop
31+
# ...your code...
32+
```
33+
34+
- feat(openai-agents): Inject propagation headers for `HostedMCPTool` by @alexander-alderman-webb in [#5297](https://github.com/getsentry/sentry-python/pull/5297)
35+
- feat(stdlib): Handle proxy tunnels in httlib integration by @sl0thentr0py in [#5303](https://github.com/getsentry/sentry-python/pull/5303)
36+
- feat: Support array types for logs and metrics attributes by @alexander-alderman-webb in [#5314](https://github.com/getsentry/sentry-python/pull/5314)
37+
38+
### Bug Fixes 🐛
39+
40+
#### Integrations
41+
42+
- fix(integrations): google genai report image inputs by @constantinius in [#5337](https://github.com/getsentry/sentry-python/pull/5337)
43+
- fix(integrations): google-genai: reworked `gen_ai.request.messages` extraction from parameters by @constantinius in [#5275](https://github.com/getsentry/sentry-python/pull/5275)
44+
- fix(integrations): pydantic-ai: properly format binary input message parts to be conformant with the `gen_ai.request.messages` structure by @constantinius in [#5251](https://github.com/getsentry/sentry-python/pull/5251)
45+
- fix(integrations): Anthropic: add content transformation for images and documents by @constantinius in [#5276](https://github.com/getsentry/sentry-python/pull/5276)
46+
- fix(integrations): langchain add multimodal content transformation functions for images, audio, and files by @constantinius in [#5278](https://github.com/getsentry/sentry-python/pull/5278)
47+
48+
#### Litellm
49+
50+
- fix(litellm): fix `gen_ai.request.messages` to be as expected by @constantinius in [#5255](https://github.com/getsentry/sentry-python/pull/5255)
51+
- fix(litellm): Guard against module shadowing by @alexander-alderman-webb in [#5249](https://github.com/getsentry/sentry-python/pull/5249)
52+
53+
#### Other
54+
55+
- fix(ai): redact message parts content of type blob by @constantinius in [#5243](https://github.com/getsentry/sentry-python/pull/5243)
56+
- fix(clickhouse): Guard against module shadowing by @alexander-alderman-webb in [#5250](https://github.com/getsentry/sentry-python/pull/5250)
57+
- fix(gql): Revert signature change of patched gql.Client.execute by @alexander-alderman-webb in [#5289](https://github.com/getsentry/sentry-python/pull/5289)
58+
- fix(grpc): Derive interception state from channel fields by @alexander-alderman-webb in [#5302](https://github.com/getsentry/sentry-python/pull/5302)
59+
- fix(pure-eval): Guard against module shadowing by @alexander-alderman-webb in [#5252](https://github.com/getsentry/sentry-python/pull/5252)
60+
- fix(ray): Guard against module shadowing by @alexander-alderman-webb in [#5254](https://github.com/getsentry/sentry-python/pull/5254)
61+
- fix(threading): Handle channels shadowing by @sentrivana in [#5299](https://github.com/getsentry/sentry-python/pull/5299)
62+
- fix(typer): Guard against module shadowing by @alexander-alderman-webb in [#5253](https://github.com/getsentry/sentry-python/pull/5253)
63+
- fix: Stop suppressing exception chains in AI integrations by @alexander-alderman-webb in [#5309](https://github.com/getsentry/sentry-python/pull/5309)
64+
- fix: Send client reports for span recorder overflow by @sentrivana in [#5310](https://github.com/getsentry/sentry-python/pull/5310)
65+
66+
### Documentation 📚
67+
68+
- docs(metrics): Remove experimental notice by @alexander-alderman-webb in [#5304](https://github.com/getsentry/sentry-python/pull/5304)
69+
- docs: Update Python versions banner in README by @sentrivana in [#5287](https://github.com/getsentry/sentry-python/pull/5287)
70+
71+
### Internal Changes 🔧
72+
73+
#### Fastmcp
74+
75+
- test(fastmcp): Narrow `AttributeError` try-except by @alexander-alderman-webb in [#5339](https://github.com/getsentry/sentry-python/pull/5339)
76+
- test(fastmcp): Stop accessing non-existent attribute by @alexander-alderman-webb in [#5338](https://github.com/getsentry/sentry-python/pull/5338)
77+
78+
#### Release
79+
80+
- ci(release): Bump Craft version to fix issues by @BYK in [#5305](https://github.com/getsentry/sentry-python/pull/5305)
81+
- ci(release): Switch from action-prepare-release to Craft by @BYK in [#5290](https://github.com/getsentry/sentry-python/pull/5290)
82+
83+
#### Other
84+
85+
- chore(gen_ai): add auto-enablement for google genai by @shellmayr in [#5295](https://github.com/getsentry/sentry-python/pull/5295)
86+
- chore(repo): Add Claude Code settings with basic permissions by @philipphofmann in [#5342](https://github.com/getsentry/sentry-python/pull/5342)
87+
- ci: 🤖 Update test matrix with new releases (01/19) by @github-actions in [#5330](https://github.com/getsentry/sentry-python/pull/5330)
88+
- ci: Add periodic AI integration tests by @alexander-alderman-webb in [#5313](https://github.com/getsentry/sentry-python/pull/5313)
89+
- chore: Use pull_request_target for changelog preview by @BYK in [#5323](https://github.com/getsentry/sentry-python/pull/5323)
90+
- chore: add unlabeled trigger to changelog-preview by @BYK in [#5315](https://github.com/getsentry/sentry-python/pull/5315)
91+
- chore: Add type for metric units by @sentrivana in [#5312](https://github.com/getsentry/sentry-python/pull/5312)
92+
- ci: Update tox and handle generic classifiers by @sentrivana in [#5306](https://github.com/getsentry/sentry-python/pull/5306)
93+
394
## 2.49.0
495

596
### New Features ✨

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year)
3232
author = "Sentry Team and Contributors"
3333

34-
release = "2.49.0"
34+
release = "2.50.0"
3535
version = ".".join(release.split(".")[:2]) # The short X.Y version.
3636

3737

sentry_sdk/_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,7 @@ class SDKInfo(TypedDict):
359359
)
360360

361361
HttpStatusCodeRange = Union[int, Container[int]]
362+
363+
class TextPart(TypedDict):
364+
type: Literal["text"]
365+
content: str

sentry_sdk/consts.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,12 @@ class SPANDATA:
542542
Example: 2048
543543
"""
544544

545+
GEN_AI_SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions"
546+
"""
547+
The system instructions passed to the model.
548+
Example: [{"type": "text", "text": "You are a helpful assistant."},{"type": "text", "text": "Be concise and clear."}]
549+
"""
550+
545551
GEN_AI_REQUEST_MESSAGES = "gen_ai.request.messages"
546552
"""
547553
The messages passed to the model. The "content" can be a string or an array of objects.
@@ -1471,4 +1477,4 @@ def _get_default_options() -> "dict[str, Any]":
14711477
del _get_default_options
14721478

14731479

1474-
VERSION = "2.49.0"
1480+
VERSION = "2.50.0"

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/openai_agents/spans/invoke_agent.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
import agents
1919
from typing import Any, Optional
2020

21+
from sentry_sdk._types import TextPart
22+
23+
24+
def _transform_system_instruction(system_instructions: "str") -> "list[TextPart]":
25+
return [
26+
{
27+
"type": "text",
28+
"content": system_instructions,
29+
}
30+
]
31+
2132

2233
def invoke_agent_span(
2334
context: "agents.RunContextWrapper", agent: "agents.Agent", kwargs: "dict[str, Any]"
@@ -35,16 +46,16 @@ def invoke_agent_span(
3546
if should_send_default_pii():
3647
messages = []
3748
if agent.instructions:
38-
message = (
49+
system_instruction = (
3950
agent.instructions
4051
if isinstance(agent.instructions, str)
4152
else safe_serialize(agent.instructions)
4253
)
43-
messages.append(
44-
{
45-
"content": [{"text": message, "type": "text"}],
46-
"role": "system",
47-
}
54+
set_data_normalized(
55+
span,
56+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
57+
_transform_system_instruction(system_instruction),
58+
unpack=False,
4859
)
4960

5061
original_input = kwargs.get("original_input")

0 commit comments

Comments
 (0)