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, 04:15 UTC
[CI] Add composed-schema regression tests for DeepSeek V3.2/V4 parsers (#43255)
Signed-off-by: Ace Eldeib <aeldeib@coreweave.com> Co-authored-by: Flora Feng <4florafeng@gmail.com>
Ace Eldeib committed
May 21, 2026 at 02:36 UTC
63ea11709bd9e9b14669e3973dff92d2dcea3cb1
2 files changed
+186
tests/tool_parsers/test_deepseekv32_tool_parser.py
+137
@@ -221,6 +221,99 @@ class TestExtractToolCalls:
221
assert args == {"value": 42}
222
assert isinstance(args["value"], int)
223
224
+ @pytest.mark.skip_global_cleanup
225
+ def test_composed_schema_converts_object_and_array_params(self):
226
+ """Composed JSON Schema types must still drive DSML type coercion."""
227
+ tool = ChatCompletionToolsParam(
228
+ function=FunctionDefinition(
229
+ name="set_timer",
230
+ parameters={
231
+ "type": "object",
232
+ "properties": {
233
+ "wait": {
234
+ "anyOf": [
235
+ {
236
+ "type": "object",
237
+ "properties": {
238
+ "type": {"const": "until"},
239
+ "date": {"type": "string"},
240
+ },
241
+ },
242
+ {
243
+ "type": "object",
244
+ "properties": {
245
+ "type": {"const": "for"},
246
+ "minutes": {"type": "number"},
247
+ },
248
+ },
249
+ ],
250
+ },
251
+ "patches": {
252
+ "oneOf": [
253
+ {"type": "array", "items": {"type": "object"}},
254
+ {"type": "null"},
255
+ ],
256
+ },
257
+ },
258
+ },
259
+ ),
260
+ )
261
+ parser = make_parser(tools=[tool])
262
+ model_output = (
263
+ f"{FC_START}\n"
264
+ f'{INV_START}set_timer">\n'
265
+ f'{PARAM_START}wait" string="false">'
266
+ f'{{"type":"for","minutes":2880}}'
267
+ f"{PARAM_END}\n"
268
+ f'{PARAM_START}patches" string="false">'
269
+ f'[{{"op":"replace","path":"/schedule","value":"quiet"}}]'
270
+ f"{PARAM_END}\n"
271
+ f"{INV_END}\n"
272
+ f"{FC_END}"
273
+ )
274
+ result = parser.extract_tool_calls(model_output, None)
275
+ assert result.tools_called
276
+ args = json.loads(result.tool_calls[0].function.arguments)
277
+ assert args == {
278
+ "wait": {"type": "for", "minutes": 2880},
279
+ "patches": [{"op": "replace", "path": "/schedule", "value": "quiet"}],
280
+ }
281
+ assert isinstance(args["wait"], dict)
282
+ assert isinstance(args["patches"], list)
283
+
284
+ @pytest.mark.skip_global_cleanup
285
+ def test_string_attr_true_preserves_literal_for_composed_schema(self):
286
+ tool = ChatCompletionToolsParam(
287
+ function=FunctionDefinition(
288
+ name="set_timer",
289
+ parameters={
290
+ "type": "object",
291
+ "properties": {
292
+ "wait": {
293
+ "anyOf": [
294
+ {"type": "object"},
295
+ {"type": "null"},
296
+ ],
297
+ },
298
+ },
299
+ },
300
+ ),
301
+ )
302
+ parser = make_parser(tools=[tool])
303
+ model_output = (
304
+ f"{FC_START}\n"
305
+ f'{INV_START}set_timer">\n'
306
+ f'{PARAM_START}wait" string="true">'
307
+ f'{{"type":"for","minutes":2880}}'
308
+ f"{PARAM_END}\n"
309
+ f"{INV_END}\n"
310
+ f"{FC_END}"
311
+ )
312
+ result = parser.extract_tool_calls(model_output, None)
313
+ assert result.tools_called
314
+ args = json.loads(result.tool_calls[0].function.arguments)
315
+ assert args == {"wait": '{"type":"for","minutes":2880}'}
316
+
317
def test_arguments_wrapper_repaired(self):
318
"""A single 'arguments' wrapper parameter must be unwrapped when it
319
is not part of the tool schema and the inner object matches schema fields."""
@@ -581,6 +674,50 @@ class TestExtractToolCallsStreaming:
674
assert args == {"value": "42"}
675
assert isinstance(args["value"], str)
676
677
+ @pytest.mark.skip_global_cleanup
678
+ def test_composed_schema_conversion_in_streaming(self):
679
+ tool = ChatCompletionToolsParam(
680
+ function=FunctionDefinition(
681
+ name="set_timer",
682
+ parameters={
683
+ "type": "object",
684
+ "properties": {
685
+ "wait": {
686
+ "anyOf": [
687
+ {"type": "object"},
688
+ {"type": "null"},
689
+ ],
690
+ },
691
+ "patches": {
692
+ "oneOf": [
693
+ {"type": "array", "items": {"type": "object"}},
694
+ {"type": "null"},
695
+ ],
696
+ },
697
+ },
698
+ },
699
+ ),
700
+ )
701
+ parser = make_parser(tools=[tool])
702
+ full_text = (
703
+ f"{FC_START}\n"
704
+ f'{INV_START}set_timer">\n'
705
+ f'{PARAM_START}wait" string="false">'
706
+ f'{{"type":"for","minutes":2880}}'
707
+ f"{PARAM_END}\n"
708
+ f'{PARAM_START}patches" string="false">'
709
+ f'[{{"op":"replace","path":"/schedule","value":"quiet"}}]'
710
+ f"{PARAM_END}\n"
711
+ f"{INV_END}\n"
712
+ f"{FC_END}"
713
+ )
714
+ deltas = self._stream(parser, full_text)
715
+ args = json.loads(self._reconstruct_args(deltas))
716
+ assert args == {
717
+ "wait": {"type": "for", "minutes": 2880},
718
+ "patches": [{"op": "replace", "path": "/schedule", "value": "quiet"}],
719
+ }
720
+
721
def test_multiple_tools_streaming(self, parser):
722
full_text = (
723
f"{FC_START}\n"
tests/tool_parsers/test_deepseekv4_tool_parser.py
+49
@@ -236,3 +236,52 @@ def test_extract_tool_calls_arguments_wrapper():
236
assert result.tools_called
237
args = json.loads(result.tool_calls[0].function.arguments)
238
assert args == {"location": "Beijing"}
239
+
240
+
241
+@pytest.mark.skip_global_cleanup
242
+def test_composed_schema_converts_object_and_array_params():
243
+ tool = ChatCompletionToolsParam(
244
+ type="function",
245
+ function={
246
+ "name": "set_timer",
247
+ "parameters": {
248
+ "type": "object",
249
+ "properties": {
250
+ "wait": {
251
+ "anyOf": [
252
+ {"type": "object"},
253
+ {"type": "null"},
254
+ ],
255
+ },
256
+ "patches": {
257
+ "allOf": [
258
+ {"type": "array", "items": {"type": "object"}},
259
+ ],
260
+ },
261
+ },
262
+ },
263
+ },
264
+ )
265
+ parser = make_parser(tools=[tool])
266
+ request = make_request(tools=[tool])
267
+ model_output = (
268
+ f"{TC_START}\n"
269
+ f'{INV_START}set_timer">\n'
270
+ f'{PARAM_START}wait" string="false">'
271
+ f'{{"type":"for","minutes":2880}}'
272
+ f"{PARAM_END}\n"
273
+ f'{PARAM_START}patches" string="false">'
274
+ f'[{{"op":"replace","path":"/schedule","value":"quiet"}}]'
275
+ f"{PARAM_END}\n"
276
+ f"{INV_END}\n"
277
+ f"{TC_END}"
278
+ )
279
+
280
+ result = parser.extract_tool_calls(model_output, request)
281
+
282
+ assert result.tools_called
283
+ args = json.loads(result.tool_calls[0].function.arguments)
284
+ assert args == {
285
+ "wait": {"type": "for", "minutes": 2880},
286
+ "patches": [{"op": "replace", "path": "/schedule", "value": "quiet"}],
287
+ }