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

A high-throughput and memory-efficient inference and serving engine for LLMs

[Bugfix] Fix tool parsing crash with non-function tool types (e.g. WebSearchTool) (#45147)

Signed-off-by: Ben Browning <bbrownin@redhat.com>

Ben Browning committed Jun 10, 2026 at 13:17 UTC d1bcb4b44cc3d7bc625d9a3b490c7766f79f400f
2 files changed +48 -3
tests/tool_use/test_tool_choice_required.py
+39 -1
@@ -5,13 +5,17 @@ from copy import deepcopy
5
6 import pytest
7 import regex as re
8 +from openai.types.responses import FunctionTool, WebSearchTool
9 from pydantic import TypeAdapter
10
11 from vllm.entrypoints.openai.chat_completion.protocol import (
12 ChatCompletionToolsParam,
13 )
14 from vllm.tool_parsers.streaming import extract_required_tool_call_streaming
14 -from vllm.tool_parsers.utils import get_json_schema_from_tools
15 +from vllm.tool_parsers.utils import (
16 + find_tool_properties,
17 + get_json_schema_from_tools,
18 +)
19
20 pytestmark = pytest.mark.cpu_test
21
@@ -354,3 +358,37 @@ def test_streaming_output_valid_with_trailing_extra_data():
358 previous_text = current_text
359
360 assert len(messages) > 0
361 +
362 +
363 +FUNCTION_TOOL = FunctionTool(
364 + type="function",
365 + name="get_weather",
366 + parameters={
367 + "type": "object",
368 + "properties": {"city": {"type": "string"}},
369 + "required": ["city"],
370 + },
371 +)
372 +WEB_SEARCH_TOOL = WebSearchTool(type="web_search")
373 +
374 +
375 +class TestNonFunctionToolsSkipped:
376 + """Non-function tools (web_search, etc.) must be silently skipped
377 + by the tool-schema utilities instead of raising TypeError."""
378 +
379 + def test_find_tool_properties_skips_web_search(self):
380 + tools = [WEB_SEARCH_TOOL, FUNCTION_TOOL]
381 + props = find_tool_properties(tools, "get_weather")
382 + assert props == {"city": {"type": "string"}}
383 +
384 + def test_find_tool_properties_only_non_function_tools(self):
385 + props = find_tool_properties([WEB_SEARCH_TOOL], "get_weather")
386 + assert props == {}
387 +
388 + def test_get_json_schema_with_mixed_tools(self):
389 + tools = [WEB_SEARCH_TOOL, FUNCTION_TOOL]
390 + schema = get_json_schema_from_tools(tools=tools, tool_choice="required")
391 + assert isinstance(schema, dict)
392 + any_of = schema["items"]["anyOf"]
393 + assert len(any_of) == 1
394 + assert any_of[0]["properties"]["name"]["enum"] == ["get_weather"]
vllm/tool_parsers/utils.py
+9 -2
@@ -151,6 +151,10 @@ def consume_space(i: int, s: str) -> int:
151 return i
152
153
154 +def _is_function_tool(tool: Tool) -> bool:
155 + return isinstance(tool, (FunctionTool, ChatCompletionToolsParam))
156 +
157 +
158 def _extract_tool_info(
159 tool: Tool,
160 ) -> tuple[str, dict[str, Any] | None]:
@@ -170,6 +174,8 @@ def find_tool_properties(
174 if not tools:
175 return {}
176 for tool in tools:
177 + if not _is_function_tool(tool):
178 + continue
179 name, params = _extract_tool_info(tool)
180 if name == tool_name:
181 return (params or {}).get("properties", {})
@@ -210,15 +216,16 @@ def _get_tool_schema_defs(
216 def _get_json_schema_from_tools(
217 tools: list[Tool],
218 ) -> dict:
219 + fn_tools = [t for t in tools if _is_function_tool(t)]
220 json_schema = {
221 "type": "array",
222 "minItems": 1,
223 "items": {
224 "type": "object",
218 - "anyOf": [_get_tool_schema_from_tool(tool) for tool in tools],
225 + "anyOf": [_get_tool_schema_from_tool(tool) for tool in fn_tools],
226 },
227 }
221 - json_schema_defs = _get_tool_schema_defs(tools)
228 + json_schema_defs = _get_tool_schema_defs(fn_tools)
229 if json_schema_defs:
230 json_schema["$defs"] = json_schema_defs
231 return json_schema