Skip to content
Open
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
15 changes: 11 additions & 4 deletions sentry_sdk/integrations/langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ class LangchainIntegration(Integration):
identifier = "langchain"
origin = f"auto.ai.{identifier}"

_ignored_exceptions: "set[type[Exception]]" = set()
Comment thread
alexander-alderman-webb marked this conversation as resolved.

def __init__(
self: "LangchainIntegration",
include_prompts: bool = True,
Expand Down Expand Up @@ -262,17 +264,22 @@ def gc_span_map(self) -> None:
self._exit_span(span, run_id)

def _handle_error(self, run_id: "UUID", error: "Any") -> None:
is_ignored = isinstance(error, tuple(LangchainIntegration._ignored_exceptions))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this achieves what we want, a more concise way that this can be written is:

Suggested change
is_ignored = isinstance(error, tuple(LangchainIntegration._ignored_exceptions))
is_ignored = type(error) in LangchainIntegration._ignored_exceptions


with capture_internal_exceptions():
if not run_id or run_id not in self.span_map:
return

span = self.span_map[run_id]

sentry_sdk.capture_exception(
error, span._scope if isinstance(span, StreamedSpan) else span.scope
)
if is_ignored:
span.__exit__(None, None, None)
else:
sentry_sdk.capture_exception(
error, span._scope if isinstance(span, StreamedSpan) else span.scope
)
span.__exit__(type(error), error, error.__traceback__)

span.__exit__(type(error), error, error.__traceback__)
del self.span_map[run_id]

def _normalize_langchain_message(self, message: "BaseMessage") -> "Any":
Expand Down
5 changes: 5 additions & 0 deletions sentry_sdk/integrations/langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
)
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import DidNotEnable, Integration

# This is fine because langgraph depends on langchain-base, and LangchainIntegration only imports from langchain-base.
from sentry_sdk.integrations.langchain import LangchainIntegration
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.tracing_utils import (
Expand All @@ -19,6 +22,7 @@
from sentry_sdk.utils import safe_serialize

try:
from langgraph.errors import GraphBubbleUp
from langgraph.graph import StateGraph
from langgraph.pregel import Pregel
except ImportError:
Expand All @@ -34,6 +38,7 @@ def __init__(self: "LanggraphIntegration", include_prompts: bool = True) -> None

@staticmethod
def setup_once() -> None:
LangchainIntegration._ignored_exceptions.add(GraphBubbleUp)
# LangGraph lets users create agents using a StateGraph or the Functional API.
# StateGraphs are then compiled to a CompiledStateGraph. Both CompiledStateGraph and
# the functional API execute on a Pregel instance. Pregel is the runtime for the graph
Expand Down
27 changes: 27 additions & 0 deletions tests/integrations/langgraph/test_langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from unittest.mock import MagicMock, patch

import pytest
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import HumanMessage
from langchain_core.outputs import ChatResult
from langgraph.errors import GraphBubbleUp

import sentry_sdk
from sentry_sdk import start_transaction
Expand Down Expand Up @@ -125,6 +129,15 @@ async def ainvoke(self, state, config=None):
return {"messages": [MockMessage("Async Pregel response")]}


class InterruptingChatModel(BaseChatModel):
@property
def _llm_type(self) -> str:
return "interrupting-chat-model"

def _generate(self, messages, stop=None, run_manager=None, **kwargs) -> ChatResult:
raise GraphBubbleUp("interrupt")


def test_langgraph_integration_init():
"""Test LanggraphIntegration initialization with different parameters."""
integration = LanggraphIntegration()
Expand Down Expand Up @@ -2104,3 +2117,17 @@ def original_invoke(self, *args, **kwargs):
assert len(parsed_messages) == 1
assert "small message 5" in str(parsed_messages[0])
assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5


def test_graph_bubble_up_ignored(sentry_init, capture_items):
sentry_init(
integrations=[LanggraphIntegration()],
)

events = capture_items("event")

model = InterruptingChatModel()
with pytest.raises(GraphBubbleUp):
model.invoke([HumanMessage(content="hi")])

assert len(events) == 0
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Loading