@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / f91480b2d4
Fork 0

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 14, 05:30 UTC

[Bugfix] fix tool call arguments is empty (#25223)

Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com> Co-authored-by: xin.li <xin.li@daocloud.io>

Chauncey committed Sep 20, 2025 at 13:29 UTC f91480b2d44c263fb600b5cba5b0e6c7a195f742
2 files changed +65 -3
tests/entrypoints/openai/test_completion_with_function_calling.py
+60
@@ -1,6 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +import datetime
5 from typing import Union
6
7 import openai # use the official client for correctness check
@@ -284,3 +285,62 @@ async def test_tool_id_kimi_k2(k2_client: openai.AsyncOpenAI, model_name: str,
285 output.extend(chunk.choices[0].delta.tool_calls)
286 for o in output:
287 assert o.id is None or o.id == 'functions.get_current_weather:0'
288 +
289 +
290 +@pytest.mark.asyncio
291 +@pytest.mark.parametrize("model_name", [MODEL_NAME])
292 +@pytest.mark.parametrize("arguments", ["{}", ''])
293 +async def test_no_args_tool_call(client: openai.AsyncOpenAI, model_name: str,
294 + arguments: str):
295 + # Step 1: Define a tool that requires no parameters
296 + tools = [{
297 + "type": "function",
298 + "function": {
299 + "name": "get_current_time",
300 + "description":
301 + "Get the current date and time. No parameters needed.",
302 + "parameters": {
303 + "type": "object",
304 + "properties": {}, # No parameters
305 + "required": [] # No required fields
306 + }
307 + }
308 + }]
309 + messages = [{"role": "user", "content": "What time is it now?"}]
310 + # Step 2: Send user message and let model decide whether to call the tool
311 + response = await client.chat.completions.create(
312 + model=model_name,
313 + messages=messages,
314 + tools=tools,
315 + tool_choice="auto" # Let model choose automatically
316 + )
317 +
318 + # Step 3: Check if model wants to call a tool
319 + message = response.choices[0].message
320 + if message.tool_calls:
321 + # Get the first tool call
322 + tool_call = message.tool_calls[0]
323 + tool_name = tool_call.function.name
324 + # Step 4: Execute the tool locally (no parameters)
325 + if tool_name == "get_current_time":
326 + # Test both empty string and "{}" for no-arg tool calls
327 + tool_call.function.arguments = arguments
328 + messages.append(message)
329 + current_time = datetime.datetime.now()
330 + result = current_time.isoformat()
331 + messages.append({
332 + "role": "tool",
333 + "tool_call_id": tool_call.id,
334 + "content": result,
335 + })
336 + # Step 5: Send tool result back to model to continue conversation
337 + final_response = await client.chat.completions.create(
338 + model=model_name,
339 + messages=messages,
340 + )
341 + # Output final natural language response
342 + assert final_response.choices[0].message.content is not None
343 +
344 + else:
345 + # No tool called — just print model's direct reply
346 + assert message.content is not None
vllm/entrypoints/chat_utils.py
+5 -3
@@ -1450,9 +1450,11 @@ def _postprocess_messages(messages: list[ConversationMessage]) -> None:
1450 and isinstance(message["tool_calls"], list)
1451 ):
1452 for item in message["tool_calls"]:
1453 - item["function"]["arguments"] = json.loads(
1454 - item["function"]["arguments"]
1455 - )
1453 + # if arguments is None or empty string, set to {}
1454 + if content := item["function"].get("arguments"):
1455 + item["function"]["arguments"] = json.loads(content)
1456 + else:
1457 + item["function"]["arguments"] = {}
1458
1459
1460 def parse_chat_messages(