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 5, 21:00 UTC
[Bugfix] Support multi-type params parsing for DeepSeek v3.2 (#33703)
Signed-off-by: Stanislav Kirillov <stas@nebius.com> Co-authored-by: Stanislav Kirillov <stas@nebius.com> Co-authored-by: Chauncey <chaunceyjiang@gmail.com>
Stanislav Kirillov committed
Mar 30, 2026 at 06:07 UTC
a6db99ba02ec67e1982619dc5bbed4b55ba4a953
2 files changed
+201
-18
tests/tool_parsers/test_deepseekv32_tool_parser.py
+181
@@ -11,6 +11,7 @@ from unittest.mock import MagicMock
11
12
import pytest
13
14
+from vllm.tokenizers import get_tokenizer
15
from vllm.tool_parsers.deepseekv32_tool_parser import DeepSeekV32ToolParser
16
17
# ---------------------------------------------------------------------------
@@ -474,3 +475,183 @@ class TestExtractToolCallsStreaming:
475
deltas = self._stream(parser, partial_text)
476
# Should have no tool call deltas yet
477
assert all(not d.tool_calls for d in deltas)
478
+
479
+
480
+@pytest.fixture(scope="module")
481
+def deepseekv32_tokenizer():
482
+ return get_tokenizer(tokenizer_name="deepseek-ai/DeepSeek-V3.2")
483
+
484
+
485
+@pytest.fixture
486
+def parser(deepseekv32_tokenizer):
487
+ return DeepSeekV32ToolParser(deepseekv32_tokenizer)
488
+
489
+
490
+def test_convert_param_value_single_types(parser):
491
+ """Test _convert_param_value with single type parameters."""
492
+ # Test string type
493
+ assert parser._convert_param_value("hello", "string") == "hello"
494
+ assert parser._convert_param_value("123", "string") == "123"
495
+
496
+ # Test integer type - valid integers
497
+ assert parser._convert_param_value("123", "integer") == 123
498
+ assert parser._convert_param_value("456", "int") == 456
499
+ # Invalid integer should return original string (due to exception catch)
500
+ assert parser._convert_param_value("abc", "integer") == "abc"
501
+
502
+ # Test float/number type
503
+ assert parser._convert_param_value("123.45", "float") == 123.45
504
+ assert (
505
+ parser._convert_param_value("123.0", "number") == 123
506
+ ) # Should be int when whole number
507
+ assert parser._convert_param_value("123.5", "number") == 123.5
508
+ # Invalid float should return original string
509
+ assert parser._convert_param_value("abc", "float") == "abc"
510
+
511
+ # Test boolean type - valid boolean values
512
+ assert parser._convert_param_value("true", "boolean") is True
513
+ assert parser._convert_param_value("false", "bool") is False
514
+ assert parser._convert_param_value("1", "boolean") is True
515
+ assert parser._convert_param_value("0", "boolean") is False
516
+ # Invalid boolean should return original string
517
+ assert parser._convert_param_value("yes", "boolean") == "yes"
518
+ assert parser._convert_param_value("no", "bool") == "no"
519
+
520
+ # Test null value
521
+ assert parser._convert_param_value("null", "string") is None
522
+ assert parser._convert_param_value("null", "integer") is None
523
+
524
+ # Test object/array type (JSON)
525
+ assert parser._convert_param_value('{"key": "value"}', "object") == {"key": "value"}
526
+ assert parser._convert_param_value("[1, 2, 3]", "array") == [1, 2, 3]
527
+ # Invalid JSON should return original string
528
+ assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
529
+
530
+ # Test fallback for unknown type (tries json.loads, then returns original)
531
+ assert parser._convert_param_value('{"key": "value"}', "unknown") == {
532
+ "key": "value"
533
+ }
534
+ assert parser._convert_param_value("plain text", "unknown") == "plain text"
535
+
536
+
537
+def test_convert_param_value_multi_typed_values(parser):
538
+ """Test _convert_param_value with multi-typed values (list of types)."""
539
+ # Test with list of types where first type succeeds
540
+ assert parser._convert_param_value("123", ["integer", "string"]) == 123
541
+ assert parser._convert_param_value("true", ["boolean", "string"]) is True
542
+ assert parser._convert_param_value('{"x": 1}', ["object", "string"]) == {"x": 1}
543
+
544
+ # Test with list of types where first type fails but second succeeds
545
+ # "abc" is not a valid integer, so should try string next
546
+ assert parser._convert_param_value("abc", ["integer", "string"]) == "abc"
547
+
548
+ # Test with list of types where all fail - should return original value
549
+ # "invalid json" is not valid JSON, last type is "object" which will fail JSON parse
550
+ result = parser._convert_param_value("invalid json", ["integer", "object"])
551
+ assert result == "invalid json" # Returns original value after all types fail
552
+
553
+ # Test with three types
554
+ assert parser._convert_param_value("123.5", ["integer", "float", "string"]) == 123.5
555
+ assert parser._convert_param_value("true", ["integer", "boolean", "string"]) is True
556
+
557
+ # Test with null in multi-type list
558
+ assert parser._convert_param_value("null", ["integer", "string"]) is None
559
+ assert parser._convert_param_value("null", ["boolean", "object"]) is None
560
+
561
+ # Test nested type conversion - boolean fails, integer succeeds
562
+ value = parser._convert_param_value("123", ["boolean", "integer", "string"])
563
+ assert value == 123 # Should be integer, not boolean
564
+
565
+ # Test that order matters
566
+ assert (
567
+ parser._convert_param_value("123", ["string", "integer"]) == "123"
568
+ ) # String first
569
+ assert (
570
+ parser._convert_param_value("123", ["integer", "string"]) == 123
571
+ ) # Integer first
572
+
573
+ # Test with all types failing - returns original value
574
+ assert (
575
+ parser._convert_param_value("not_a_number", ["integer", "float", "boolean"])
576
+ == "not_a_number"
577
+ )
578
+
579
+
580
+def test_convert_param_value_stricter_type_checking(parser):
581
+ """Test stricter type checking in the updated implementation."""
582
+ # Boolean now has stricter validation
583
+ assert parser._convert_param_value("true", "boolean") is True
584
+ assert parser._convert_param_value("false", "boolean") is False
585
+ assert parser._convert_param_value("1", "boolean") is True
586
+ assert parser._convert_param_value("0", "boolean") is False
587
+
588
+ # These should return original string (not valid boolean values)
589
+ assert parser._convert_param_value("yes", "boolean") == "yes"
590
+ assert parser._convert_param_value("no", "boolean") == "no"
591
+ assert parser._convert_param_value("TRUE", "boolean") is True
592
+ assert parser._convert_param_value("FALSE", "boolean") is False
593
+
594
+ # Integer and float now raise exceptions for invalid values
595
+ assert parser._convert_param_value("123abc", "integer") == "123abc"
596
+ assert parser._convert_param_value("123.45.67", "float") == "123.45.67"
597
+
598
+ # JSON parsing is stricter - invalid JSON returns original
599
+ assert parser._convert_param_value("{invalid: json}", "object") == "{invalid: json}"
600
+ assert parser._convert_param_value("[1, 2,", "array") == "[1, 2,"
601
+
602
+ # Test multi-type with stricter checking
603
+ # "yes" is not valid boolean, but string would accept it
604
+ assert parser._convert_param_value("yes", ["boolean", "string"]) == "yes"
605
+
606
+ # "123abc" is not valid integer or float, but string accepts it
607
+ assert (
608
+ parser._convert_param_value("123abc", ["integer", "float", "string"])
609
+ == "123abc"
610
+ )
611
+
612
+
613
+def test_convert_param_value_edge_cases(parser):
614
+ """Test edge cases for _convert_param_value."""
615
+ # Empty string
616
+ assert parser._convert_param_value("", "string") == ""
617
+ assert (
618
+ parser._convert_param_value("", "integer") == ""
619
+ ) # Invalid int returns original
620
+
621
+ # Whitespace - trimmed by conversion functions
622
+ assert parser._convert_param_value(" 123 ", "integer") == 123
623
+ assert parser._convert_param_value(" true ", "boolean") is True
624
+
625
+ # Numeric strings with special characters
626
+ assert parser._convert_param_value("123.45.67", "float") == "123.45.67"
627
+ assert parser._convert_param_value("123abc", "integer") == "123abc"
628
+
629
+ # JSON with whitespace - should parse correctly
630
+ assert parser._convert_param_value(' { "key" : "value" } ', "object") == {
631
+ "key": "value"
632
+ }
633
+
634
+ # Invalid JSON returns original
635
+ assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
636
+ assert parser._convert_param_value("[1, 2,", "array") == "[1, 2,"
637
+
638
+
639
+def test_convert_param_value_checked_helper(parser):
640
+ """Test the _convert_param_value_checked helper function indirectly."""
641
+ # This tests the behavior through the main function
642
+ # Valid conversions should work
643
+ assert parser._convert_param_value("123", "integer") == 123
644
+ assert parser._convert_param_value("123.45", "float") == 123.45
645
+ assert parser._convert_param_value("true", "boolean") is True
646
+ assert parser._convert_param_value('{"x": 1}', "object") == {"x": 1}
647
+
648
+ # Invalid conversions should return original value (exception caught)
649
+ assert parser._convert_param_value("abc", "integer") == "abc"
650
+ assert parser._convert_param_value("abc", "float") == "abc"
651
+ assert parser._convert_param_value("yes", "boolean") == "yes"
652
+ assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
653
+
654
+ # Test that null handling works in checked function
655
+ assert parser._convert_param_value("null", "integer") is None
656
+ assert parser._convert_param_value("null", "boolean") is None
657
+ assert parser._convert_param_value("null", "object") is None
vllm/tool_parsers/deepseekv32_tool_parser.py
+20
-18
@@ -100,7 +100,7 @@ class DeepSeekV32ToolParser(ToolParser):
100
param_dict[param_name] = param_val
101
return param_dict
102
103
- def _convert_param_value(self, value: str, param_type: str) -> Any:
103
+ def _convert_param_value_checked(self, value: str, param_type: str) -> Any:
104
"""Convert parameter value to the correct type."""
105
if value.lower() == "null":
106
return None
@@ -109,29 +109,31 @@ class DeepSeekV32ToolParser(ToolParser):
109
if param_type in ["string", "str", "text"]:
110
return value
111
elif param_type in ["integer", "int"]:
112
- try:
113
- return int(value)
114
- except (ValueError, TypeError):
115
- return value
112
+ return int(value)
113
elif param_type in ["number", "float"]:
117
- try:
118
- val = float(value)
119
- return val if val != int(val) else int(val)
120
- except (ValueError, TypeError):
121
- return value
114
+ val = float(value)
115
+ return val if val != int(val) else int(val)
116
elif param_type in ["boolean", "bool"]:
117
+ value = value.strip()
118
+ if value.lower() not in ["false", "0", "true", "1"]:
119
+ raise ValueError("Invalid boolean value")
120
return value.lower() in ["true", "1"]
121
elif param_type in ["object", "array"]:
125
- try:
126
- return json.loads(value)
127
- except json.JSONDecodeError:
128
- return value
122
+ return json.loads(value)
123
else:
130
- # Try JSON parse first, fallback to string
124
+ return json.loads(value)
125
+
126
+ def _convert_param_value(self, value: str, param_type: str | list[str]) -> Any:
127
+ """Convert parameter value to the correct type."""
128
+ if not isinstance(param_type, list):
129
+ param_type = [param_type]
130
+ for current_type in param_type:
131
try:
132
- return json.loads(value)
133
- except json.JSONDecodeError:
134
- return value
132
+ return self._convert_param_value_checked(value, current_type)
133
+ except Exception:
134
+ continue
135
+ # return value as fallback
136
+ return value
137
138
def _convert_params_with_schema(
139
self,