Skip to content

Commit 5d8c5f3

Browse files
committed
Merge branch 'master' into ivana/span-first-11-sampling
2 parents 4ba4351 + adcd90c commit 5d8c5f3

File tree

15 files changed

+314
-191
lines changed

15 files changed

+314
-191
lines changed

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

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

3636
- name: Run Python SDK Tests
37-
uses: getsentry/testing-ai-sdk-integrations@121da677853244cedfe11e95184b2b431af102eb
37+
uses: getsentry/testing-ai-sdk-integrations@285c012e522f241581534dfc89bd99ec3b1da4f6
3838
env:
3939
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4040
with:

scripts/build_aws_lambda_layer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class LayerBuilder:
1818
def __init__(
1919
self,
2020
base_dir: str,
21-
out_zip_filename: "Optional[str]"=None,
21+
out_zip_filename: "Optional[str]" = None,
2222
) -> None:
2323
self.base_dir = base_dir
2424
self.python_site_packages = os.path.join(self.base_dir, PYTHON_SITE_PACKAGES)

scripts/populate_tox/package_dependencies.jsonl

Lines changed: 17 additions & 16 deletions
Large diffs are not rendered by default.

scripts/populate_tox/populate_tox.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
See scripts/populate_tox/README.md for more info.
55
"""
66

7+
import re
78
import functools
89
import hashlib
910
import json
@@ -872,7 +873,10 @@ def get_last_updated() -> Optional[datetime]:
872873

873874

874875
def _normalize_name(package: str) -> str:
875-
return package.lower().replace("-", "_")
876+
# From https://peps.python.org/pep-0503/#normalized-names
877+
# but normalizing to underscores instead of hyphens since tox-formatted packages
878+
# use underscores.
879+
return re.sub(r"[-_.]+", "_", package).lower()
876880

877881

878882
def _extract_wheel_info_to_cache(wheel: dict):

scripts/populate_tox/releases.jsonl

Lines changed: 44 additions & 43 deletions
Large diffs are not rendered by default.

sentry_sdk/_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ class SDKInfo(TypedDict):
351351
"max_runtime": int,
352352
"failure_issue_threshold": int,
353353
"recovery_threshold": int,
354+
"owner": str,
354355
},
355356
total=False,
356357
)

sentry_sdk/integrations/httpx.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424

2525
try:
26-
from httpx import AsyncClient, Client, Request, Response # type: ignore
26+
from httpx import AsyncClient, Client, Request, Response
2727
except ImportError:
2828
raise DidNotEnable("httpx is not installed")
2929

@@ -94,7 +94,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
9494

9595
return rv
9696

97-
Client.send = send
97+
Client.send = send # type: ignore
9898

9999

100100
def _install_httpx_async_client() -> None:
@@ -150,4 +150,4 @@ async def send(
150150

151151
return rv
152152

153-
AsyncClient.send = send
153+
AsyncClient.send = send # type: ignore

sentry_sdk/integrations/pydantic_ai/patches/tools.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ async def wrapped_execute_tool_call(
5050
call = validated.call
5151
name = call.tool_name
5252
tool = self.tools.get(name) if self.tools else None
53+
selected_tool_definition = getattr(tool, "tool_def", None)
5354

5455
# Determine tool type by checking tool.toolset
5556
tool_type = "function"
@@ -73,6 +74,7 @@ async def wrapped_execute_tool_call(
7374
args_dict,
7475
agent,
7576
tool_type=tool_type,
77+
tool_definition=selected_tool_definition,
7678
) as span:
7779
try:
7880
result = await original_execute_tool_call(
@@ -127,6 +129,7 @@ async def wrapped_call_tool(
127129
# Extract tool info before calling original
128130
name = call.tool_name
129131
tool = self.tools.get(name) if self.tools else None
132+
selected_tool_definition = getattr(tool, "tool_def", None)
130133

131134
# Determine tool type by checking tool.toolset
132135
tool_type = "function" # default
@@ -150,6 +153,7 @@ async def wrapped_call_tool(
150153
args_dict,
151154
agent,
152155
tool_type=tool_type,
156+
tool_definition=selected_tool_definition,
153157
) as span:
154158
try:
155159
result = await original_call_tool(

sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@
99

1010
if TYPE_CHECKING:
1111
from typing import Any, Optional
12+
from pydantic_ai._tool_manager import ToolDefinition # type: ignore
1213

1314

1415
def execute_tool_span(
15-
tool_name: str, tool_args: "Any", agent: "Any", tool_type: str = "function"
16+
tool_name: str,
17+
tool_args: "Any",
18+
agent: "Any",
19+
tool_type: str = "function",
20+
tool_definition: "Optional[ToolDefinition]" = None,
1621
) -> "sentry_sdk.tracing.Span":
1722
"""Create a span for tool execution.
1823
@@ -21,6 +26,7 @@ def execute_tool_span(
2126
tool_args: The arguments passed to the tool
2227
agent: The agent executing the tool
2328
tool_type: The type of tool ("function" for regular tools, "mcp" for MCP services)
29+
tool_definition: The definition of the tool, if available
2430
"""
2531
span = sentry_sdk.start_span(
2632
op=OP.GEN_AI_EXECUTE_TOOL,
@@ -32,6 +38,12 @@ def execute_tool_span(
3238
span.set_data(SPANDATA.GEN_AI_TOOL_TYPE, tool_type)
3339
span.set_data(SPANDATA.GEN_AI_TOOL_NAME, tool_name)
3440

41+
if tool_definition is not None and hasattr(tool_definition, "description"):
42+
span.set_data(
43+
SPANDATA.GEN_AI_TOOL_DESCRIPTION,
44+
tool_definition.description,
45+
)
46+
3547
_set_agent_data(span, agent)
3648

3749
if _should_send_prompts() and tool_args is not None:

sentry_sdk/traces.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,16 @@ def __init__(
264264
self._start_timestamp = datetime.now(timezone.utc)
265265
self._timestamp: "Optional[datetime]" = None
266266

267+
try:
268+
# profiling depends on this value and requires that
269+
# it is measured in nanoseconds
270+
self._start_timestamp_monotonic_ns = nanosecond_time()
271+
except AttributeError:
272+
pass
273+
274+
self._start_timestamp = datetime.now(timezone.utc)
275+
self._timestamp: "Optional[datetime]" = None
276+
267277
try:
268278
# profiling depends on this value and requires that
269279
# it is measured in nanoseconds
@@ -294,8 +304,12 @@ def __enter__(self) -> "StreamedSpan":
294304
def __exit__(
295305
self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
296306
) -> None:
307+
if self._timestamp is not None:
308+
# This span is already finished, ignore
309+
return
310+
297311
if value is not None and should_be_treated_as_error(ty, value):
298-
self.status = SpanStatus.ERROR
312+
self.status = SpanStatus.ERROR.value
299313

300314
self._end()
301315

@@ -335,7 +349,9 @@ def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None
335349
del self._previous_span_on_scope
336350
self._scope.span = old_span
337351

338-
# Set attributes from the segment
352+
# Set attributes from the segment. These are set on span end on purpose
353+
# so that we have the best chance to capture the segment's final name
354+
# (since it might change during its lifetime)
339355
self.set_attribute("sentry.segment.id", self._segment.span_id)
340356
self.set_attribute("sentry.segment.name", self._segment.name)
341357

0 commit comments

Comments
 (0)