Files
vllm/tests/entrypoints/openai/test_tool_calls_serialization.py
+86 431cea3eea [Bugfix] Fix tool_calls Iterable consumed when debug logging is enabled (#34844)
Signed-off-by: Wojciech Wais <[email protected]>
Signed-off-by: mgoin <[email protected]>
Signed-off-by: Xinyu Chen <[email protected]>
Signed-off-by: Ekagra Ranjan <[email protected]>
Signed-off-by: Rishi Puri <[email protected]>
Signed-off-by: Jaebok Lee <[email protected]>
Signed-off-by: DarkLight1337 <[email protected]>
Signed-off-by: yuwei <[email protected]>
Signed-off-by: Artem Perevedentsev <[email protected]>
Signed-off-by: Ibrahim Arshad <[email protected]>
Signed-off-by: Li <[email protected]>
Signed-off-by: chaunceyjiang <[email protected]>
Signed-off-by: Kunshang Ji <[email protected]>
Signed-off-by: Kunshang Ji <[email protected]>
Signed-off-by: R <[email protected]>
Signed-off-by: Lucas Wilkinson <[email protected]>
Signed-off-by: lkm2835 <[email protected]>
Signed-off-by: Ronen Schaffer <[email protected]>
Signed-off-by: vnadathur <[email protected]>
Signed-off-by: WorldExplored <[email protected]>
Signed-off-by: Srreyansh Sethi <[email protected]>
Signed-off-by: Isotr0py <[email protected]>
Signed-off-by: Elham Harirpoush <[email protected]>
Signed-off-by: Yan Ma <[email protected]>
Signed-off-by: Nick Hill <[email protected]>
Signed-off-by: jackcfwang <[email protected]>
Signed-off-by: Chendi Xue <[email protected]>
Signed-off-by: Injae Ryou <[email protected]>
Signed-off-by: Richard Zou <[email protected]>
Signed-off-by: milesial <[email protected]>
Signed-off-by: Elvir Crncevic <[email protected]>
Signed-off-by: whx-sjtu <[email protected]>
Signed-off-by: Lalithnarayan C <[email protected]>
Signed-off-by: PatchouliTaisa <[email protected]>
Signed-off-by: jatseng-ai <[email protected]>
Signed-off-by: jatseng-ai <[email protected]>
Signed-off-by: Matthias Gehre <[email protected]>
Signed-off-by: xaguilar-amd <[email protected]>
Signed-off-by: rdondeti <[email protected]>
Signed-off-by: Ravitez Dondeti <[email protected]>
Signed-off-by: NickLucche <[email protected]>
Signed-off-by: Peter Nguyen <[email protected]>
Signed-off-by: wang.yuqi <[email protected]>
Signed-off-by: zhuhaoran <[email protected]>
Signed-off-by: Jee Jee Li <[email protected]>
Signed-off-by: tjtanaa <[email protected]>
Signed-off-by: Jesus Federico <[email protected]>
Signed-off-by: manu <[email protected]>
Signed-off-by: ZhanqiuHu <[email protected]>
Signed-off-by: Yifan Zong <[email protected]>
Signed-off-by: Rahul-Tuli <[email protected]>
Signed-off-by: Fynn Schmitt-Ulms <[email protected]>
Signed-off-by: Harry Mellor <[email protected]>
Signed-off-by: Michael Goin <[email protected]>
Signed-off-by: Benjamin Chislett <[email protected]>
Signed-off-by: Tianyu Guo <[email protected]>
Signed-off-by: leeyongjun <[email protected]>
Signed-off-by: Ziying Tao <[email protected]>
Signed-off-by: jiang1.li <[email protected]>
Signed-off-by: Vibhav Agarwal <[email protected]>
Signed-off-by: ShubyM <[email protected]>
Signed-off-by: wzhao18 <[email protected]>
Signed-off-by: Itay Etelis <[email protected]>
Signed-off-by: EdalatiAli <[email protected]>
Signed-off-by: Andreas Karatzas <[email protected]>
Signed-off-by: r266-tech <[email protected]>
Signed-off-by: Roger Wang <[email protected]>
Signed-off-by: Martin Hickey <[email protected]>
Signed-off-by: Mark McLoughlin <[email protected]>
Signed-off-by: Animesh Jain <[email protected]>
Signed-off-by: Yongye Zhu <[email protected]>
Signed-off-by: zhxchen17 <[email protected]>
Signed-off-by: EricccYang <[email protected]>
Signed-off-by: Kaicheng Yang <[email protected]>
Signed-off-by: baoloongmao <[email protected]>
Signed-off-by: sihao.li <[email protected]>
Signed-off-by: sfeng33 <[email protected]>
Signed-off-by: Yufeng He <[email protected]>
Signed-off-by: Zhu, Zufang <[email protected]>
Signed-off-by: Tihomir Elek <[email protected]>
Signed-off-by: yiliu30 <[email protected]>
Signed-off-by: yewentao256 <[email protected]>
Signed-off-by: Santino Ramos <[email protected]>
Signed-off-by: haosdent <[email protected]>
Signed-off-by: JartX <[email protected]>
Signed-off-by: George-ao <[email protected]>
Signed-off-by: Yuyi Ao <[email protected]>
Signed-off-by: Tyler Michael Smith <[email protected]>
Signed-off-by: Mukesh Baphna <[email protected]>
Signed-off-by: Pedram Razavi <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Michael Goin <[email protected]>
Co-authored-by: Xinyu Chen <[email protected]>
Co-authored-by: Ekagra Ranjan <[email protected]>
Co-authored-by: Rishi Puri <[email protected]>
Co-authored-by: zzaebok <[email protected]>
Co-authored-by: Cyrus Leung <[email protected]>
Co-authored-by: Yuwei An <[email protected]>
Co-authored-by: yuwei <[email protected]>
Co-authored-by: Artem Perevedentsev <[email protected]>
Co-authored-by: Ibrahim Arshad <[email protected]>
Co-authored-by: Chuan (Richard) Li <[email protected]>
Co-authored-by: Chauncey <[email protected]>
Co-authored-by: Kunshang Ji <[email protected]>
Co-authored-by: Ganesh R <[email protected]>
Co-authored-by: Lucas Wilkinson <[email protected]>
Co-authored-by: Robert Shaw <[email protected]>
Co-authored-by: Kyungmin Lee <[email protected]>
Co-authored-by: Ronen Schaffer <[email protected]>
Co-authored-by: Srreyansh Sethi <[email protected]>
Co-authored-by: vnadathur <[email protected]>
Co-authored-by: vnadathur <[email protected]>
Co-authored-by: Isotr0py <[email protected]>
Co-authored-by: Elham <[email protected]>
Co-authored-by: Yan Ma <[email protected]>
Co-authored-by: Nick Hill <[email protected]>
Co-authored-by: Chaofan Wang <[email protected]>
Co-authored-by: Chendi.Xue <[email protected]>
Co-authored-by: Injae Ryou <[email protected]>
Co-authored-by: Richard Zou <[email protected]>
Co-authored-by: milesial <[email protected]>
Co-authored-by: Elvir Crnčević <[email protected]>
Co-authored-by: Claude Sonnet 4 <[email protected]>
Co-authored-by: Hexiang Wang <[email protected]>
Co-authored-by: Lalithnarayan C <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Luka Govedič <[email protected]>
Co-authored-by: PatchyTIS <[email protected]>
Co-authored-by: PatchouliTaisa <[email protected]>
Co-authored-by: jatseng-ai <[email protected]>
Co-authored-by: Matthias Gehre <[email protected]>
Co-authored-by: xaguilar-amd <[email protected]>
Co-authored-by: Ravitez Dondeti <[email protected]>
Co-authored-by: Nicolò Lucchesi <[email protected]>
Co-authored-by: Peter Nguyen <[email protected]>
Co-authored-by: wang.yuqi <[email protected]>
Co-authored-by: zhrrr <[email protected]>
Co-authored-by: Jee Jee Li <[email protected]>
Co-authored-by: TJian <[email protected]>
Co-authored-by: Jesus Federico <[email protected]>
Co-authored-by: Manu <[email protected]>
Co-authored-by: zhanqiuhu <[email protected]>
Co-authored-by: yzong-rh <[email protected]>
Co-authored-by: Fynn Schmitt-Ulms <[email protected]>
Co-authored-by: Rahul-Tuli <[email protected]>
Co-authored-by: Harry Mellor <[email protected]>
Co-authored-by: Benjamin Chislett <[email protected]>
Co-authored-by: Tianyu Guo <[email protected]>
Co-authored-by: Lee Yongjun <[email protected]>
Co-authored-by: z1ying <[email protected]>
Co-authored-by: Li, Jiang <[email protected]>
Co-authored-by: Vibhav Agarwal <[email protected]>
Co-authored-by: vibhav-agarwal <[email protected]>
Co-authored-by: ShubyM <[email protected]>
Co-authored-by: Wei Zhao <[email protected]>
Co-authored-by: Itay Etelis <[email protected]>
Co-authored-by: Itay Etelis <[email protected]>
Co-authored-by: EdalatiAli <[email protected]>
Co-authored-by: Andreas Karatzas <[email protected]>
Co-authored-by: r266-tech <[email protected]>
Co-authored-by: Roger Wang <[email protected]>
Co-authored-by: Martin Hickey <[email protected]>
Co-authored-by: Or Ozeri <[email protected]>
Co-authored-by: Mark McLoughlin <[email protected]>
Co-authored-by: Le Yang <[email protected]>
Co-authored-by: Animesh Jain <[email protected]>
Co-authored-by: Yongye Zhu <[email protected]>
Co-authored-by: Zhengxu Chen <[email protected]>
Co-authored-by: Kaicheng Yang <[email protected]>
Co-authored-by: maobaolong <[email protected]>
Co-authored-by: sihao_li <[email protected]>
Co-authored-by: Flora Feng <[email protected]>
Co-authored-by: Yufeng He <[email protected]>
Co-authored-by: zofia <[email protected]>
Co-authored-by: Tihomir Elek <[email protected]>
Co-authored-by: Yi Liu <[email protected]>
Co-authored-by: Wentao Ye <[email protected]>
Co-authored-by: Santino Ramos <[email protected]>
Co-authored-by: haosdent <[email protected]>
Co-authored-by: JartX <[email protected]>
Co-authored-by: Yuyi Ao <[email protected]>
Co-authored-by: Tyler Michael Smith <[email protected]>
Co-authored-by: mukesh-hai <[email protected]>
Co-authored-by: Pedram Razavi <[email protected]>
2026-04-15 01:32:47 -07:00

151 lines
5.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Unit tests for tool_calls Iterable → list materialisation.
Regression tests for https://github.com/vllm-project/vllm/issues/34792.
Setting VLLM_LOGGING_LEVEL=debug caused tool calling to break for Mistral
models because:
1. The OpenAI Python SDK types tool_calls as Iterable[...] in
ChatCompletionAssistantMessageParam.
2. Pydantic v2, when validating from Python objects (not from raw JSON),
wraps Iterable fields in a one-shot lazy iterator.
3. Debug logging called model_dump_json() which consumed that iterator.
4. The Mistral tokenizer then saw empty tool_calls and raised
"ValueError: Unexpected tool call id ...".
"""
import pytest
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
def _make_tool_call(tc_id: str, name: str, args: str) -> dict:
return {
"id": tc_id,
"type": "function",
"function": {"name": name, "arguments": args},
}
def _make_request(messages: list) -> ChatCompletionRequest:
return ChatCompletionRequest(
model="test-model",
messages=messages,
)
def test_tool_calls_list_preserved_after_model_dump():
"""tool_calls in assistant messages must be readable after model_dump_json.
When the request is built from Python dicts (as in the Anthropic → OpenAI
conversion path), Pydantic v2 previously wrapped the Iterable tool_calls
in a one-shot iterator. model_dump_json() consumed it, leaving subsequent
readers (e.g. the Mistral tokenizer) with an empty sequence.
"""
tool_call = _make_tool_call("call_abc123", "get_weather", '{"city": "Paris"}')
messages = [
{"role": "user", "content": "What is the weather in Paris?"},
{"role": "assistant", "content": None, "tool_calls": [tool_call]},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": '{"temperature": 20}',
},
]
req = _make_request(messages)
# Simulate debug logging: serialize the model (this was the trigger)
_ = req.model_dump_json()
# The assistant message must still have accessible tool_calls afterwards
assistant_msg = req.messages[1]
assert isinstance(assistant_msg, dict)
tool_calls = assistant_msg.get("tool_calls")
assert tool_calls is not None, "tool_calls must not be None after model_dump_json"
assert isinstance(tool_calls, list), "tool_calls must be a list"
assert len(tool_calls) > 0, "tool_calls must not be empty after model_dump_json"
def test_tool_calls_from_generator_are_materialised():
"""tool_calls passed as a generator must be converted to list on validation."""
tool_call = _make_tool_call("call_gen1", "search", '{"query": "vllm"}')
def tool_calls_gen():
yield tool_call
messages = [
{"role": "user", "content": "Search for vllm"},
{
"role": "assistant",
"content": None,
"tool_calls": tool_calls_gen(), # one-shot generator
},
]
req = _make_request(messages)
assistant_msg = req.messages[1]
assert isinstance(assistant_msg, dict)
# Iterate twice — must not raise or return empty on second pass
tool_calls_first = list(assistant_msg.get("tool_calls", []))
tool_calls_second = list(assistant_msg.get("tool_calls", []))
assert len(tool_calls_first) == 1, "First read must return the tool call"
assert len(tool_calls_second) == 1, "Second read must also return the tool call"
def test_tool_calls_list_passthrough():
"""tool_calls already provided as a list must remain a list."""
tool_call = _make_tool_call("call_list1", "calculate", '{"expr": "2+2"}')
messages = [
{"role": "user", "content": "Calculate 2+2"},
{"role": "assistant", "content": None, "tool_calls": [tool_call]},
]
req = _make_request(messages)
assistant_msg = req.messages[1]
assert isinstance(assistant_msg, dict)
assert isinstance(assistant_msg.get("tool_calls"), list)
def test_messages_without_tool_calls_unaffected():
"""Messages without tool_calls must be handled correctly."""
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi there!"},
]
req = _make_request(messages)
# None of the messages should have tool_calls injected
for msg in req.messages:
assert isinstance(msg, dict)
assert msg.get("tool_calls") is None or msg.get("tool_calls") == []
@pytest.mark.parametrize("num_tool_calls", [1, 3])
def test_multiple_tool_calls_materialised(num_tool_calls: int):
"""Multiple tool calls in a single message are all preserved."""
tool_calls = [
_make_tool_call(f"call_{i}", f"func_{i}", f'{{"arg": {i}}}')
for i in range(num_tool_calls)
]
messages = [
{"role": "user", "content": "Do things"},
{"role": "assistant", "content": None, "tool_calls": iter(tool_calls)},
]
req = _make_request(messages)
assistant_msg = req.messages[1]
assert isinstance(assistant_msg, dict)
result_tool_calls = assistant_msg.get("tool_calls")
assert isinstance(result_tool_calls, list)
assert len(result_tool_calls) == num_tool_calls
# Verify after model_dump_json too
_ = req.model_dump_json()
assert len(assistant_msg.get("tool_calls", [])) == num_tool_calls