A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 10, 06:30 UTC
[responsesAPI] add unit test for optional function tool call id (#32036)
Signed-off-by: Andrew Xia <axia@fb.com> Co-authored-by: Andrew Xia <axia@fb.com>
Andrew Xia committed
Jan 12, 2026 at 19:14 UTC
a307ac073432d3f224c58a54d363fa93f6a659f4
1 file changed
+118
tests/entrypoints/test_responses_utils.py
+118
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
+from openai.types.chat import ChatCompletionMessageParam
6
from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
7
from openai.types.responses.response_function_tool_call_output_item import (
8
ResponseFunctionToolCallOutputItem,
@@ -14,8 +15,10 @@ from openai.types.responses.response_reasoning_item import (
15
Summary,
16
)
17
18
+from vllm.entrypoints.constants import MCP_PREFIX
19
from vllm.entrypoints.responses_utils import (
20
_construct_single_message_from_response_item,
21
+ _maybe_combine_reasoning_and_tool_call,
22
construct_chat_messages_with_tool_call,
23
convert_tool_responses_to_completions_format,
24
)
@@ -160,3 +163,118 @@ class TestResponsesUtils:
163
formatted_item = _construct_single_message_from_response_item(output_item)
164
assert formatted_item["role"] == "assistant"
165
assert formatted_item["content"] == "dongyi"
166
+
167
+
168
+class TestMaybeCombineReasoningAndToolCall:
169
+ """Tests for _maybe_combine_reasoning_and_tool_call function."""
170
+
171
+ def test_returns_none_when_item_id_is_none(self):
172
+ """
173
+ Test fix from PR #31999: when item.id is None, should return None
174
+ instead of raising TypeError on startswith().
175
+ """
176
+ item = ResponseFunctionToolCall(
177
+ type="function_call",
178
+ id=None, # This was causing TypeError before the fix
179
+ call_id="call_123",
180
+ name="test_function",
181
+ arguments="{}",
182
+ )
183
+ messages: list[ChatCompletionMessageParam] = []
184
+
185
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
186
+
187
+ assert result is None
188
+
189
+ def test_returns_none_when_id_does_not_start_with_mcp_prefix(self):
190
+ """Test that non-MCP tool calls are not combined."""
191
+ item = ResponseFunctionToolCall(
192
+ type="function_call",
193
+ id="regular_id", # Does not start with MCP_PREFIX
194
+ call_id="call_123",
195
+ name="test_function",
196
+ arguments="{}",
197
+ )
198
+ messages = [{"role": "assistant", "reasoning": "some reasoning"}]
199
+
200
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
201
+
202
+ assert result is None
203
+
204
+ def test_returns_none_when_last_message_is_not_assistant(self):
205
+ """Test that non-assistant last message returns None."""
206
+ item = ResponseFunctionToolCall(
207
+ type="function_call",
208
+ id=f"{MCP_PREFIX}tool_id",
209
+ call_id="call_123",
210
+ name="test_function",
211
+ arguments="{}",
212
+ )
213
+ messages = [{"role": "user", "content": "hello"}]
214
+
215
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
216
+
217
+ assert result is None
218
+
219
+ def test_returns_none_when_last_message_has_no_reasoning(self):
220
+ """Test that assistant message without reasoning returns None."""
221
+ item = ResponseFunctionToolCall(
222
+ type="function_call",
223
+ id=f"{MCP_PREFIX}tool_id",
224
+ call_id="call_123",
225
+ name="test_function",
226
+ arguments="{}",
227
+ )
228
+ messages = [{"role": "assistant", "content": "some content"}]
229
+
230
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
231
+
232
+ assert result is None
233
+
234
+ def test_combines_reasoning_and_mcp_tool_call(self):
235
+ """Test successful combination of reasoning message and MCP tool call."""
236
+ item = ResponseFunctionToolCall(
237
+ type="function_call",
238
+ id=f"{MCP_PREFIX}tool_id",
239
+ call_id="call_123",
240
+ name="test_function",
241
+ arguments='{"arg": "value"}',
242
+ )
243
+ messages = [{"role": "assistant", "reasoning": "I need to call this tool"}]
244
+
245
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
246
+
247
+ assert result is not None
248
+ assert result["role"] == "assistant"
249
+ assert result["reasoning"] == "I need to call this tool"
250
+ assert "tool_calls" in result
251
+ assert len(result["tool_calls"]) == 1
252
+ assert result["tool_calls"][0]["id"] == "call_123"
253
+ assert result["tool_calls"][0]["function"]["name"] == "test_function"
254
+ assert result["tool_calls"][0]["function"]["arguments"] == '{"arg": "value"}'
255
+ assert result["tool_calls"][0]["type"] == "function"
256
+
257
+ def test_returns_none_for_non_function_tool_call_type(self):
258
+ """Test that non-ResponseFunctionToolCall items return None."""
259
+ # Pass a dict instead of ResponseFunctionToolCall
260
+ item = {"type": "message", "content": "hello"}
261
+ messages = [{"role": "assistant", "reasoning": "some reasoning"}]
262
+
263
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
264
+
265
+ assert result is None
266
+
267
+ def test_returns_none_when_id_is_empty_string(self):
268
+ """Test that empty string id returns None (falsy check)."""
269
+ item = ResponseFunctionToolCall(
270
+ type="function_call",
271
+ id="", # Empty string is falsy
272
+ call_id="call_123",
273
+ name="test_function",
274
+ arguments="{}",
275
+ )
276
+ messages = [{"role": "assistant", "reasoning": "some reasoning"}]
277
+
278
+ result = _maybe_combine_reasoning_and_tool_call(item, messages)
279
+
280
+ assert result is None