sentry-sdk 2.39.0__py2.py3-none-any.whl → 2.41.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sentry-sdk might be problematic. Click here for more details.

Files changed (52) hide show
  1. sentry_sdk/_metrics.py +81 -0
  2. sentry_sdk/_metrics_batcher.py +156 -0
  3. sentry_sdk/_types.py +27 -22
  4. sentry_sdk/ai/__init__.py +7 -0
  5. sentry_sdk/ai/utils.py +48 -0
  6. sentry_sdk/client.py +87 -36
  7. sentry_sdk/consts.py +15 -9
  8. sentry_sdk/envelope.py +31 -17
  9. sentry_sdk/feature_flags.py +0 -1
  10. sentry_sdk/hub.py +17 -9
  11. sentry_sdk/integrations/__init__.py +1 -0
  12. sentry_sdk/integrations/anthropic.py +10 -2
  13. sentry_sdk/integrations/asgi.py +3 -2
  14. sentry_sdk/integrations/dramatiq.py +89 -31
  15. sentry_sdk/integrations/grpc/aio/client.py +2 -1
  16. sentry_sdk/integrations/grpc/client.py +3 -4
  17. sentry_sdk/integrations/langchain.py +29 -5
  18. sentry_sdk/integrations/langgraph.py +5 -3
  19. sentry_sdk/integrations/launchdarkly.py +0 -1
  20. sentry_sdk/integrations/litellm.py +251 -0
  21. sentry_sdk/integrations/litestar.py +4 -4
  22. sentry_sdk/integrations/logging.py +1 -1
  23. sentry_sdk/integrations/loguru.py +1 -1
  24. sentry_sdk/integrations/openai.py +3 -2
  25. sentry_sdk/integrations/openai_agents/spans/ai_client.py +4 -1
  26. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +10 -2
  27. sentry_sdk/integrations/openai_agents/utils.py +60 -19
  28. sentry_sdk/integrations/pure_eval.py +3 -1
  29. sentry_sdk/integrations/spark/spark_driver.py +2 -1
  30. sentry_sdk/integrations/sqlalchemy.py +2 -6
  31. sentry_sdk/integrations/starlette.py +1 -3
  32. sentry_sdk/integrations/starlite.py +4 -4
  33. sentry_sdk/integrations/threading.py +52 -8
  34. sentry_sdk/integrations/wsgi.py +3 -2
  35. sentry_sdk/logger.py +1 -1
  36. sentry_sdk/profiler/utils.py +2 -6
  37. sentry_sdk/scope.py +6 -3
  38. sentry_sdk/serializer.py +1 -3
  39. sentry_sdk/session.py +4 -2
  40. sentry_sdk/sessions.py +4 -2
  41. sentry_sdk/tracing.py +36 -33
  42. sentry_sdk/tracing_utils.py +1 -3
  43. sentry_sdk/transport.py +9 -26
  44. sentry_sdk/types.py +3 -0
  45. sentry_sdk/utils.py +22 -4
  46. {sentry_sdk-2.39.0.dist-info → sentry_sdk-2.41.0.dist-info}/METADATA +3 -1
  47. {sentry_sdk-2.39.0.dist-info → sentry_sdk-2.41.0.dist-info}/RECORD +51 -49
  48. sentry_sdk/metrics.py +0 -965
  49. {sentry_sdk-2.39.0.dist-info → sentry_sdk-2.41.0.dist-info}/WHEEL +0 -0
  50. {sentry_sdk-2.39.0.dist-info → sentry_sdk-2.41.0.dist-info}/entry_points.txt +0 -0
  51. {sentry_sdk-2.39.0.dist-info → sentry_sdk-2.41.0.dist-info}/licenses/LICENSE +0 -0
  52. {sentry_sdk-2.39.0.dist-info → sentry_sdk-2.41.0.dist-info}/top_level.txt +0 -0
sentry_sdk/envelope.py CHANGED
@@ -57,25 +57,29 @@ class Envelope:
57
57
  )
58
58
 
59
59
  def add_event(
60
- self, event # type: Event
60
+ self,
61
+ event, # type: Event
61
62
  ):
