Skip to content

Commit 329ed33

Browse files
cursoragentszokeasaurusrex
authored andcommitted
fix(langchain): Make span_map an instance variable
1 parent 0e21fa2 commit 329ed33

File tree

4 files changed

+33
-10
lines changed

4 files changed

+33
-10
lines changed

sentry_sdk/integrations/langchain.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,9 @@ def __init__(self, span):
8787
class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
8888
"""Base callback handler that can be used to handle callbacks from langchain."""
8989

90-
span_map = OrderedDict() # type: OrderedDict[UUID, WatchedSpan]
91-
92-
max_span_map_size = 0
93-
9490
def __init__(self, max_span_map_size, include_prompts, tiktoken_encoding_name=None):
9591
# type: (int, bool, Optional[str]) -> None
92+
self.span_map = OrderedDict() # type: OrderedDict[UUID, WatchedSpan]
9693
self.max_span_map_size = max_span_map_size
9794
self.include_prompts = include_prompts
9895

sentry_sdk/scope.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
event_from_exception,
4343
exc_info_from_error,
4444
logger,
45+
safe_str,
4546
)
4647

4748
import typing
@@ -850,9 +851,9 @@ def set_tag(self, key, value):
850851
851852
:param key: Key of the tag to set.
852853
853-
:param value: Value of the tag to set.
854+
:param value: Value of the tag to set. Will be converted to string.
854855
"""
855-
self._tags[key] = value
856+
self._tags[key] = safe_str(value)
856857

857858
def set_tags(self, tags):
858859
# type: (Mapping[str, object]) -> None
@@ -869,9 +870,11 @@ def set_tags(self, tags):
869870
This method only modifies tag keys in the `tags` mapping passed to the method.
870871
`scope.set_tags({})` is, therefore, a no-op.
871872
872-
:param tags: A mapping of tag keys to tag values to set.
873+
:param tags: A mapping of tag keys to tag values to set. Values will be
874+
converted to strings.
873875
"""
874-
self._tags.update(tags)
876+
for key, value in tags.items():
877+
self.set_tag(key, value)
875878

876879
def remove_tag(self, key):
877880
# type: (str) -> None

sentry_sdk/tracing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
logger,
1414
nanosecond_time,
1515
should_be_treated_as_error,
16+
safe_str,
1617
)
1718

1819
from typing import TYPE_CHECKING
@@ -596,7 +597,7 @@ def to_baggage(self):
596597

597598
def set_tag(self, key, value):
598599
# type: (str, Any) -> None
599-
self._tags[key] = value
600+
self._tags[key] = safe_str(value)
600601

601602
def set_data(self, key, value):
602603
# type: (str, Any) -> None

tests/integrations/langchain/test_langchain.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
from langchain_core.outputs import ChatGenerationChunk
1818

1919
from sentry_sdk import start_transaction
20-
from sentry_sdk.integrations.langchain import LangchainIntegration
20+
from sentry_sdk.integrations.langchain import (
21+
LangchainIntegration,
22+
SentryLangchainCallback,
23+
)
2124
from langchain.agents import tool, AgentExecutor, create_openai_tools_agent
2225
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
2326

@@ -342,3 +345,22 @@ def test_span_origin(sentry_init, capture_events):
342345
assert event["contexts"]["trace"]["origin"] == "manual"
343346
for span in event["spans"]:
344347
assert span["origin"] == "auto.ai.langchain"
348+
349+
350+
def test_span_map_is_instance_variable():
351+
"""Test that each SentryLangchainCallback instance has its own span_map."""
352+
# Create two separate callback instances
353+
callback1 = SentryLangchainCallback(max_span_map_size=100, include_prompts=True)
354+
callback2 = SentryLangchainCallback(max_span_map_size=100, include_prompts=True)
355+
356+
# Verify they have different span_map instances
357+
assert (
358+
callback1.span_map is not callback2.span_map
359+
), "span_map should be an instance variable, not shared between instances"
360+
361+
362+
def test_span_map_not_class_attribute():
363+
"""Test that span_map is not accessible as a class attribute."""
364+
# This should raise AttributeError if span_map is properly an instance variable
365+
with pytest.raises(AttributeError):
366+
SentryLangchainCallback.span_map

0 commit comments

Comments
 (0)