Skip to content

Commit 220137b

Browse files
committed
missing file
1 parent 81e1d56 commit 220137b

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
"""
2+
Test for the fix of the issue where assistant message content is missing
3+
when tool calls are present in LangGraph/LangChain instrumentation.
4+
5+
This test reproduces the issue reported in GitHub where gen_ai.prompt.X.content
6+
attributes were missing for assistant messages that contained tool_calls.
7+
"""
8+
9+
import pytest
10+
from unittest.mock import Mock
11+
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
12+
from opentelemetry.instrumentation.langchain.span_utils import set_chat_request
13+
from opentelemetry.semconv_ai import SpanAttributes
14+
15+
16+
def test_assistant_message_with_tool_calls_includes_content():
17+
"""
18+
Test that when an assistant message has both content and tool_calls,
19+
both the content and tool_calls are included in the span attributes.
20+
21+
This addresses the issue where content was missing when tool_calls were present.
22+
"""
23+
# Create a mock span
24+
mock_span = Mock()
25+
mock_span.set_attribute = Mock()
26+
27+
# Create a mock span_holder
28+
mock_span_holder = Mock()
29+
mock_span_holder.request_model = None
30+
31+
# Create messages that reproduce the issue:
32+
# 1. User message
33+
# 2. Assistant message with BOTH content AND tool_calls
34+
messages = [[
35+
HumanMessage(content="what is the current time? First greet me."),
36+
AIMessage(
37+
content="Hello! Let me check the current time for you.",
38+
tool_calls=[{
39+
'id': 'call_qU7pH3EdQvzwkPyKPOdpgaKA',
40+
'name': 'get_current_time',
41+
'args': {}
42+
}]
43+
),
44+
ToolMessage(
45+
content="2025-08-15 08:15:21",
46+
tool_call_id="call_qU7pH3EdQvzwkPyKPOdpgaKA"
47+
),
48+
AIMessage(content="The current time is 2025-08-15 08:15:21")
49+
]]
50+
51+
# Call the function that was previously buggy
52+
set_chat_request(mock_span, {}, messages, {}, mock_span_holder)
53+
54+
# Verify that set_attribute was called with the expected attributes
55+
call_args = [call[0] for call in mock_span.set_attribute.call_args_list]
56+
57+
# Extract all attribute names and values
58+
attributes = {args[0]: args[1] for args in call_args}
59+
60+
# Check user message (prompt.0)
61+
assert f"{SpanAttributes.LLM_PROMPTS}.0.role" in attributes
62+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.role"] == "user"
63+
assert f"{SpanAttributes.LLM_PROMPTS}.0.content" in attributes
64+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.content"] == "what is the current time? First greet me."
65+
66+
# Check assistant message with tool calls (prompt.1)
67+
# This is the critical test - BOTH content AND tool_calls should be present
68+
assert f"{SpanAttributes.LLM_PROMPTS}.1.role" in attributes
69+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.1.role"] == "assistant"
70+
71+
# The fix should ensure that content is present even when tool_calls exist
72+
assert f"{SpanAttributes.LLM_PROMPTS}.1.content" in attributes
73+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.1.content"] == "Hello! Let me check the current time for you."
74+
75+
# Tool calls should also be present
76+
assert f"{SpanAttributes.LLM_PROMPTS}.1.tool_calls.0.id" in attributes
77+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.1.tool_calls.0.id"] == "call_qU7pH3EdQvzwkPyKPOdpgaKA"
78+
assert f"{SpanAttributes.LLM_PROMPTS}.1.tool_calls.0.name" in attributes
79+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.1.tool_calls.0.name"] == "get_current_time"
80+
81+
# Check tool message (prompt.2)
82+
assert f"{SpanAttributes.LLM_PROMPTS}.2.role" in attributes
83+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.2.role"] == "tool"
84+
assert f"{SpanAttributes.LLM_PROMPTS}.2.content" in attributes
85+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.2.content"] == "2025-08-15 08:15:21"
86+
assert f"{SpanAttributes.LLM_PROMPTS}.2.tool_call_id" in attributes
87+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.2.tool_call_id"] == "call_qU7pH3EdQvzwkPyKPOdpgaKA"
88+
89+
# Check final assistant message (prompt.3)
90+
assert f"{SpanAttributes.LLM_PROMPTS}.3.role" in attributes
91+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.3.role"] == "assistant"
92+
assert f"{SpanAttributes.LLM_PROMPTS}.3.content" in attributes
93+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.3.content"] == "The current time is 2025-08-15 08:15:21"
94+
95+
96+
def test_assistant_message_with_only_tool_calls_no_content():
97+
"""
98+
Test that when an assistant message has only tool_calls and no content,
99+
the tool_calls are still included and no content attribute is set.
100+
"""
101+
# Create a mock span
102+
mock_span = Mock()
103+
mock_span.set_attribute = Mock()
104+
105+
# Create a mock span_holder
106+
mock_span_holder = Mock()
107+
mock_span_holder.request_model = None
108+
109+
# Create message with only tool_calls, no content
110+
messages = [[
111+
AIMessage(
112+
content="", # Empty content
113+
tool_calls=[{
114+
'id': 'call_123',
115+
'name': 'some_tool',
116+
'args': {'param': 'value'}
117+
}]
118+
)
119+
]]
120+
121+
# Call the function
122+
set_chat_request(mock_span, {}, messages, {}, mock_span_holder)
123+
124+
# Verify that set_attribute was called with the expected attributes
125+
call_args = [call[0] for call in mock_span.set_attribute.call_args_list]
126+
127+
# Extract all attribute names and values
128+
attributes = {args[0]: args[1] for args in call_args}
129+
130+
# Check assistant message
131+
assert f"{SpanAttributes.LLM_PROMPTS}.0.role" in attributes
132+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.role"] == "assistant"
133+
134+
# Content should NOT be set when it's empty (due to _set_span_attribute logic)
135+
# This is expected behavior to avoid cluttering spans with empty values
136+
assert f"{SpanAttributes.LLM_PROMPTS}.0.content" not in attributes
137+
138+
# Tool calls should be present
139+
assert f"{SpanAttributes.LLM_PROMPTS}.0.tool_calls.0.id" in attributes
140+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.tool_calls.0.id"] == "call_123"
141+
assert f"{SpanAttributes.LLM_PROMPTS}.0.tool_calls.0.name" in attributes
142+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.tool_calls.0.name"] == "some_tool"
143+
144+
145+
def test_assistant_message_with_only_content_no_tool_calls():
146+
"""
147+
Test that when an assistant message has only content and no tool_calls,
148+
the content is included and no tool_calls attributes are set.
149+
"""
150+
# Create a mock span
151+
mock_span = Mock()
152+
mock_span.set_attribute = Mock()
153+
154+
# Create a mock span_holder
155+
mock_span_holder = Mock()
156+
mock_span_holder.request_model = None
157+
158+
# Create message with only content, no tool_calls
159+
messages = [[
160+
AIMessage(content="Just a regular response with no tool calls")
161+
]]
162+
163+
# Call the function
164+
set_chat_request(mock_span, {}, messages, {}, mock_span_holder)
165+
166+
# Verify that set_attribute was called with the expected attributes
167+
call_args = [call[0] for call in mock_span.set_attribute.call_args_list]
168+
169+
# Extract all attribute names and values
170+
attributes = {args[0]: args[1] for args in call_args}
171+
172+
# Check assistant message
173+
assert f"{SpanAttributes.LLM_PROMPTS}.0.role" in attributes
174+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.role"] == "assistant"
175+
176+
# Content should be present
177+
assert f"{SpanAttributes.LLM_PROMPTS}.0.content" in attributes
178+
assert attributes[f"{SpanAttributes.LLM_PROMPTS}.0.content"] == "Just a regular response with no tool calls"
179+
180+
# No tool call attributes should be present
181+
tool_call_attributes = [attr for attr in attributes.keys() if "tool_calls" in attr]
182+
assert len(tool_call_attributes) == 0

0 commit comments

Comments
 (0)