Skip to content

Commit 808c180

Browse files
authored
fix(AI): Make agents integrations set the span status in case of error (#4820)
### Description Make all AI related integrations set the `span.status` to `"error"` and also the `status` of the transaction/root_span to `"error"` in case of an error. This is also the OTel semantic convention for failing spans. #### Issues * resolves: #4752 * resolves: PY-1825
1 parent e52c3b1 commit 808c180

File tree

16 files changed

+231
-16
lines changed

16 files changed

+231
-16
lines changed

sentry_sdk/consts.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,18 +765,20 @@ class SPANSTATUS:
765765
CANCELLED = "cancelled"
766766
DATA_LOSS = "data_loss"
767767
DEADLINE_EXCEEDED = "deadline_exceeded"
768+
ERROR = "error" # OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
768769
FAILED_PRECONDITION = "failed_precondition"
769770
INTERNAL_ERROR = "internal_error"
770771
INVALID_ARGUMENT = "invalid_argument"
771772
NOT_FOUND = "not_found"
772-
OK = "ok"
773+
OK = "ok" # HTTP 200 and OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
773774
OUT_OF_RANGE = "out_of_range"
774775
PERMISSION_DENIED = "permission_denied"
775776
RESOURCE_EXHAUSTED = "resource_exhausted"
776777
UNAUTHENTICATED = "unauthenticated"
777778
UNAVAILABLE = "unavailable"
778779
UNIMPLEMENTED = "unimplemented"
779780
UNKNOWN_ERROR = "unknown_error"
781+
UNSET = "unset" # OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
780782

781783

782784
class OP:

sentry_sdk/integrations/anthropic.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import sentry_sdk
55
from sentry_sdk.ai.monitoring import record_token_usage
66
from sentry_sdk.ai.utils import set_data_normalized, get_start_span_function
7-
from sentry_sdk.consts import OP, SPANDATA
7+
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
88
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
99
from sentry_sdk.scope import should_send_default_pii
10+
from sentry_sdk.tracing_utils import set_span_errored
1011
from sentry_sdk.utils import (
1112
capture_internal_exceptions,
1213
event_from_exception,
@@ -52,6 +53,8 @@ def setup_once():
5253

5354
def _capture_exception(exc):
5455
# type: (Any) -> None
56+
set_span_errored()
57+
5558
event, hint = event_from_exception(
5659
exc,
5760
client_options=sentry_sdk.get_client().options,
@@ -357,7 +360,13 @@ def _sentry_patched_create_sync(*args, **kwargs):
357360
integration = sentry_sdk.get_client().get_integration(AnthropicIntegration)
358361
kwargs["integration"] = integration
359362

360-
return _execute_sync(f, *args, **kwargs)
363+
try:
364+
return _execute_sync(f, *args, **kwargs)
365+
finally:
366+
span = sentry_sdk.get_current_span()
367+
if span is not None and span.status == SPANSTATUS.ERROR:
368+
with capture_internal_exceptions():
369+
span.__exit__(None, None, None)
361370

362371
return _sentry_patched_create_sync
363372

@@ -390,6 +399,12 @@ async def _sentry_patched_create_async(*args, **kwargs):
390399
integration = sentry_sdk.get_client().get_integration(AnthropicIntegration)
391400
kwargs["integration"] = integration
392401

393-
return await _execute_async(f, *args, **kwargs)
402+
try:
403+
return await _execute_async(f, *args, **kwargs)
404+
finally:
405+
span = sentry_sdk.get_current_span()
406+
if span is not None and span.status == SPANSTATUS.ERROR:
407+
with capture_internal_exceptions():
408+
span.__exit__(None, None, None)
394409

395410
return _sentry_patched_create_async

sentry_sdk/integrations/cohere.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from typing import TYPE_CHECKING
99

10+
from sentry_sdk.tracing_utils import set_span_errored
11+
1012
if TYPE_CHECKING:
1113
from typing import Any, Callable, Iterator
1214
from sentry_sdk.tracing import Span
@@ -84,6 +86,8 @@ def setup_once():
8486

8587
def _capture_exception(exc):
8688
# type: (Any) -> None
89+
set_span_errored()
90+
8791
event, hint = event_from_exception(
8892
exc,
8993
client_options=sentry_sdk.get_client().options,

sentry_sdk/integrations/huggingface_hub.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from sentry_sdk.consts import OP, SPANDATA
88
from sentry_sdk.integrations import DidNotEnable, Integration
99
from sentry_sdk.scope import should_send_default_pii
10+
from sentry_sdk.tracing_utils import set_span_errored
1011
from sentry_sdk.utils import (
1112
capture_internal_exceptions,
1213
event_from_exception,
@@ -52,6 +53,8 @@ def setup_once():
5253

5354
def _capture_exception(exc):
5455
# type: (Any) -> None
56+
set_span_errored()
57+
5558
event, hint = event_from_exception(
5659
exc,
5760
client_options=sentry_sdk.get_client().options,
@@ -127,8 +130,6 @@ def new_huggingface_task(*args, **kwargs):
127130
try:
128131
res = f(*args, **kwargs)
129132
except Exception as e:
130-
# Error Handling
131-
span.set_status("error")
132133
_capture_exception(e)
133134
span.__exit__(None, None, None)
134135
raise e from None

sentry_sdk/integrations/langchain.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from sentry_sdk.consts import OP, SPANDATA
99
from sentry_sdk.integrations import DidNotEnable, Integration
1010
from sentry_sdk.scope import should_send_default_pii
11-
from sentry_sdk.tracing import Span
12-
from sentry_sdk.tracing_utils import _get_value
11+
from sentry_sdk.tracing_utils import _get_value, set_span_errored
1312
from sentry_sdk.utils import logger, capture_internal_exceptions
1413

1514
from typing import TYPE_CHECKING
@@ -26,6 +25,7 @@
2625
Union,
2726
)
2827
from uuid import UUID
28+
from sentry_sdk.tracing import Span
2929

3030

3131
try:
@@ -116,7 +116,7 @@ def _handle_error(self, run_id, error):
116116

117117
span_data = self.span_map[run_id]
118118
span = span_data.span
119-
span.set_status("unknown")
119+
set_span_errored(span)
120120

121121
sentry_sdk.capture_exception(error, span.scope)
122122

sentry_sdk/integrations/openai.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from sentry_sdk.consts import SPANDATA
88
from sentry_sdk.integrations import DidNotEnable, Integration
99
from sentry_sdk.scope import should_send_default_pii
10+
from sentry_sdk.tracing_utils import set_span_errored
1011
from sentry_sdk.utils import (
1112
capture_internal_exceptions,
1213
event_from_exception,
@@ -83,6 +84,8 @@ def _capture_exception(exc, manual_span_cleanup=True):
8384
# Close an eventually open span
8485
# We need to do this by hand because we are not using the start_span context manager
8586
current_span = sentry_sdk.get_current_span()
87+
set_span_errored(current_span)
88+
8689
if manual_span_cleanup and current_span is not None:
8790
current_span.__exit__(None, None, None)
8891

sentry_sdk/integrations/openai_agents/spans/execute_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def update_execute_tool_span(span, agent, tool, result):
4242
if isinstance(result, str) and result.startswith(
4343
"An error occurred while running the tool"
4444
):
45-
span.set_status(SPANSTATUS.INTERNAL_ERROR)
45+
span.set_status(SPANSTATUS.ERROR)
4646

4747
if should_send_default_pii():
4848
span.set_data(SPANDATA.GEN_AI_TOOL_OUTPUT, result)

sentry_sdk/integrations/openai_agents/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sentry_sdk.consts import SPANDATA
44
from sentry_sdk.integrations import DidNotEnable
55
from sentry_sdk.scope import should_send_default_pii
6+
from sentry_sdk.tracing_utils import set_span_errored
67
from sentry_sdk.utils import event_from_exception, safe_serialize
78

89
from typing import TYPE_CHECKING
@@ -20,6 +21,8 @@
2021

2122
def _capture_exception(exc):
2223
# type: (Any) -> None
24+
set_span_errored()
25+
2326
event, hint = event_from_exception(
2427
exc,
2528
client_options=sentry_sdk.get_client().options,

sentry_sdk/tracing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ def __enter__(self):
416416
def __exit__(self, ty, value, tb):
417417
# type: (Optional[Any], Optional[Any], Optional[Any]) -> None
418418
if value is not None and should_be_treated_as_error(ty, value):
419-
self.set_status(SPANSTATUS.INTERNAL_ERROR)
419+
if self.status != SPANSTATUS.ERROR:
420+
self.set_status(SPANSTATUS.INTERNAL_ERROR)
420421

421422
with capture_internal_exceptions():
422423
scope, old_span = self._context_manager_state

sentry_sdk/tracing_utils.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import uuid
1212

1313
import sentry_sdk
14-
from sentry_sdk.consts import OP, SPANDATA, SPANTEMPLATE
14+
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS, SPANTEMPLATE
1515
from sentry_sdk.utils import (
1616
capture_internal_exceptions,
1717
filename_for_module,
@@ -892,6 +892,19 @@ def get_current_span(scope=None):
892892
return current_span
893893

894894

895+
def set_span_errored(span=None):
896+
# type: (Optional[Span]) -> None
897+
"""
898+
Set the status of the current or given span to ERROR.
899+
Also sets the status of the transaction (root span) to ERROR.
900+
"""
901+
span = span or get_current_span()
902+
if span is not None:
903+
span.set_status(SPANSTATUS.ERROR)
904+
if span.containing_transaction is not None:
905+
span.containing_transaction.set_status(SPANSTATUS.ERROR)
906+
907+
895908
def _generate_sample_rand(
896909
trace_id, # type: Optional[str]
897910
*,

0 commit comments

Comments
 (0)