Skip to content

Commit 843c062

Browse files
authored
fix(ai): add message truncation in langchain (#4950)
1 parent b11c2f2 commit 843c062

File tree

2 files changed

+116
-23
lines changed

2 files changed

+116
-23
lines changed

sentry_sdk/integrations/langchain.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
normalize_message_roles,
1010
set_data_normalized,
1111
get_start_span_function,
12+
truncate_and_annotate_messages,
1213
)
1314
from sentry_sdk.consts import OP, SPANDATA
1415
from sentry_sdk.integrations import DidNotEnable, Integration
@@ -221,12 +222,17 @@ def on_llm_start(
221222
}
222223
for prompt in prompts
223224
]
224-
set_data_normalized(
225-
span,
226-
SPANDATA.GEN_AI_REQUEST_MESSAGES,
227-
normalized_messages,
228-
unpack=False,
225+
scope = sentry_sdk.get_current_scope()
226+
messages_data = truncate_and_annotate_messages(
227+
normalized_messages, span, scope
229228
)
229+
if messages_data is not None:
230+
set_data_normalized(
231+
span,
232+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
233+
messages_data,
234+
unpack=False,
235+
)
230236

231237
def on_chat_model_start(self, serialized, messages, *, run_id, **kwargs):
232238
# type: (SentryLangchainCallback, Dict[str, Any], List[List[BaseMessage]], UUID, Any) -> Any
@@ -278,13 +284,17 @@ def on_chat_model_start(self, serialized, messages, *, run_id, **kwargs):
278284
self._normalize_langchain_message(message)
279285
)
280286
normalized_messages = normalize_message_roles(normalized_messages)
281-
282-
set_data_normalized(
283-
span,
284-
SPANDATA.GEN_AI_REQUEST_MESSAGES,
285-
normalized_messages,
286-
unpack=False,
287+
scope = sentry_sdk.get_current_scope()
288+
messages_data = truncate_and_annotate_messages(
289+
normalized_messages, span, scope
287290
)
291+
if messages_data is not None:
292+
set_data_normalized(
293+
span,
294+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
295+
messages_data,
296+
unpack=False,
297+
)
288298

289299
def on_chat_model_end(self, response, *, run_id, **kwargs):
290300
# type: (SentryLangchainCallback, LLMResult, UUID, Any) -> Any
@@ -758,12 +768,17 @@ def new_invoke(self, *args, **kwargs):
758768
and integration.include_prompts
759769
):
760770
normalized_messages = normalize_message_roles([input])
761-
set_data_normalized(
762-
span,
763-
SPANDATA.GEN_AI_REQUEST_MESSAGES,
764-
normalized_messages,
765-
unpack=False,
771+
scope = sentry_sdk.get_current_scope()
772+
messages_data = truncate_and_annotate_messages(
773+
normalized_messages, span, scope
766774
)
775+
if messages_data is not None:
776+
set_data_normalized(
777+
span,
778+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
779+
messages_data,
780+
unpack=False,
781+
)
767782

768783
output = result.get("output")
769784
if (
@@ -813,12 +828,17 @@ def new_stream(self, *args, **kwargs):
813828
and integration.include_prompts
814829
):
815830
normalized_messages = normalize_message_roles([input])
816-
set_data_normalized(
817-
span,
818-
SPANDATA.GEN_AI_REQUEST_MESSAGES,
819-
normalized_messages,
820-
unpack=False,
831+
scope = sentry_sdk.get_current_scope()
832+
messages_data = truncate_and_annotate_messages(
833+
normalized_messages, span, scope
821834
)
835+
if messages_data is not None:
836+
set_data_normalized(
837+
span,
838+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
839+
messages_data,
840+
unpack=False,
841+
)
822842

823843
# Run the agent
824844
result = f(self, *args, **kwargs)

tests/integrations/langchain/test_langchain.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from typing import List, Optional, Any, Iterator
23
from unittest import mock
34
from unittest.mock import Mock, patch
@@ -884,8 +885,6 @@ def test_langchain_message_role_mapping(sentry_init, capture_events):
884885

885886
# Parse the message data (might be JSON string)
886887
if isinstance(messages_data, str):
887-
import json
888-
889888
try:
890889
messages = json.loads(messages_data)
891890
except json.JSONDecodeError:
@@ -958,3 +957,77 @@ def test_langchain_message_role_normalization_units():
958957
assert normalized[3]["role"] == "system" # system unchanged
959958
assert "role" not in normalized[4] # Message without role unchanged
960959
assert normalized[5] == "string message" # String message unchanged
960+
961+
962+
def test_langchain_message_truncation(sentry_init, capture_events):
963+
"""Test that large messages are truncated properly in Langchain integration."""
964+
from langchain_core.outputs import LLMResult, Generation
965+
966+
sentry_init(
967+
integrations=[LangchainIntegration(include_prompts=True)],
968+
traces_sample_rate=1.0,
969+
send_default_pii=True,
970+
)
971+
events = capture_events()
972+
973+
callback = SentryLangchainCallback(max_span_map_size=100, include_prompts=True)
974+
975+
run_id = "12345678-1234-1234-1234-123456789012"
976+
serialized = {"_type": "openai-chat", "model_name": "gpt-3.5-turbo"}
977+
978+
large_content = (
979+
"This is a very long message that will exceed our size limits. " * 1000
980+
)
981+
prompts = [
982+
"small message 1",
983+
large_content,
984+
large_content,
985+
"small message 4",
986+
"small message 5",
987+
]
988+
989+
with start_transaction():
990+
callback.on_llm_start(
991+
serialized=serialized,
992+
prompts=prompts,
993+
run_id=run_id,
994+
invocation_params={
995+
"temperature": 0.7,
996+
"max_tokens": 100,
997+
"model": "gpt-3.5-turbo",
998+
},
999+
)
1000+
1001+
response = LLMResult(
1002+
generations=[[Generation(text="The response")]],
1003+
llm_output={
1004+
"token_usage": {
1005+
"total_tokens": 25,
1006+
"prompt_tokens": 10,
1007+
"completion_tokens": 15,
1008+
}
1009+
},
1010+
)
1011+
callback.on_llm_end(response=response, run_id=run_id)
1012+
1013+
assert len(events) > 0
1014+
tx = events[0]
1015+
assert tx["type"] == "transaction"
1016+
1017+
llm_spans = [
1018+
span for span in tx.get("spans", []) if span.get("op") == "gen_ai.pipeline"
1019+
]
1020+
assert len(llm_spans) > 0
1021+
1022+
llm_span = llm_spans[0]
1023+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES in llm_span["data"]
1024+
1025+
messages_data = llm_span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]
1026+
assert isinstance(messages_data, str)
1027+
1028+
parsed_messages = json.loads(messages_data)
1029+
assert isinstance(parsed_messages, list)
1030+
assert len(parsed_messages) == 2
1031+
assert "small message 4" in str(parsed_messages[0])
1032+
assert "small message 5" in str(parsed_messages[1])
1033+
assert tx["_meta"]["spans"]["0"]["data"]["gen_ai.request.messages"][""]["len"] == 5

0 commit comments

Comments
 (0)