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, 05:30 UTC
[Bugfix] Use shared coerce_to_schema_type in DeepSeekV32 tool parser (#43019)
Signed-off-by: sfeng33 <4florafeng@gmail.com>
Flora Feng committed
May 20, 2026 at 10:21 UTC
a10d69116cb25c8137eeb3f320add71d4e04fda9
2 files changed
+270
-285
tests/tool_parsers/test_deepseekv32_tool_parser.py
+261
-233
@@ -16,7 +16,6 @@ from vllm.entrypoints.openai.chat_completion.protocol import (
16
ChatCompletionToolsParam,
17
FunctionDefinition,
18
)
19
-from vllm.tokenizers import get_tokenizer
19
from vllm.tool_parsers.deepseekv32_tool_parser import DeepSeekV32ToolParser
20
21
# ---------------------------------------------------------------------------
@@ -65,58 +64,6 @@ def build_tool_call(func_name: str, params: dict[str, str]) -> str:
64
return f'{FC_START}\n{INV_START}{func_name}">\n{param_strs}\n{INV_END}\n{FC_END}'
65
66
68
-# ---------------------------------------------------------------------------
69
-# Tests: DeepSeekV32ToolParser._convert_param_value
70
-# ---------------------------------------------------------------------------
71
-
72
-
73
-class TestConvertParamValue:
74
- @pytest.fixture
75
- def parser(self):
76
- return make_parser()
77
-
78
- def test_null(self, parser):
79
- assert parser._convert_param_value("null", "string") is None
80
- assert parser._convert_param_value("NULL", "integer") is None
81
-
82
- def test_string(self, parser):
83
- assert parser._convert_param_value("hello", "string") == "hello"
84
-
85
- def test_integer_valid(self, parser):
86
- assert parser._convert_param_value("42", "integer") == 42
87
-
88
- def test_integer_invalid_falls_back_to_str(self, parser):
89
- assert parser._convert_param_value("abc", "int") == "abc"
90
-
91
- def test_number_float(self, parser):
92
- assert parser._convert_param_value("3.14", "number") == pytest.approx(3.14)
93
-
94
- def test_number_whole_returns_int(self, parser):
95
- assert parser._convert_param_value("5.0", "number") == 5
96
- assert isinstance(parser._convert_param_value("5.0", "number"), int)
97
-
98
- def test_boolean_true(self, parser):
99
- assert parser._convert_param_value("true", "boolean") is True
100
- assert parser._convert_param_value("1", "bool") is True
101
-
102
- def test_boolean_false(self, parser):
103
- assert parser._convert_param_value("false", "boolean") is False
104
- assert parser._convert_param_value("False", "bool") is False
105
-
106
- def test_object_valid_json(self, parser):
107
- assert parser._convert_param_value('{"k": 1}', "object") == {"k": 1}
108
-
109
- def test_object_invalid_json_falls_back(self, parser):
110
- assert parser._convert_param_value("not-json", "object") == "not-json"
111
-
112
- def test_array_valid_json(self, parser):
113
- assert parser._convert_param_value("[1, 2]", "array") == [1, 2]
114
-
115
- def test_unknown_type_tries_json_then_string(self, parser):
116
- assert parser._convert_param_value("123", "unknown") == 123
117
- assert parser._convert_param_value("hello", "unknown") == "hello"
118
-
119
-
67
# ---------------------------------------------------------------------------
68
# Tests: extract_tool_calls (non-streaming)
69
# ---------------------------------------------------------------------------
@@ -331,6 +278,163 @@ class TestExtractToolCalls:
278
args = json.loads(result.tool_calls[0].function.arguments)
279
assert args == {"location": "Beijing"}
280
281
+ def test_object_and_array_params(self):
282
+ """Object and array schema types must be JSON-parsed, not left as strings."""
283
+ tool = ChatCompletionToolsParam(
284
+ function=FunctionDefinition(
285
+ name="update",
286
+ parameters={
287
+ "type": "object",
288
+ "properties": {
289
+ "tags": {"type": "array"},
290
+ "meta": {"type": "object"},
291
+ },
292
+ },
293
+ ),
294
+ )
295
+ parser = make_parser(tools=[tool])
296
+ model_output = (
297
+ f"{FC_START}\n"
298
+ f'{INV_START}update">\n'
299
+ f'{PARAM_START}tags" string="false">["a", "b"]{PARAM_END}\n'
300
+ f'{PARAM_START}meta" string="false">{{"k": 1}}{PARAM_END}\n'
301
+ f"{INV_END}\n"
302
+ f"{FC_END}"
303
+ )
304
+ result = parser.extract_tool_calls(model_output, None)
305
+ assert result.tools_called
306
+ args = json.loads(result.tool_calls[0].function.arguments)
307
+ assert args["tags"] == ["a", "b"]
308
+ assert isinstance(args["tags"], list)
309
+ assert args["meta"] == {"k": 1}
310
+ assert isinstance(args["meta"], dict)
311
+
312
+ def test_number_param(self):
313
+ """Number (float) schema type must be converted correctly."""
314
+ tool = ChatCompletionToolsParam(
315
+ function=FunctionDefinition(
316
+ name="measure",
317
+ parameters={
318
+ "type": "object",
319
+ "properties": {
320
+ "ratio": {"type": "number"},
321
+ "whole": {"type": "number"},
322
+ },
323
+ },
324
+ ),
325
+ )
326
+ parser = make_parser(tools=[tool])
327
+ model_output = (
328
+ f"{FC_START}\n"
329
+ f'{INV_START}measure">\n'
330
+ f'{PARAM_START}ratio" string="false">3.14{PARAM_END}\n'
331
+ f'{PARAM_START}whole" string="false">5.0{PARAM_END}\n'
332
+ f"{INV_END}\n"
333
+ f"{FC_END}"
334
+ )
335
+ result = parser.extract_tool_calls(model_output, None)
336
+ args = json.loads(result.tool_calls[0].function.arguments)
337
+ assert args["ratio"] == pytest.approx(3.14)
338
+ assert args["whole"] == 5
339
+ assert isinstance(args["whole"], int)
340
+
341
+ def test_multi_typed_schema(self):
342
+ """Schema with type: ["integer", "null"] must handle both cases."""
343
+ tool = ChatCompletionToolsParam(
344
+ function=FunctionDefinition(
345
+ name="set_val",
346
+ parameters={
347
+ "type": "object",
348
+ "properties": {
349
+ "count": {"type": ["integer", "null"]},
350
+ "label": {"type": ["string", "null"]},
351
+ },
352
+ },
353
+ ),
354
+ )
355
+ parser = make_parser(tools=[tool])
356
+ model_output = (
357
+ f"{FC_START}\n"
358
+ f'{INV_START}set_val">\n'
359
+ f'{PARAM_START}count" string="false">42{PARAM_END}\n'
360
+ f'{PARAM_START}label" string="false">hello{PARAM_END}\n'
361
+ f"{INV_END}\n"
362
+ f"{FC_END}"
363
+ )
364
+ result = parser.extract_tool_calls(model_output, None)
365
+ args = json.loads(result.tool_calls[0].function.arguments)
366
+ assert args["count"] == 42
367
+ assert isinstance(args["count"], int)
368
+ assert args["label"] == "hello"
369
+
370
+ def test_multi_typed_null_value(self):
371
+ """Literal 'null' must become None when the schema includes 'null'."""
372
+ tool = ChatCompletionToolsParam(
373
+ function=FunctionDefinition(
374
+ name="clear",
375
+ parameters={
376
+ "type": "object",
377
+ "properties": {
378
+ "value": {"type": ["integer", "null"]},
379
+ },
380
+ },
381
+ ),
382
+ )
383
+ parser = make_parser(tools=[tool])
384
+ model_output = (
385
+ f"{FC_START}\n"
386
+ f'{INV_START}clear">\n'
387
+ f'{PARAM_START}value" string="false">null{PARAM_END}\n'
388
+ f"{INV_END}\n"
389
+ f"{FC_END}"
390
+ )
391
+ result = parser.extract_tool_calls(model_output, None)
392
+ args = json.loads(result.tool_calls[0].function.arguments)
393
+ assert args["value"] is None
394
+
395
+ def test_null_not_coerced_without_null_in_schema(self):
396
+ """Literal 'null' must stay as a string when the schema is just 'string'."""
397
+ tool = ChatCompletionToolsParam(
398
+ function=FunctionDefinition(
399
+ name="echo",
400
+ parameters={
401
+ "type": "object",
402
+ "properties": {
403
+ "text": {"type": "string"},
404
+ },
405
+ },
406
+ ),
407
+ )
408
+ parser = make_parser(tools=[tool])
409
+ model_output = (
410
+ f"{FC_START}\n"
411
+ f'{INV_START}echo">\n'
412
+ f'{PARAM_START}text" string="false">null{PARAM_END}\n'
413
+ f"{INV_END}\n"
414
+ f"{FC_END}"
415
+ )
416
+ result = parser.extract_tool_calls(model_output, None)
417
+ args = json.loads(result.tool_calls[0].function.arguments)
418
+ assert args["text"] == "null"
419
+ assert isinstance(args["text"], str)
420
+
421
+ def test_no_schema_keeps_strings(self):
422
+ """Without a tool schema, all string='false' params default to string."""
423
+ parser = make_parser(tools=None)
424
+ model_output = (
425
+ f"{FC_START}\n"
426
+ f'{INV_START}unknown_fn">\n'
427
+ f'{PARAM_START}count" string="false">42{PARAM_END}\n'
428
+ f'{PARAM_START}flag" string="false">true{PARAM_END}\n'
429
+ f"{INV_END}\n"
430
+ f"{FC_END}"
431
+ )
432
+ result = parser.extract_tool_calls(model_output, None)
433
+ assert result.tools_called
434
+ args = json.loads(result.tool_calls[0].function.arguments)
435
+ assert args["count"] == "42"
436
+ assert args["flag"] == "true"
437
+
438
439
# ---------------------------------------------------------------------------
440
# Tests: extract_tool_calls_streaming
@@ -713,6 +817,110 @@ class TestExtractToolCallsStreaming:
817
content = "".join(d.content for d in deltas if d.content is not None)
818
assert content == full_text
819
820
+ def test_object_and_array_params_streaming(self):
821
+ """Streaming: object/array params must be JSON-parsed."""
822
+ tool = ChatCompletionToolsParam(
823
+ function=FunctionDefinition(
824
+ name="update",
825
+ parameters={
826
+ "type": "object",
827
+ "properties": {
828
+ "tags": {"type": "array"},
829
+ "meta": {"type": "object"},
830
+ },
831
+ },
832
+ ),
833
+ )
834
+ parser = make_parser(tools=[tool])
835
+ full_text = (
836
+ f"{FC_START}\n"
837
+ f'{INV_START}update">\n'
838
+ f'{PARAM_START}tags" string="false">["a", "b"]{PARAM_END}\n'
839
+ f'{PARAM_START}meta" string="false">{{"k": 1}}{PARAM_END}\n'
840
+ f"{INV_END}\n"
841
+ f"{FC_END}"
842
+ )
843
+ deltas = self._stream(parser, full_text)
844
+ args = json.loads(self._reconstruct_args(deltas))
845
+ assert args["tags"] == ["a", "b"]
846
+ assert args["meta"] == {"k": 1}
847
+
848
+ def test_multi_typed_schema_streaming(self):
849
+ """Streaming: type: ["integer", "null"] must coerce correctly."""
850
+ tool = ChatCompletionToolsParam(
851
+ function=FunctionDefinition(
852
+ name="set_val",
853
+ parameters={
854
+ "type": "object",
855
+ "properties": {
856
+ "count": {"type": ["integer", "null"]},
857
+ },
858
+ },
859
+ ),
860
+ )
861
+ parser = make_parser(tools=[tool])
862
+ full_text = (
863
+ f"{FC_START}\n"
864
+ f'{INV_START}set_val">\n'
865
+ f'{PARAM_START}count" string="false">42{PARAM_END}\n'
866
+ f"{INV_END}\n"
867
+ f"{FC_END}"
868
+ )
869
+ deltas = self._stream(parser, full_text)
870
+ args = json.loads(self._reconstruct_args(deltas))
871
+ assert args["count"] == 42
872
+ assert isinstance(args["count"], int)
873
+
874
+ def test_multi_typed_null_streaming(self):
875
+ """Streaming: 'null' with ["integer", "null"] schema must become None."""
876
+ tool = ChatCompletionToolsParam(
877
+ function=FunctionDefinition(
878
+ name="clear",
879
+ parameters={
880
+ "type": "object",
881
+ "properties": {
882
+ "value": {"type": ["integer", "null"]},
883
+ },
884
+ },
885
+ ),
886
+ )
887
+ parser = make_parser(tools=[tool])
888
+ full_text = (
889
+ f"{FC_START}\n"
890
+ f'{INV_START}clear">\n'
891
+ f'{PARAM_START}value" string="false">null{PARAM_END}\n'
892
+ f"{INV_END}\n"
893
+ f"{FC_END}"
894
+ )
895
+ deltas = self._stream(parser, full_text)
896
+ args = json.loads(self._reconstruct_args(deltas))
897
+ assert args["value"] is None
898
+
899
+ def test_number_param_streaming(self):
900
+ """Streaming: number type must be converted."""
901
+ tool = ChatCompletionToolsParam(
902
+ function=FunctionDefinition(
903
+ name="measure",
904
+ parameters={
905
+ "type": "object",
906
+ "properties": {
907
+ "ratio": {"type": "number"},
908
+ },
909
+ },
910
+ ),
911
+ )
912
+ parser = make_parser(tools=[tool])
913
+ full_text = (
914
+ f"{FC_START}\n"
915
+ f'{INV_START}measure">\n'
916
+ f'{PARAM_START}ratio" string="false">3.14{PARAM_END}\n'
917
+ f"{INV_END}\n"
918
+ f"{FC_END}"
919
+ )
920
+ deltas = self._stream(parser, full_text)
921
+ args = json.loads(self._reconstruct_args(deltas))
922
+ assert args["ratio"] == pytest.approx(3.14)
923
+
924
925
class TestDelimiterPreservation:
926
"""Regression: fast detokenization skipping DSML delimiters (PR #33964)."""
@@ -794,183 +1002,3 @@ class TestDelimiterPreservation:
1002
assert reconstructor.tool_calls[0].function.name == "search"
1003
streamed_args = json.loads(reconstructor.tool_calls[0].function.arguments)
1004
assert streamed_args == ns_args
797
-
798
-
799
-@pytest.fixture(scope="module")
800
-def deepseekv32_tokenizer():
801
- return get_tokenizer(tokenizer_name="deepseek-ai/DeepSeek-V3.2")
802
-
803
-
804
-@pytest.fixture
805
-def parser(deepseekv32_tokenizer):
806
- return DeepSeekV32ToolParser(deepseekv32_tokenizer)
807
-
808
-
809
-def test_convert_param_value_single_types(parser):
810
- """Test _convert_param_value with single type parameters."""
811
- # Test string type
812
- assert parser._convert_param_value("hello", "string") == "hello"
813
- assert parser._convert_param_value("123", "string") == "123"
814
-
815
- # Test integer type - valid integers
816
- assert parser._convert_param_value("123", "integer") == 123
817
- assert parser._convert_param_value("456", "int") == 456
818
- # Invalid integer should return original string (due to exception catch)
819
- assert parser._convert_param_value("abc", "integer") == "abc"
820
-
821
- # Test float/number type
822
- assert parser._convert_param_value("123.45", "float") == 123.45
823
- assert (
824
- parser._convert_param_value("123.0", "number") == 123
825
- ) # Should be int when whole number
826
- assert parser._convert_param_value("123.5", "number") == 123.5
827
- # Invalid float should return original string
828
- assert parser._convert_param_value("abc", "float") == "abc"
829
-
830
- # Test boolean type - valid boolean values
831
- assert parser._convert_param_value("true", "boolean") is True
832
- assert parser._convert_param_value("false", "bool") is False
833
- assert parser._convert_param_value("1", "boolean") is True
834
- assert parser._convert_param_value("0", "boolean") is False
835
- # Invalid boolean should return original string
836
- assert parser._convert_param_value("yes", "boolean") == "yes"
837
- assert parser._convert_param_value("no", "bool") == "no"
838
-
839
- # Test null value
840
- assert parser._convert_param_value("null", "string") is None
841
- assert parser._convert_param_value("null", "integer") is None
842
-
843
- # Test object/array type (JSON)
844
- assert parser._convert_param_value('{"key": "value"}', "object") == {"key": "value"}
845
- assert parser._convert_param_value("[1, 2, 3]", "array") == [1, 2, 3]
846
- # Invalid JSON should return original string
847
- assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
848
-
849
- # Test fallback for unknown type (tries json.loads, then returns original)
850
- assert parser._convert_param_value('{"key": "value"}', "unknown") == {
851
- "key": "value"
852
- }
853
- assert parser._convert_param_value("plain text", "unknown") == "plain text"
854
-
855
-
856
-def test_convert_param_value_multi_typed_values(parser):
857
- """Test _convert_param_value with multi-typed values (list of types)."""
858
- # Test with list of types where first type succeeds
859
- assert parser._convert_param_value("123", ["integer", "string"]) == 123
860
- assert parser._convert_param_value("true", ["boolean", "string"]) is True
861
- assert parser._convert_param_value('{"x": 1}', ["object", "string"]) == {"x": 1}
862
-
863
- # Test with list of types where first type fails but second succeeds
864
- # "abc" is not a valid integer, so should try string next
865
- assert parser._convert_param_value("abc", ["integer", "string"]) == "abc"
866
-
867
- # Test with list of types where all fail - should return original value
868
- # "invalid json" is not valid JSON, last type is "object" which will fail JSON parse
869
- result = parser._convert_param_value("invalid json", ["integer", "object"])
870
- assert result == "invalid json" # Returns original value after all types fail
871
-
872
- # Test with three types
873
- assert parser._convert_param_value("123.5", ["integer", "float", "string"]) == 123.5
874
- assert parser._convert_param_value("true", ["integer", "boolean", "string"]) is True
875
-
876
- # Test with null in multi-type list
877
- assert parser._convert_param_value("null", ["integer", "string"]) is None
878
- assert parser._convert_param_value("null", ["boolean", "object"]) is None
879
-
880
- # Test nested type conversion - boolean fails, integer succeeds
881
- value = parser._convert_param_value("123", ["boolean", "integer", "string"])
882
- assert value == 123 # Should be integer, not boolean
883
-
884
- # Test that order matters
885
- assert (
886
- parser._convert_param_value("123", ["string", "integer"]) == "123"
887
- ) # String first
888
- assert (
889
- parser._convert_param_value("123", ["integer", "string"]) == 123
890
- ) # Integer first
891
-
892
- # Test with all types failing - returns original value
893
- assert (
894
- parser._convert_param_value("not_a_number", ["integer", "float", "boolean"])
895
- == "not_a_number"
896
- )
897
-
898
-
899
-def test_convert_param_value_stricter_type_checking(parser):
900
- """Test stricter type checking in the updated implementation."""
901
- # Boolean now has stricter validation
902
- assert parser._convert_param_value("true", "boolean") is True
903
- assert parser._convert_param_value("false", "boolean") is False
904
- assert parser._convert_param_value("1", "boolean") is True
905
- assert parser._convert_param_value("0", "boolean") is False
906
-
907
- # These should return original string (not valid boolean values)
908
- assert parser._convert_param_value("yes", "boolean") == "yes"
909
- assert parser._convert_param_value("no", "boolean") == "no"
910
- assert parser._convert_param_value("TRUE", "boolean") is True
911
- assert parser._convert_param_value("FALSE", "boolean") is False
912
-
913
- # Integer and float now raise exceptions for invalid values
914
- assert parser._convert_param_value("123abc", "integer") == "123abc"
915
- assert parser._convert_param_value("123.45.67", "float") == "123.45.67"
916
-
917
- # JSON parsing is stricter - invalid JSON returns original
918
- assert parser._convert_param_value("{invalid: json}", "object") == "{invalid: json}"
919
- assert parser._convert_param_value("[1, 2,", "array") == "[1, 2,"
920
-
921
- # Test multi-type with stricter checking
922
- # "yes" is not valid boolean, but string would accept it
923
- assert parser._convert_param_value("yes", ["boolean", "string"]) == "yes"
924
-
925
- # "123abc" is not valid integer or float, but string accepts it
926
- assert (
927
- parser._convert_param_value("123abc", ["integer", "float", "string"])
928
- == "123abc"
929
- )
930
-
931
-
932
-def test_convert_param_value_edge_cases(parser):
933
- """Test edge cases for _convert_param_value."""
934
- # Empty string
935
- assert parser._convert_param_value("", "string") == ""
936
- assert (
937
- parser._convert_param_value("", "integer") == ""
938
- ) # Invalid int returns original
939
-
940
- # Whitespace - trimmed by conversion functions
941
- assert parser._convert_param_value(" 123 ", "integer") == 123
942
- assert parser._convert_param_value(" true ", "boolean") is True
943
-
944
- # Numeric strings with special characters
945
- assert parser._convert_param_value("123.45.67", "float") == "123.45.67"
946
- assert parser._convert_param_value("123abc", "integer") == "123abc"
947
-
948
- # JSON with whitespace - should parse correctly
949
- assert parser._convert_param_value(' { "key" : "value" } ', "object") == {
950
- "key": "value"
951
- }
952
-
953
- # Invalid JSON returns original
954
- assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
955
- assert parser._convert_param_value("[1, 2,", "array") == "[1, 2,"
956
-
957
-
958
-def test_convert_param_value_checked_helper(parser):
959
- """Test the _convert_param_value_checked helper function indirectly."""
960
- # This tests the behavior through the main function
961
- # Valid conversions should work
962
- assert parser._convert_param_value("123", "integer") == 123
963
- assert parser._convert_param_value("123.45", "float") == 123.45
964
- assert parser._convert_param_value("true", "boolean") is True
965
- assert parser._convert_param_value('{"x": 1}', "object") == {"x": 1}
966
-
967
- # Invalid conversions should return original value (exception caught)
968
- assert parser._convert_param_value("abc", "integer") == "abc"
969
- assert parser._convert_param_value("abc", "float") == "abc"
970
- assert parser._convert_param_value("yes", "boolean") == "yes"
971
- assert parser._convert_param_value("{invalid}", "object") == "{invalid}"
972
-
973
- # Test that null handling works in checked function
974
- assert parser._convert_param_value("null", "integer") is None
975
- assert parser._convert_param_value("null", "boolean") is None
976
- assert parser._convert_param_value("null", "object") is None
vllm/tool_parsers/deepseekv32_tool_parser.py
+9
-52
@@ -26,7 +26,12 @@ from vllm.tool_parsers.abstract_tool_parser import (
26
Tool,
27
ToolParser,
28
)
29
-from vllm.tool_parsers.utils import partial_tag_overlap
29
+from vllm.tool_parsers.utils import (
30
+ coerce_to_schema_type,
31
+ extract_types_from_schema,
32
+ find_tool_properties,
33
+ partial_tag_overlap,
34
+)
35
36
logger = init_logger(__name__)
37
@@ -109,41 +114,6 @@ class DeepSeekV32ToolParser(ToolParser):
114
param_dict[param_name] = (param_val, string_attr)
115
return param_dict
116
112
- def _convert_param_value_checked(self, value: str, param_type: str) -> Any:
113
- """Convert parameter value to the correct type."""
114
- if value.lower() == "null":
115
- return None
116
-
117
- param_type = param_type.lower()
118
- if param_type in ["string", "str", "text"]:
119
- return value
120
- elif param_type in ["integer", "int"]:
121
- return int(value)
122
- elif param_type in ["number", "float"]:
123
- val = float(value)
124
- return val if val != int(val) else int(val)
125
- elif param_type in ["boolean", "bool"]:
126
- value = value.strip()
127
- if value.lower() not in ["false", "0", "true", "1"]:
128
- raise ValueError("Invalid boolean value")
129
- return value.lower() in ["true", "1"]
130
- elif param_type in ["object", "array"]:
131
- return json.loads(value)
132
- else:
133
- return json.loads(value)
134
-
135
- def _convert_param_value(self, value: str, param_type: str | list[str]) -> Any:
136
- """Convert parameter value to the correct type."""
137
- if not isinstance(param_type, list):
138
- param_type = [param_type]
139
- for current_type in param_type:
140
- try:
141
- return self._convert_param_value_checked(value, current_type)
142
- except Exception:
143
- continue
144
- # return value as fallback
145
- return value
146
-
117
@staticmethod
118
def _repair_param_dict(
119
param_dict: dict[str, Any],
@@ -172,18 +142,7 @@ class DeepSeekV32ToolParser(ToolParser):
142
param_dict: dict[str, tuple[str, str]],
143
) -> dict[str, Any]:
144
"""Convert raw string param values using the tool schema types."""
175
- param_config: dict = {}
176
- if self.tools:
177
- for tool in self.tools:
178
- if (
179
- hasattr(tool, "function")
180
- and tool.function.name == function_name
181
- and hasattr(tool.function, "parameters")
182
- ):
183
- schema = tool.function.parameters
184
- if isinstance(schema, dict) and "properties" in schema:
185
- param_config = schema["properties"]
186
- break
145
+ param_config = find_tool_properties(self.tools, function_name)
146
147
converted: dict[str, Any] = {}
148
for name, (value, string_attr) in param_dict.items():
@@ -191,10 +150,8 @@ class DeepSeekV32ToolParser(ToolParser):
150
converted[name] = value
151
continue
152
194
- param_type = "string"
195
- if name in param_config and isinstance(param_config[name], dict):
196
- param_type = param_config[name].get("type", "string")
197
- converted[name] = self._convert_param_value(value, param_type)
153
+ param_types = extract_types_from_schema(param_config.get(name, {}))
154
+ converted[name] = coerce_to_schema_type(value, param_types)
155
return self._repair_param_dict(converted, param_config)
156
157
def extract_tool_calls(