From 374f4678bf56df88d0ac3f6b3a6b3f76656094df Mon Sep 17 00:00:00 2001 From: Simon Hellmayr Date: Fri, 19 Sep 2025 10:22:57 +0200 Subject: [PATCH] fix(langgraph): add explicit exception handling --- sentry_sdk/integrations/langgraph.py | 38 ++++++++++++++++--- .../integrations/langgraph/test_langgraph.py | 18 +++++++-- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/sentry_sdk/integrations/langgraph.py b/sentry_sdk/integrations/langgraph.py index df3941bb13..c066b3e1df 100644 --- a/sentry_sdk/integrations/langgraph.py +++ b/sentry_sdk/integrations/langgraph.py @@ -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: @@ -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"]: @@ -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) @@ -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) diff --git a/tests/integrations/langgraph/test_langgraph.py b/tests/integrations/langgraph/test_langgraph.py index 5e35f772f5..b6707de11f 100644 --- a/tests/integrations/langgraph/test_langgraph.py +++ b/tests/integrations/langgraph/test_langgraph.py @@ -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): @@ -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):