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 3, 08:00 UTC
[Refactor] Extract extract_types_from_schema utility from Minimax M2 tool parser (#43025)
Signed-off-by: sfeng33 <4florafeng@gmail.com>
Flora Feng committed
May 19, 2026 at 11:21 UTC
42b4f1fdf7269de8aa83755a805555fe78add28b
3 files changed
+123
-106
tests/tool_parsers/test_utils.py
+67
-1
@@ -3,7 +3,10 @@
3
4
import pytest
5
6
-from vllm.tool_parsers.utils import coerce_to_schema_type
6
+from vllm.tool_parsers.utils import (
7
+ coerce_to_schema_type,
8
+ extract_types_from_schema,
9
+)
10
11
12
class TestCoerceToSchemaType:
@@ -146,3 +149,66 @@ class TestCoerceToSchemaType:
149
150
def test_unrecognized_type_falls_back_to_json(self):
151
assert coerce_to_schema_type("42", "interval") == 42
152
+
153
+
154
+class TestExtractTypesFromSchema:
155
+ def test_direct_type_string(self):
156
+ assert extract_types_from_schema({"type": "string"}) == ["string"]
157
+
158
+ def test_direct_type_integer(self):
159
+ assert extract_types_from_schema({"type": "integer"}) == ["integer"]
160
+
161
+ def test_type_array(self):
162
+ result = set(extract_types_from_schema({"type": ["string", "null"]}))
163
+ assert result == {"string", "null"}
164
+
165
+ def test_anyof(self):
166
+ schema = {"anyOf": [{"type": "object"}, {"type": "null"}]}
167
+ result = set(extract_types_from_schema(schema))
168
+ assert result == {"object", "null"}
169
+
170
+ def test_oneof(self):
171
+ schema = {"oneOf": [{"type": "integer"}, {"type": "string"}]}
172
+ result = set(extract_types_from_schema(schema))
173
+ assert result == {"integer", "string"}
174
+
175
+ def test_allof(self):
176
+ schema = {"allOf": [{"type": "object"}]}
177
+ assert extract_types_from_schema(schema) == ["object"]
178
+
179
+ def test_enum_infers_types(self):
180
+ schema = {"enum": [1, "a", None]}
181
+ result = set(extract_types_from_schema(schema))
182
+ assert result == {"integer", "string", "null"}
183
+
184
+ def test_enum_with_bool(self):
185
+ schema = {"enum": [True, False]}
186
+ assert extract_types_from_schema(schema) == ["boolean"]
187
+
188
+ def test_enum_with_float(self):
189
+ schema = {"enum": [1.5, 2.5]}
190
+ assert extract_types_from_schema(schema) == ["number"]
191
+
192
+ def test_enum_with_list_and_dict(self):
193
+ schema = {"enum": [[1, 2], {"a": 1}]}
194
+ result = set(extract_types_from_schema(schema))
195
+ assert result == {"array", "object"}
196
+
197
+ def test_none_schema_defaults_to_string(self):
198
+ assert extract_types_from_schema(None) == ["string"]
199
+
200
+ def test_non_dict_schema_defaults_to_string(self):
201
+ assert extract_types_from_schema("string") == ["string"]
202
+
203
+ def test_empty_dict_defaults_to_string(self):
204
+ assert extract_types_from_schema({}) == ["string"]
205
+
206
+ def test_nested_anyof(self):
207
+ schema = {
208
+ "anyOf": [
209
+ {"anyOf": [{"type": "integer"}, {"type": "null"}]},
210
+ {"type": "string"},
211
+ ]
212
+ }
213
+ result = set(extract_types_from_schema(schema))
214
+ assert result == {"integer", "null", "string"}
vllm/tool_parsers/minimax_m2_tool_parser.py
+10
-105
@@ -4,7 +4,6 @@
4
import json
5
import uuid
6
from collections.abc import Sequence
7
-from typing import Any
7
8
import regex as re
9
@@ -25,7 +24,11 @@ from vllm.tool_parsers.abstract_tool_parser import (
24
Tool,
25
ToolParser,
26
)
28
-from vllm.tool_parsers.utils import coerce_to_schema_type
27
+from vllm.tool_parsers.utils import (
28
+ coerce_to_schema_type,
29
+ extract_types_from_schema,
30
+ find_tool_properties,
31
+)
32
33
logger = init_logger(__name__)
34
@@ -87,89 +90,6 @@ class MinimaxM2ToolParser(ToolParser):
90
return name_str[1:-1]
91
return name_str
92
90
- def _extract_types_from_schema(self, schema: Any) -> list[str]:
91
- """
92
- Extract all possible types from a JSON schema definition.
93
- Handles anyOf, oneOf, allOf, type arrays, and enum fields.
94
-
95
- Args:
96
- schema: The JSON schema definition for a parameter
97
-
98
- Returns:
99
- List of type strings (e.g., ["string", "integer", "null"])
100
- """
101
- if schema is None:
102
- return ["string"]
103
-
104
- if not isinstance(schema, dict):
105
- return ["string"]
106
-
107
- types: set[str] = set()
108
-
109
- # Handle direct "type" field
110
- if "type" in schema:
111
- type_value = schema["type"]
112
- if isinstance(type_value, str):
113
- types.add(type_value)
114
- elif isinstance(type_value, list):
115
- for t in type_value:
116
- if isinstance(t, str):
117
- types.add(t)
118
-
119
- # Handle enum - infer types from enum values
120
- if "enum" in schema and isinstance(schema["enum"], list) and schema["enum"]:
121
- for value in schema["enum"]:
122
- if value is None:
123
- types.add("null")
124
- elif isinstance(value, bool):
125
- types.add("boolean")
126
- elif isinstance(value, int):
127
- types.add("integer")
128
- elif isinstance(value, float):
129
- types.add("number")
130
- elif isinstance(value, str):
131
- types.add("string")
132
- elif isinstance(value, list):
133
- types.add("array")
134
- elif isinstance(value, dict):
135
- types.add("object")
136
-
137
- # Handle anyOf, oneOf, allOf - recursively extract types
138
- for choice_field in ("anyOf", "oneOf", "allOf"):
139
- if choice_field in schema and isinstance(schema[choice_field], list):
140
- for choice in schema[choice_field]:
141
- extracted = self._extract_types_from_schema(choice)
142
- types.update(extracted)
143
-
144
- # If no types found, default to string
145
- if not types:
146
- return ["string"]
147
-
148
- return list(types)
149
-
150
- def _get_param_types_from_config(
151
- self, param_name: str, param_config: dict
152
- ) -> list[str]:
153
- """
154
- Get parameter types from parameter configuration.
155
- Handles anyOf, oneOf, allOf, and direct type definitions.
156
-
157
- Args:
158
- param_name: The name of the parameter
159
- param_config: The properties dict from the tool schema
160
-
161
- Returns:
162
- List of type strings
163
- """
164
- if param_name not in param_config:
165
- return ["string"]
166
-
167
- param_schema = param_config[param_name]
168
- if not isinstance(param_schema, dict):
169
- return ["string"]
170
-
171
- return self._extract_types_from_schema(param_schema)
172
-
93
def _parse_single_invoke(
94
self, invoke_str: str, tools: list | None
95
) -> ToolCall | None:
@@ -180,20 +100,7 @@ class MinimaxM2ToolParser(ToolParser):
100
return None
101
102
function_name = self._extract_name(name_match.group(1))
183
-
184
- # Get parameter configuration
185
- param_config = {}
186
- if tools:
187
- for tool in tools:
188
- if (
189
- hasattr(tool, "function")
190
- and tool.function.name == function_name
191
- and hasattr(tool.function, "parameters")
192
- ):
193
- params = tool.function.parameters
194
- if isinstance(params, dict) and "properties" in params:
195
- param_config = params["properties"]
196
- break
103
+ tool_properties = find_tool_properties(tools, function_name)
104
105
# Extract parameters
106
param_dict = {}
@@ -202,12 +109,10 @@ class MinimaxM2ToolParser(ToolParser):
109
if param_match:
110
param_name = self._extract_name(param_match.group(1))
111
param_value = param_match.group(2).strip()
205
-
206
- # Get parameter types (supports anyOf/oneOf/allOf)
207
- param_type = self._get_param_types_from_config(param_name, param_config)
208
-
209
- # Convert value
210
- param_dict[param_name] = coerce_to_schema_type(param_value, param_type)
112
+ param_types = extract_types_from_schema(
113
+ tool_properties.get(param_name, {})
114
+ )
115
+ param_dict[param_name] = coerce_to_schema_type(param_value, param_types)
116
117
return ToolCall(
118
type="function",
vllm/tool_parsers/utils.py
+46
@@ -450,6 +450,52 @@ def make_valid_python(text: str) -> tuple[str, str] | None:
450
return candidate, added_text
451
452
453
+def extract_types_from_schema(schema: Any) -> list[str]:
454
+ """Extract all possible type strings from a JSON Schema definition.
455
+
456
+ Handles ``type`` (string or list), ``enum`` value inference, and
457
+ recursive ``anyOf``/``oneOf``/``allOf``. Returns ``["string"]``
458
+ when no type information can be determined.
459
+ """
460
+ if schema is None or not isinstance(schema, dict):
461
+ return ["string"]
462
+
463
+ types: set[str] = set()
464
+
465
+ if "type" in schema:
466
+ type_value = schema["type"]
467
+ if isinstance(type_value, str):
468
+ types.add(type_value)
469
+ elif isinstance(type_value, list):
470
+ for t in type_value:
471
+ if isinstance(t, str):
472
+ types.add(t)
473
+
474
+ if "enum" in schema and isinstance(schema["enum"], list) and schema["enum"]:
475
+ for value in schema["enum"]:
476
+ if value is None:
477
+ types.add("null")
478
+ elif isinstance(value, bool):
479
+ types.add("boolean")
480
+ elif isinstance(value, int):
481
+ types.add("integer")
482
+ elif isinstance(value, float):
483
+ types.add("number")
484
+ elif isinstance(value, str):
485
+ types.add("string")
486
+ elif isinstance(value, list):
487
+ types.add("array")
488
+ elif isinstance(value, dict):
489
+ types.add("object")
490
+
491
+ for choice_field in ("anyOf", "oneOf", "allOf"):
492
+ if choice_field in schema and isinstance(schema[choice_field], list):
493
+ for choice in schema[choice_field]:
494
+ types.update(extract_types_from_schema(choice))
495
+
496
+ return list(types) if types else ["string"]
497
+
498
+
499
_TYPE_ALIASES: dict[str, str] = {
500
"str": "string",
501
"text": "string",