Skip to content

Commit f9521de

Browse files
Merge branch 'webb/store-span-on-openai-agents-context-wrapper' into webb/openai-agents-span-management
2 parents 2c0edd5 + cea080b commit f9521de

File tree

18 files changed

+805
-180
lines changed

18 files changed

+805
-180
lines changed

scripts/populate_tox/config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,13 @@
235235
"litestar": {
236236
"package": "litestar",
237237
"deps": {
238-
"*": ["pytest-asyncio", "python-multipart", "requests", "cryptography"],
238+
"*": [
239+
"pytest-asyncio",
240+
"python-multipart",
241+
"requests",
242+
"cryptography",
243+
"sniffio",
244+
],
239245
"<2.7": ["httpx<0.28"],
240246
},
241247
},

scripts/populate_tox/package_dependencies.jsonl

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

scripts/populate_tox/releases.jsonl

Lines changed: 21 additions & 22 deletions
Large diffs are not rendered by default.

sentry_sdk/client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from sentry_sdk.transport import Transport, Item
7171
from sentry_sdk._log_batcher import LogBatcher
7272
from sentry_sdk._metrics_batcher import MetricsBatcher
73+
from sentry_sdk.utils import Dsn
7374

7475
I = TypeVar("I", bound=Integration) # noqa: E741
7576

@@ -201,6 +202,11 @@ def dsn(self):
201202
# type: () -> Optional[str]
202203
return None
203204

205+
@property
206+
def parsed_dsn(self):
207+
# type: () -> Optional[Dsn]
208+
return None
209+
204210
def should_send_default_pii(self):
205211
# type: () -> bool
206212
return False
@@ -512,6 +518,12 @@ def dsn(self):
512518
"""Returns the configured DSN as string."""
513519
return self.options["dsn"]
514520

