[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>
This commit is contained in:
Stanislav Kirillov
2026-03-30 06:07:28 +02:00
committed by GitHub
parent 4f2ed5fddb
commit a6db99ba02
2 changed files with 201 additions and 18 deletions

View File

@@ -11,6 +11,7 @@ from unittest.mock import MagicMock
import pytest
from vllm.tokenizers import get_tokenizer
from vllm.tool_parsers.deepseekv32_tool_parser import DeepSeekV32ToolParser
# ---------------------------------------------------------------------------
@@ -474,3 +475,183 @@ class TestExtractToolCallsStreaming:
deltas = self._stream(parser, partial_text)
# Should have no tool call deltas yet
assert all(not d.tool_calls for d in deltas)
@pytest.fixture(scope="module")
def deepseekv32_tokenizer():
return get_tokenizer(tokenizer_name="deepseek-ai/DeepSeek-V3.2")
@pytest.fixture
def parser(deepseekv32_tokenizer):
return DeepSeekV32ToolParser(deepseekv32_tokenizer)
def test_convert_param_value_single_types(parser):
"""Test _convert_param_value with single type parameters."""
# Test string type
assert parser._convert_param_value("hello", "string") == "hello"
assert parser._convert_param_value("123", "string") == "123"
# Test integer type - valid integers
assert parser._convert_param_value("123", "integer") == 123
assert parser._convert_param_value("456", "int") == 456
# Invalid integer should return original string (due to exception catch)
assert parser._convert_param_value("abc", "integer") == "abc"
# Test float/number type
assert parser._convert_param_value("123.45", "float") == 123.45
assert (
parser._convert_param_value("123.0", "number") == 123
) # Should be int when whole number
assert parser._convert_param_value("123.5", "number") == 123.5
# Invalid float should return original string
assert parser._convert_param_value("abc", "float") == "abc"
# Test boolean type - valid boolean values
assert parser._convert_param_value("true", "boolean") is True
assert parser._convert_param_value("false", "bool") is False
assert parser._convert_param_value("1", "boolean") is True
assert parser._convert_param_value("0", "boolean") is False
# Invalid boolean should return original string
assert parser._convert_param_value("yes", "boolean") == "yes"
assert parser._convert_param_value("no", "bool") == "no"
# Test null value
assert parser._convert_param_value("null", "string") is None
assert parser._convert_param_value("null", "integer") is None
# Test object/array type (JSON)
assert parser._convert_param_value('{"key": "value"}', "object") == {"key": "value"}
assert parser._convert_param_value("[1, 2, 3]", "array") == [1, 2, 3]
# Invalid JSON should return original string
assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
# Test fallback for unknown type (tries json.loads, then returns original)
assert parser._convert_param_value('{"key": "value"}', "unknown") == {
"key": "value"
}
assert parser._convert_param_value("plain text", "unknown") == "plain text"
def test_convert_param_value_multi_typed_values(parser):
"""Test _convert_param_value with multi-typed values (list of types)."""
# Test with list of types where first type succeeds
assert parser._convert_param_value("123", ["integer", "string"]) == 123
assert parser._convert_param_value("true", ["boolean", "string"]) is True
assert parser._convert_param_value('{"x": 1}', ["object", "string"]) == {"x": 1}
# Test with list of types where first type fails but second succeeds
# "abc" is not a valid integer, so should try string next
assert parser._convert_param_value("abc", ["integer", "string"]) == "abc"
# Test with list of types where all fail - should return original value
# "invalid json" is not valid JSON, last type is "object" which will fail JSON parse
result = parser._convert_param_value("invalid json", ["integer", "object"])
assert result == "invalid json" # Returns original value after all types fail
# Test with three types
assert parser._convert_param_value("123.5", ["integer", "float", "string"]) == 123.5
assert parser._convert_param_value("true", ["integer", "boolean", "string"]) is True
# Test with null in multi-type list
assert parser._convert_param_value("null", ["integer", "string"]) is None
assert parser._convert_param_value("null", ["boolean", "object"]) is None
# Test nested type conversion - boolean fails, integer succeeds
value = parser._convert_param_value("123", ["boolean", "integer", "string"])
assert value == 123 # Should be integer, not boolean
# Test that order matters
assert (
parser._convert_param_value("123", ["string", "integer"]) == "123"
) # String first
assert (
parser._convert_param_value("123", ["integer", "string"]) == 123
) # Integer first
# Test with all types failing - returns original value
assert (
parser._convert_param_value("not_a_number", ["integer", "float", "boolean"])
== "not_a_number"
)
def test_convert_param_value_stricter_type_checking(parser):
"""Test stricter type checking in the updated implementation."""
# Boolean now has stricter validation
assert parser._convert_param_value("true", "boolean") is True
assert parser._convert_param_value("false", "boolean") is False
assert parser._convert_param_value("1", "boolean") is True
assert parser._convert_param_value("0", "boolean") is False
# These should return original string (not valid boolean values)
assert parser._convert_param_value("yes", "boolean") == "yes"
assert parser._convert_param_value("no", "boolean") == "no"
assert parser._convert_param_value("TRUE", "boolean") is True
assert parser._convert_param_value("FALSE", "boolean") is False
# Integer and float now raise exceptions for invalid values
assert parser._convert_param_value("123abc", "integer") == "123abc"
assert parser._convert_param_value("123.45.67", "float") == "123.45.67"
# JSON parsing is stricter - invalid JSON returns original
assert parser._convert_param_value("{invalid: json}", "object") == "{invalid: json}"
assert parser._convert_param_value("[1, 2,", "array") == "[1, 2,"
# Test multi-type with stricter checking
# "yes" is not valid boolean, but string would accept it
assert parser._convert_param_value("yes", ["boolean", "string"]) == "yes"
# "123abc" is not valid integer or float, but string accepts it
assert (
parser._convert_param_value("123abc", ["integer", "float", "string"])
== "123abc"
)
def test_convert_param_value_edge_cases(parser):
"""Test edge cases for _convert_param_value."""
# Empty string
assert parser._convert_param_value("", "string") == ""
assert (
parser._convert_param_value("", "integer") == ""
) # Invalid int returns original
# Whitespace - trimmed by conversion functions
assert parser._convert_param_value(" 123 ", "integer") == 123
assert parser._convert_param_value(" true ", "boolean") is True
# Numeric strings with special characters
assert parser._convert_param_value("123.45.67", "float") == "123.45.67"
assert parser._convert_param_value("123abc", "integer") == "123abc"
# JSON with whitespace - should parse correctly
assert parser._convert_param_value(' { "key" : "value" } ', "object") == {
"key": "value"
}
# Invalid JSON returns original
assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
assert parser._convert_param_value("[1, 2,", "array") == "[1, 2,"
def test_convert_param_value_checked_helper(parser):
"""Test the _convert_param_value_checked helper function indirectly."""
# This tests the behavior through the main function
# Valid conversions should work
assert parser._convert_param_value("123", "integer") == 123
assert parser._convert_param_value("123.45", "float") == 123.45
assert parser._convert_param_value("true", "boolean") is True
assert parser._convert_param_value('{"x": 1}', "object") == {"x": 1}
# Invalid conversions should return original value (exception caught)
assert parser._convert_param_value("abc", "integer") == "abc"
assert parser._convert_param_value("abc", "float") == "abc"
assert parser._convert_param_value("yes", "boolean") == "yes"
assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
# Test that null handling works in checked function
assert parser._convert_param_value("null", "integer") is None
assert parser._convert_param_value("null", "boolean") is None
assert parser._convert_param_value("null", "object") is None

View File

@@ -100,7 +100,7 @@ class DeepSeekV32ToolParser(ToolParser):
param_dict[param_name] = param_val
return param_dict
def _convert_param_value(self, value: str, param_type: str) -> Any:
def _convert_param_value_checked(self, value: str, param_type: str) -> Any:
"""Convert parameter value to the correct type."""
if value.lower() == "null":
return None
@@ -109,29 +109,31 @@ class DeepSeekV32ToolParser(ToolParser):
if param_type in ["string", "str", "text"]:
return value
elif param_type in ["integer", "int"]:
try:
return int(value)
except (ValueError, TypeError):
return value
return int(value)
elif param_type in ["number", "float"]:
try:
val = float(value)
return val if val != int(val) else int(val)
except (ValueError, TypeError):
return value
val = float(value)
return val if val != int(val) else int(val)
elif param_type in ["boolean", "bool"]:
value = value.strip()
if value.lower() not in ["false", "0", "true", "1"]:
raise ValueError("Invalid boolean value")
return value.lower() in ["true", "1"]
elif param_type in ["object", "array"]:
try:
return json.loads(value)
except json.JSONDecodeError:
return value
return json.loads(value)
else:
# Try JSON parse first, fallback to string
return json.loads(value)
def _convert_param_value(self, value: str, param_type: str | list[str]) -> Any:
"""Convert parameter value to the correct type."""
if not isinstance(param_type, list):
param_type = [param_type]
for current_type in param_type:
try:
return json.loads(value)
except json.JSONDecodeError:
return value
return self._convert_param_value_checked(value, current_type)
except Exception:
continue
# return value as fallback
return value
def _convert_params_with_schema(
self,