62
63
  # type: (...) -> None
63
64
  self.add_item(Item(payload=PayloadRef(json=event), type="event"))
64
65
 
65
66
  def add_transaction(
66
- self, transaction # type: Event
67
+ self,
68
+ transaction, # type: Event
67
69
  ):
68
70
  # type: (...) -> None
69
71
  self.add_item(Item(payload=PayloadRef(json=transaction), type="transaction"))
70
72
 
71
73
  def add_profile(
72
- self, profile # type: Any
74
+ self,
75
+ profile, # type: Any
73
76
  ):
74
77
  # type: (...) -> None
75
78
  self.add_item(Item(payload=PayloadRef(json=profile), type="profile"))
76
79
 
77
80
  def add_profile_chunk(
78
- self, profile_chunk # type: Any
81
+ self,
82
+ profile_chunk, # type: Any
79
83
  ):
80
84
  # type: (...) -> None
81
85
  self.add_item(
@@ -87,13 +91,15 @@ class Envelope:
87
91
  )
88
92
 
89
93
  def add_checkin(
90
- self, checkin # type: Any
94
+ self,
95
+ checkin, # type: Any
91
96
  ):
92
97
  # type: (...) -> None
93
98
  self.add_item(Item(payload=PayloadRef(json=checkin), type="check_in"))
94
99
 
95
100
  def add_session(
96
- self, session # type: Union[Session, Any]
101
+ self,
102
+ session, # type: Union[Session, Any]
97
103
  ):
98
104
  # type: (...) -> None
99
105
  if isinstance(session, Session):
@@ -101,13 +107,15 @@ class Envelope:
101
107
  self.add_item(Item(payload=PayloadRef(json=session), type="session"))
102
108
 
103
109
  def add_sessions(
104
- self, sessions # type: Any
110
+ self,
111
+ sessions, # type: Any
105
112
  ):
106
113
  # type: (...) -> None
107
114
  self.add_item(Item(payload=PayloadRef(json=sessions), type="sessions"))
108
115
 
109
116
  def add_item(
110
- self, item # type: Item
117
+ self,
118
+ item, # type: Item
111
119
  ):
112
120
  # type: (...) -> None
113
121
  self.items.append(item)
@@ -133,7 +141,8 @@ class Envelope:
133
141
  return iter(self.items)
134
142
 
135
143
  def serialize_into(
136
- self, f # type: Any
144
+ self,
145
+ f, # type: Any
137
146
  ):
138
147
  # type: (...) -> None
139
148
  f.write(json_dumps(self.headers))
@@ -149,7 +158,8 @@ class Envelope:
149
158
 
150
159
  @classmethod
151
160
  def deserialize_from(
152
- cls, f # type: Any
161
+ cls,
162
+ f, # type: Any
153
163
  ):
154
164
  # type: (...) -> Envelope
155
165
  headers = parse_json(f.readline())
@@ -163,7 +173,8 @@ class Envelope:
163
173
 
164
174
  @classmethod
165
175
  def deserialize(
166
- cls, bytes # type: bytes
176
+ cls,
177
+ bytes, # type: bytes
167
178
  ):
168
179
  # type: (...) -> Envelope
169
180
  return cls.deserialize_from(io.BytesIO(bytes))
@@ -274,14 +285,14 @@ class Item:
274
285
  return "error"
275
286
  elif ty == "log":
276
287
  return "log_item"
288
+ elif ty == "trace_metric":
289
+ return "trace_metric"
277
290
  elif ty == "client_report":
278
291
  return "internal"
279
292
  elif ty == "profile":
280
293
  return "profile"
281
294
  elif ty == "profile_chunk":
282
295
  return "profile_chunk"
283
- elif ty == "statsd":
284
- return "metric_bucket"
285
296
  elif ty == "check_in":
286
297
  return "monitor"
287
298
  else:
@@ -307,7 +318,8 @@ class Item:
307
318
  return None
308
319
 
309
320
  def serialize_into(
310
- self, f # type: Any
321
+ self,
322
+ f, # type: Any
311
323
  ):
312
324
  # type: (...) -> None
313
325
  headers = dict(self.headers)
