Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions sentry_sdk/integrations/langgraph.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from functools import wraps
from typing import Any, Callable, List, Optional
from typing import TYPE_CHECKING

import sentry_sdk
from sentry_sdk.ai.utils import set_data_normalized
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import safe_serialize
from sentry_sdk.utils import (
event_from_exception,
safe_serialize,
)

if TYPE_CHECKING:
from typing import Any, Callable, List, Optional


try:
Expand Down Expand Up @@ -41,6 +47,20 @@ def setup_once():
Pregel.ainvoke = _wrap_pregel_ainvoke(Pregel.ainvoke)


def _capture_exception(exc):
# type: (Any) -> None
span = sentry_sdk.get_current_span()
if span is not None:
span.set_status(SPANSTATUS.ERROR)

event, hint = event_from_exception(
exc,
client_options=sentry_sdk.get_client().options,
mechanism={"type": "langgraph", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)


def _get_graph_name(graph_obj):
# type: (Any) -> Optional[str]
for attr in ["name", "graph_name", "__name__", "_name"]:
Expand Down Expand Up @@ -187,7 +207,11 @@ def new_invoke(self, *args, **kwargs):
unpack=False,
)

result = f(self, *args, **kwargs)
try:
result = f(self, *args, **kwargs)
except Exception as exc:
_capture_exception(exc)
raise exc from None

_set_response_attributes(span, input_messages, result, integration)

Expand Down Expand Up @@ -237,7 +261,11 @@ async def new_ainvoke(self, *args, **kwargs):
unpack=False,
)

result = await f(self, *args, **kwargs)
try:
result = await f(self, *args, **kwargs)
except Exception as exc:
_capture_exception(exc)
raise exc from None

_set_response_attributes(span, input_messages, result, integration)

Expand Down
18 changes: 14 additions & 4 deletions tests/integrations/langgraph/test_langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,14 +398,19 @@ def original_invoke(self, *args, **kwargs):
wrapped_invoke = _wrap_pregel_invoke(original_invoke)
wrapped_invoke(pregel, test_state)

tx = events[0]
error_event, tx = events
assert error_event["level"] == "error"
assert "Graph execution failed" in str(
error_event["exception"]["values"][0]["value"]
)

invoke_spans = [
span for span in tx["spans"] if span["op"] == OP.GEN_AI_INVOKE_AGENT
]
assert len(invoke_spans) == 1

invoke_span = invoke_spans[0]
assert invoke_span.get("tags", {}).get("status") == "internal_error"
assert invoke_span.get("tags", {}).get("status") == "error"


def test_pregel_ainvoke_error(sentry_init, capture_events):
Expand All @@ -432,14 +437,19 @@ async def run_error_test():

asyncio.run(run_error_test())

tx = events[0]
error_event, tx = events
assert error_event["level"] == "error"
assert "Async graph execution failed" in str(
error_event["exception"]["values"][0]["value"]
)

invoke_spans = [
span for span in tx["spans"] if span["op"] == OP.GEN_AI_INVOKE_AGENT
]
assert len(invoke_spans) == 1

invoke_span = invoke_spans[0]
assert invoke_span.get("tags", {}).get("status") == "internal_error"
assert invoke_span.get("tags", {}).get("status") == "error"


def test_span_origin(sentry_init, capture_events):
Expand Down
Loading