@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / a6db99ba02
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 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,