@@ -326,7 +338,8 @@ class Item:
326
338
 
327
339
  @classmethod
328
340
  def deserialize_from(
329
- cls, f # type: Any
341
+ cls,
342
+ f, # type: Any
330
343
  ):
331
344
  # type: (...) -> Optional[Item]
332
345
  line = f.readline().rstrip()
@@ -341,7 +354,7 @@ class Item:
341
354
  # if no length was specified we need to read up to the end of line
342
355
  # and remove it (if it is present, i.e. not the very last char in an eof terminated envelope)
343
356
  payload = f.readline().rstrip(b"\n")
344
- if headers.get("type") in ("event", "transaction", "metric_buckets"):
357
+ if headers.get("type") in ("event", "transaction"):
345
358
  rv = cls(headers=headers, payload=PayloadRef(json=parse_json(payload)))
346
359
  else:
347
360
  rv = cls(headers=headers, payload=payload)
@@ -349,7 +362,8 @@ class Item:
349
362
 
350
363
  @classmethod
351
364
  def deserialize(
352
- cls, bytes # type: bytes
365
+ cls,
366
+ bytes, # type: bytes
353
367
  ):
354
368
  # type: (...) -> Optional[Item]
355
369
  return cls.deserialize_from(io.BytesIO(bytes))
@@ -15,7 +15,6 @@ DEFAULT_FLAG_CAPACITY = 100
15
15
 
16
16
 
17
17
  class FlagBuffer:
18
-
19
18
  def __init__(self, capacity):
20
19
  # type: (int) -> None
21
20
  self.capacity = capacity
sentry_sdk/hub.py CHANGED
@@ -205,7 +205,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
205
205
  scope._isolation_scope.set(old_isolation_scope)
206
206
 
207
207
  def run(
208
- self, callback # type: Callable[[], T]
208
+ self,
209
+ callback, # type: Callable[[], T]
209
210
  ):
210
211
  # type: (...) -> T
211
212
  """
@@ -219,7 +220,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
219
220
  return callback()
220
221
 
221
222
  def get_integration(
222
- self, name_or_class # type: Union[str, Type[Integration]]
223
+ self,
224
+ name_or_class, # type: Union[str, Type[Integration]]
223
225
  ):
224
226
  # type: (...) -> Any
225
227
  """
@@ -277,7 +279,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
277
279
  return self._last_event_id
278
280
 
279
281
  def bind_client(
280
- self, new # type: Optional[BaseClient]
282
+ self,
283
+ new, # type: Optional[BaseClient]
281
284
  ):
282
285
  # type: (...) -> None
283
286
  """
@@ -430,7 +433,7 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
430
433
  transaction=None,
431
434
  instrumenter=INSTRUMENTER.SENTRY,
432
435
  custom_sampling_context=None,
433
- **kwargs
436
+ **kwargs,
434
437
  ):
435
438
  # type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan]
436
439
  """
@@ -487,14 +490,16 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
487
490
 
488
491
  @overload
489
492
  def push_scope(
490
- self, callback=None # type: Optional[None]
493
+ self,
494
+ callback=None, # type: Optional[None]
491
495
  ):
492
496
  # type: (...) -> ContextManager[Scope]
493
497
  pass
494
498
 
495
499
  @overload
496
500
  def push_scope( # noqa: F811
497
- self, callback # type: Callable[[Scope], None]
501
+ self,
502
+ callback, # type: Callable[[Scope], None]
498
503
  ):
499
504
  # type: (...) -> None
500
505
  pass
@@ -540,14 +545,16 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
540
545
 
541
546
  @overload
542
547
  def configure_scope(
543
- self, callback=None # type: Optional[None]
548
+ self,
549
+ callback=None, # type: Optional[None]
544
550
  ):
545
551
  # type: (...) -> ContextManager[Scope]
546
552
  pass
547
553
 
548
554
  @overload
549
555
  def configure_scope( # noqa: F811
550
- self, callback # type: Callable[[Scope], None]
556
+ self,
557
+ callback, # type: Callable[[Scope], None]
551
558
  ):
552
559
  # type: (...) -> None
553
560
  pass