521+
@property
522+
def parsed_dsn(self):
523+
# type: () -> Optional[Dsn]
524+
"""Returns the configured parsed DSN object."""
525+
return self.transport.parsed_dsn if self.transport else None
526+
515527
def _prepare_event(
516528
self,
517529
event, # type: Event

sentry_sdk/consts.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,7 @@ def __init__(
10231023
trace_ignore_status_codes=frozenset(), # type: AbstractSet[int]
10241024
enable_metrics=True, # type: bool
10251025
before_send_metric=None, # type: Optional[Callable[[Metric, Hint], Optional[Metric]]]
1026+
org_id=None, # type: Optional[str]
10261027
):
10271028
# type: (...) -> None
10281029
"""Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`.
@@ -1426,6 +1427,10 @@ def __init__(
14261427
If `trace_ignore_status_codes` is not provided, requests with any status code
14271428
may be traced.
14281429
1430+
:param org_id: An optional organization ID. The SDK will try to extract if from the DSN in most cases
1431+
but you can provide it explicitly for self-hosted and Relay setups. This value is used for
1432+
trace propagation and for features like `strict_trace_continuation`.
1433+
14291434
:param _experiments:
14301435
"""
14311436
pass

sentry_sdk/integrations/anthropic.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from collections.abc import Iterable
12
from functools import wraps
23
from typing import TYPE_CHECKING
34

45
import sentry_sdk
56
from sentry_sdk.ai.monitoring import record_token_usage
67
from sentry_sdk.ai.utils import (
8+
GEN_AI_ALLOWED_MESSAGE_ROLES,
79
set_data_normalized,
810
normalize_message_roles,
911
truncate_and_annotate_messages,
@@ -39,7 +41,7 @@
3941
raise DidNotEnable("Anthropic not installed")
4042

4143
if TYPE_CHECKING:
42-
from typing import Any, AsyncIterator, Iterator
44+
from typing import Any, AsyncIterator, Iterator, List, Optional, Union
4345
from sentry_sdk.tracing import Span
4446

4547

@@ -122,6 +124,7 @@ def _set_input_data(span, kwargs, integration):
122124
"""
123125
Set input data for the span based on the provided keyword arguments for the anthropic message creation.
124126
"""
127+
system_prompt = kwargs.get("system")
125128
messages = kwargs.get("messages")
126129
if (
127130
messages is not None
@@ -130,18 +133,40 @@ def _set_input_data(span, kwargs, integration):
130133
and integration.include_prompts
131134
):
132135
normalized_messages = []
136+
if system_prompt:
137+
system_prompt_content = None # type: Optional[Union[str, List[dict[str, Any]]]]
138+
if isinstance(system_prompt, str):
139+
system_prompt_content = system_prompt
140+
elif isinstance(system_prompt, Iterable):
141+
system_prompt_content = []
142+
for item in system_prompt:
143+
if (
144+
isinstance(item, dict)
145+
and item.get("type") == "text"
146+
and item.get("text")
147+
):
148+
system_prompt_content.append(item.copy())
149+
150+
if system_prompt_content:
151+
normalized_messages.append(
152+
{
153+
"role": GEN_AI_ALLOWED_MESSAGE_ROLES.SYSTEM,
154+
"content": system_prompt_content,
155+
}
156+
)
157+
133158
for message in messages:
134159
if (
135-
message.get("role") == "user"
160+
message.get("role") == GEN_AI_ALLOWED_MESSAGE_ROLES.USER
136161
and "content" in message
137162
and isinstance(message["content"], (list, tuple))
138163
):
139164
for item in message["content"]:
140165
if item.get("type") == "tool_result":
141166
normalized_messages.append(
142167
{
143-
"role": "tool",
144-
"content": {
168+
"role": GEN_AI_ALLOWED_MESSAGE_ROLES.TOOL,
169+
"content": { # type: ignore[dict-item]
145170
"tool_use_id": item.get("tool_use_id"),
146171
"output": item.get("content"),
147172
},

sentry_sdk/integrations/google_genai/utils.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -442,19 +442,14 @@ def set_span_data_for_request(span, integration, model, contents, kwargs):
442442
if kwargs.get("stream", False):
443443
span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True)
444444

445-
config = kwargs.get("config")
446-
447-
if config is None:
448-
return
449-
450-
config = cast(GenerateContentConfig, config)
445+
config = kwargs.get("config") # type: Optional[GenerateContentConfig]
451446

452447
# Set input messages/prompts if PII is allowed
453448
if should_send_default_pii() and integration.include_prompts:
454449
messages = []
455450

456451
# Add system instruction if present
457-
if hasattr(config, "system_instruction"):
452+
if config and hasattr(config, "system_instruction"):
458453
system_instruction = config.system_instruction
459454
if system_instruction:
460455
system_text = extract_contents_text(system_instruction)
@@ -496,7 +491,7 @@ def set_span_data_for_request(span, integration, model, contents, kwargs):
496491
span.set_data(span_key, value)
497492

498493
# Set tools if available
499-
if hasattr(config, "tools"):
494+
if config is not None and hasattr(config, "tools"):
500495
tools = config.tools
501496
if tools:
502497
formatted_tools = _format_tools_for_span(tools)

sentry_sdk/integrations/langchain.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,9 @@ def on_llm_end(self, response, *, run_id, **kwargs):
443443

444444
if generation is not None:
445445
try:
446-
response_model = generation.generation_info.get("model_name")
446+
response_model = generation.message.response_metadata.get(
447+
"model_name"
448+
)
447449
if response_model is not None:
448450
span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, response_model)
449451
except AttributeError:

sentry_sdk/integrations/opentelemetry/span_processor.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
)
2424
from sentry_sdk.scope import add_global_event_processor
2525
from sentry_sdk.tracing import Transaction, Span as SentrySpan
26-
from sentry_sdk.utils import Dsn
2726

2827
from urllib3.util import parse_url as urlparse
2928

@@ -113,12 +112,7 @@ def on_start(self, otel_span, parent_context=None):
113112
# type: (OTelSpan, Optional[context_api.Context]) -> None
114113
client = get_client()
115114

116-
if not client.dsn:
117-
return
118-
119-
try:
120-
_ = Dsn(client.dsn)
121-
except Exception:
115+
if not client.parsed_dsn:
122116
return
123117

124118
if client.options["instrumenter"] != INSTRUMENTER.OTEL:
@@ -233,10 +227,8 @@ def _is_sentry_span(self, otel_span):
233227
otel_span_url = otel_span.attributes.get(SpanAttributes.HTTP_URL)
234228
otel_span_url = cast("Optional[str]", otel_span_url)
235229

236-
dsn_url = None
237-
client = get_client()
238-
if client.dsn:
239-
dsn_url = Dsn(client.dsn).netloc
230+
parsed_dsn = get_client().parsed_dsn
231+
dsn_url = parsed_dsn.netloc if parsed_dsn else None
240232

241233
if otel_span_url and dsn_url and dsn_url in otel_span_url:
242234
return True

sentry_sdk/tracing_utils.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -664,8 +664,10 @@ def from_options(cls, scope):
664664
if options.get("release"):
665665
sentry_items["release"] = options["release"]
666666

667-
if options.get("dsn"):
668-
sentry_items["public_key"] = Dsn(options["dsn"]).public_key
667+
if client.parsed_dsn:
668+
sentry_items["public_key"] = client.parsed_dsn.public_key
669+
if client.parsed_dsn.org_id:
670+
sentry_items["org_id"] = client.parsed_dsn.org_id
669671

670672
if options.get("traces_sample_rate"):
671673
sentry_items["sample_rate"] = str(options["traces_sample_rate"])
@@ -696,8 +698,10 @@ def populate_from_transaction(cls, transaction):
696698
if options.get("release"):
697699
sentry_items["release"] = options["release"]
698700

699-
if options.get("dsn"):
700-
sentry_items["public_key"] = Dsn(options["dsn"]).public_key
701+
if client.parsed_dsn:
702+
sentry_items["public_key"] = client.parsed_dsn.public_key
703+
if client.parsed_dsn.org_id:
704+
sentry_items["org_id"] = client.parsed_dsn.org_id
701705

702706
if (
703707
transaction.name

0 commit comments

Comments
 (0)