@@ -587,7 +594,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
587
594
  return inner()
588
595
 
589
596
  def start_session(
590
- self, session_mode="application" # type: str
597
+ self,
598
+ session_mode="application", # type: str
591
599
  ):
592
600
  # type: (...) -> None
593
601
  """
@@ -146,6 +146,7 @@ _MIN_VERSIONS = {
146
146
  "langchain": (0, 1, 0),
147
147
  "langgraph": (0, 6, 6),
148
148
  "launchdarkly": (9, 8, 0),
149
+ "litellm": (1, 77, 5),
149
150
  "loguru": (0, 7, 0),
150
151
  "openai": (1, 0, 0),
151
152
  "openai_agents": (0, 0, 19),
@@ -3,7 +3,11 @@ from typing import TYPE_CHECKING
3
3
 
4
4
  import sentry_sdk
5
5
  from sentry_sdk.ai.monitoring import record_token_usage
6
- from sentry_sdk.ai.utils import set_data_normalized, get_start_span_function
6
+ from sentry_sdk.ai.utils import (
7
+ set_data_normalized,
8
+ normalize_message_roles,
9
+ get_start_span_function,
10
+ )
7
11
  from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
8
12
  from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
9
13
  from sentry_sdk.scope import should_send_default_pii
@@ -140,8 +144,12 @@ def _set_input_data(span, kwargs, integration):
140
144
  else:
141
145
  normalized_messages.append(message)
142
146
 
147
+ role_normalized_messages = normalize_message_roles(normalized_messages)
143
148
  set_data_normalized(
144
- span, SPANDATA.GEN_AI_REQUEST_MESSAGES, normalized_messages, unpack=False
149
+ span,
150
+ SPANDATA.GEN_AI_REQUEST_MESSAGES,
151
+ role_normalized_messages,
152
+ unpack=False,
145
153
  )
146
154
 
147
155
  set_data_normalized(
@@ -233,14 +233,15 @@ class SentryAsgiMiddleware:
233
233
  if transaction:
234
234
  transaction.set_tag("asgi.type", ty)
235
235
 
236
- with (
236
+ transaction_context = (
237
237
  sentry_sdk.start_transaction(
238
238
  transaction,
239
239
  custom_sampling_context={"asgi_scope": scope},
240
240
  )
241
241
  if transaction is not None
242
242
  else nullcontext()
243
- ):
243
+ )
244
+ with transaction_context:
244
245
  try:
245
246
 
246
247
  async def _sentry_wrapped_send(event):
@@ -1,18 +1,31 @@
1
1
  import json
2
2
 
3
3
  import sentry_sdk
4
- from sentry_sdk.integrations import Integration
4
+ from sentry_sdk.consts import OP, SPANSTATUS
5
+ from sentry_sdk.api import continue_trace, get_baggage, get_traceparent
6
+ from sentry_sdk.integrations import Integration, DidNotEnable
5
7
  from sentry_sdk.integrations._wsgi_common import request_body_within_bounds
8
+ from sentry_sdk.tracing import (
9
+ BAGGAGE_HEADER_NAME,
10
+ SENTRY_TRACE_HEADER_NAME,
11
+ TransactionSource,
12
+ )
6
13
  from sentry_sdk.utils import (
7
14
  AnnotatedValue,
8
15
  capture_internal_exceptions,
9
16
  event_from_exception,
10
17
  )
18
+ from typing import TypeVar
19
+
20
+ R = TypeVar("R")
11
21
 
12
- from dramatiq.broker import Broker # type: ignore
13
- from dramatiq.message import Message # type: ignore
14
- from dramatiq.middleware import Middleware, default_middleware # type: ignore
15
- from dramatiq.errors import Retry # type: ignore
22
+ try:
23
+ from dramatiq.broker import Broker
24
+ from dramatiq.middleware import Middleware, default_middleware
25
+ from dramatiq.errors import Retry
26
+ from dramatiq.message import Message
27
+ except ImportError:
28
+ raise DidNotEnable("Dramatiq is not installed")
16
29
 
17
30
  from typing import TYPE_CHECKING
18
31
 
@@ -34,10 +47,12 @@ class DramatiqIntegration(Integration):
34
47
  """
35
48
 
36
49
  identifier = "dramatiq"
50
+ origin = f"auto.queue.{identifier}"
37
51
 
38
52
  @staticmethod
39
53
  def setup_once():
40
54
  # type: () -> None
55
+
41
56
  _patch_dramatiq_broker()
42
57
 
43
58
 
@@ -85,22 +100,54 @@ class SentryMiddleware(Middleware): # type: ignore[misc]
85
100
  DramatiqIntegration.
86
101
  """
87
102
 
88
- def before_process_message(self, broker, message):
89
- # type: (Broker, Message) -> None
103
+ SENTRY_HEADERS_NAME = "_sentry_headers"
104
+
105
+ def before_enqueue(self, broker, message, delay):
106
+ # type: (Broker, Message[R], int) -> None
90
107
  integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
91
108
  if integration is None:
92
109
  return
93
110
 
94
- message._scope_manager = sentry_sdk.new_scope()
95
- message._scope_manager.__enter__()
111
+ message.options[self.SENTRY_HEADERS_NAME] = {
112
+ BAGGAGE_HEADER_NAME: get_baggage(),
113
+ SENTRY_TRACE_HEADER_NAME: get_traceparent(),
114
+ }
115
+
116
+ def before_process_message(self, broker, message):
117
+ # type: (Broker, Message[R]) -> None
118
+ integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
119
+ if integration is None:
120
+ return
96
121
 
97
- scope = sentry_sdk.get_current_scope()
98
- scope.set_transaction_name(message.actor_name)
122
+ message._scope_manager = sentry_sdk.isolation_scope()
123
+ scope = message._scope_manager.__enter__()
124
+ scope.clear_breadcrumbs()
99
125
  scope.set_extra("dramatiq_message_id", message.message_id)
100
126
  scope.add_event_processor(_make_message_event_processor(message, integration))
101
127
 
128
+ sentry_headers = message.options.get(self.SENTRY_HEADERS_NAME) or {}
129
+ if "retries" in message.options:
130
+ # start new trace in case of retrying
131
+ sentry_headers = {}
132
+
133
+ transaction = continue_trace(
134
+ sentry_headers,
135
+ name=message.actor_name,
136
+ op=OP.QUEUE_TASK_DRAMATIQ,
137
+ source=TransactionSource.TASK,
138
+ origin=DramatiqIntegration.origin,
139
+ )
140
+ transaction.set_status(SPANSTATUS.OK)
141
+ sentry_sdk.start_transaction(
142
+ transaction,
143
+ name=message.actor_name,
144
+ op=OP.QUEUE_TASK_DRAMATIQ,
145
+ source=TransactionSource.TASK,
146
+ )
147
+ transaction.__enter__()
148
+
102
149
  def after_process_message(self, broker, message, *, result=None, exception=None):
103
- # type: (Broker, Message, Any, Optional[Any], Optional[Exception]) -> None
150
+ # type: (Broker, Message[R], Optional[Any], Optional[Exception]) -> None
104
151
  integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
105
152
  if integration is None:
106
153
  return
@@ -108,27 +155,38 @@ class SentryMiddleware(Middleware): # type: ignore[misc]
108
155
  actor = broker.get_actor(message.actor_name)
109
156
  throws = message.options.get("throws") or actor.options.get("throws")
110
157
 
111
- try:
112
- if (
113
- exception is not None
114
- and not (throws and isinstance(exception, throws))
115
- and not isinstance(exception, Retry)
116
- ):
117
- event, hint = event_from_exception(
118
- exception,
119
- client_options=sentry_sdk.get_client().options,
120
- mechanism={
121
- "type": DramatiqIntegration.identifier,
122
- "handled": False,
123
- },
124
- )
125
- sentry_sdk.capture_event(event, hint=hint)
126
- finally:
127
- message._scope_manager.__exit__(None, None, None)
158
+ scope_manager = message._scope_manager
159
+ transaction = sentry_sdk.get_current_scope().transaction
160
+ if not transaction:
161
+ return None
162
+
163
+ is_event_capture_required = (
164
+ exception is not None
165
+ and not (throws and isinstance(exception, throws))
166
+ and not isinstance(exception, Retry)
167
+ )
168
+ if not is_event_capture_required:
169
+ # normal transaction finish
170
+ transaction.__exit__(None, None, None)
171
+ scope_manager.__exit__(None, None, None)
172
+ return
173
+
174
+ event, hint = event_from_exception(
175
+ exception, # type: ignore[arg-type]
176
+ client_options=sentry_sdk.get_client().options,
177
+ mechanism={
178
+ "type": DramatiqIntegration.identifier,
179
+ "handled": False,
180
+ },
181
+ )
182
+ sentry_sdk.capture_event(event, hint=hint)
183
+ # transaction error
184
+ transaction.__exit__(type(exception), exception, None)
185
+ scope_manager.__exit__(type(exception), exception, None)
128
186
 
129
187
 
130
188
  def _make_message_event_processor(message, integration):
131
- # type: (Message, DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]]
189
+ # type: (Message[R], DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]]
132
190
 
133
191
  def inner(event, hint):
134
192
  # type: (Event, Hint) -> Optional[Event]
@@ -142,7 +200,7 @@ def _make_message_event_processor(message, integration):
142
200
 
143
201
  class DramatiqMessageExtractor:
144
202
  def __init__(self, message):
145
- # type: (Message) -> None
203
+ # type: (Message[R]) -> None
146
204
  self.message_data = dict(message.asdict())
147
205
 
148
206
  def content_length(self):
@@ -65,7 +65,8 @@ class SentryUnaryUnaryClientInterceptor(ClientInterceptor, UnaryUnaryClientInter
65
65
 
66
66
 
67
67
  class SentryUnaryStreamClientInterceptor(
68
- ClientInterceptor, UnaryStreamClientInterceptor # type: ignore
68
+ ClientInterceptor,
69
+ UnaryStreamClientInterceptor, # type: ignore
69
70
  ):
70
71
  async def intercept_unary_stream(
71
72
  self,
@@ -19,7 +19,8 @@ except ImportError:
19
19
 
20
20
 
21
21
  class ClientInterceptor(
22
- grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor # type: ignore
22
+ grpc.UnaryUnaryClientInterceptor, # type: ignore
23
+ grpc.UnaryStreamClientInterceptor, # type: ignore
23
24
  ):
24
25
  _is_intercepted = False
25
26
 
@@ -60,9 +61,7 @@ class ClientInterceptor(
60
61
  client_call_details
61
62
  )
62
63
 
63
- response = continuation(
64
- client_call_details, request
65
- ) # type: UnaryStreamCall
64
+ response = continuation(client_call_details, request) # type: UnaryStreamCall
66
65
  # Setting code on unary-stream leads to execution getting stuck
67
66
  # span.set_data("code", response.code().name)
68
67
 
@@ -4,7 +4,12 @@ from functools import wraps
4
4
 
5
5
  import sentry_sdk
6
6
  from sentry_sdk.ai.monitoring import set_ai_pipeline_name
7
- from sentry_sdk.ai.utils import set_data_normalized, get_start_span_function
7
+ from sentry_sdk.ai.utils import (
8
+ GEN_AI_ALLOWED_MESSAGE_ROLES,
9
+ normalize_message_roles,
10
+ set_data_normalized,
11
+ get_start_span_function,
12
+ )
8
13
  from sentry_sdk.consts import OP, SPANDATA
9
14
  from sentry_sdk.integrations import DidNotEnable, Integration
10
15
  from sentry_sdk.scope import should_send_default_pii
@@ -209,8 +214,18 @@ class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
209
214
  _set_tools_on_span(span, all_params.get("tools"))
210
215
 
211
216
  if should_send_default_pii() and self.include_prompts:
217
+ normalized_messages = [
218
+ {
219
+ "role": GEN_AI_ALLOWED_MESSAGE_ROLES.USER,
220
+ "content": {"type": "text", "text": prompt},
221
+ }
222
+ for prompt in prompts
223
+ ]
212
224
  set_data_normalized(
213
- span, SPANDATA.GEN_AI_REQUEST_MESSAGES, prompts, unpack=False
225
+ span,
226
+ SPANDATA.GEN_AI_REQUEST_MESSAGES,
227
+ normalized_messages,
228
+ unpack=False,
214
229
  )
215
230
 
216
231
  def on_chat_model_start(self, serialized, messages, *, run_id, **kwargs):
@@ -262,6 +277,8 @@ class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
262
277
  normalized_messages.append(
263
278
  self._normalize_langchain_message(message)
264
279
  )
280
+ normalized_messages = normalize_message_roles(normalized_messages)
281
+
265
282
  set_data_normalized(
266
283
  span,
267
284
  SPANDATA.GEN_AI_REQUEST_MESSAGES,
@@ -554,7 +571,6 @@ def _simplify_langchain_tools(tools):
554
571
  for tool in tools:
555
572
  try:
556
573
  if isinstance(tool, dict):
557
-
558
574
  if "function" in tool and isinstance(tool["function"], dict):
559
575
  func = tool["function"]
560
576
  simplified_tool = {
@@ -741,8 +757,12 @@ def _wrap_agent_executor_invoke(f):
741
757
  and should_send_default_pii()
742
758
  and integration.include_prompts
743
759
  ):
760
+ normalized_messages = normalize_message_roles([input])
744
761
  set_data_normalized(
745
- span, SPANDATA.GEN_AI_REQUEST_MESSAGES, [input], unpack=False
762
+ span,
763
+ SPANDATA.GEN_AI_REQUEST_MESSAGES,
764
+ normalized_messages,
765
+ unpack=False,
746
766
  )
747
767
 
748
768
  output = result.get("output")
@@ -792,8 +812,12 @@ def _wrap_agent_executor_stream(f):
792
812
  and should_send_default_pii()
793
813
  and integration.include_prompts
794
814
  ):
815
+ normalized_messages = normalize_message_roles([input])
795
816
  set_data_normalized(
796
- span, SPANDATA.GEN_AI_REQUEST_MESSAGES, [input], unpack=False
817
+ span,
818
+ SPANDATA.GEN_AI_REQUEST_MESSAGES,
819
+ normalized_messages,
820
+ unpack=False,
797
821
  )
798
822
 
799
823
  # Run the agent
@@ -2,7 +2,7 @@ from functools import wraps
2
2
  from typing import Any, Callable, List, Optional
3
3
 
4
4
  import sentry_sdk
5
- from sentry_sdk.ai.utils import set_data_normalized
5
+ from sentry_sdk.ai.utils import set_data_normalized, normalize_message_roles
6
6
  from sentry_sdk.consts import OP, SPANDATA
7
7
  from sentry_sdk.integrations import DidNotEnable, Integration
8
8
  from sentry_sdk.scope import should_send_default_pii
@@ -180,10 +180,11 @@ def _wrap_pregel_invoke(f):
180
180
  ):
181
181
  input_messages = _parse_langgraph_messages(args[0])
182
182
  if input_messages:
183
+ normalized_input_messages = normalize_message_roles(input_messages)
183
184
  set_data_normalized(
184
185
  span,
185
186
  SPANDATA.GEN_AI_REQUEST_MESSAGES,
186
- input_messages,
187
+ normalized_input_messages,
187
188
  unpack=False,
188
189
  )
189
190
 
@@ -230,10 +231,11 @@ def _wrap_pregel_ainvoke(f):
230
231
  ):
231
232
  input_messages = _parse_langgraph_messages(args[0])
232
233
  if input_messages:
234
+ normalized_input_messages = normalize_message_roles(input_messages)
233
235
  set_data_normalized(
234
236
  span,
235
237
  SPANDATA.GEN_AI_REQUEST_MESSAGES,
236
- input_messages,
238
+ normalized_input_messages,
237
239
  unpack=False,
238
240
  )
239
241
 
@@ -44,7 +44,6 @@ class LaunchDarklyIntegration(Integration):
44
44
 
45
45
 
46
46
  class LaunchDarklyHook(Hook):
47
-
48
47
  @property
49
48
  def metadata(self):
50
49
  # type: () -> Metadata