forked from Karylab-cklius/vllm
[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>
This commit is contained in:
@@ -221,6 +221,99 @@ class TestExtractToolCalls:
|
||||
assert args == {"value": 42}
|
||||
assert isinstance(args["value"], int)
|
||||
|
||||
@pytest.mark.skip_global_cleanup
|
||||
def test_composed_schema_converts_object_and_array_params(self):
|
||||
"""Composed JSON Schema types must still drive DSML type coercion."""
|
||||
tool = ChatCompletionToolsParam(
|
||||
function=FunctionDefinition(
|
||||
name="set_timer",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"wait": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"const": "until"},
|
||||
"date": {"type": "string"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"const": "for"},
|
||||
"minutes": {"type": "number"},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"patches": {
|
||||
"oneOf": [
|
||||
{"type": "array", "items": {"type": "object"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
parser = make_parser(tools=[tool])
|
||||
model_output = (
|
||||
f"{FC_START}\n"
|
||||
f'{INV_START}set_timer">\n'
|
||||
f'{PARAM_START}wait" string="false">'
|
||||
f'{{"type":"for","minutes":2880}}'
|
||||
f"{PARAM_END}\n"
|
||||
f'{PARAM_START}patches" string="false">'
|
||||
f'[{{"op":"replace","path":"/schedule","value":"quiet"}}]'
|
||||
f"{PARAM_END}\n"
|
||||
f"{INV_END}\n"
|
||||
f"{FC_END}"
|
||||
)
|
||||
result = parser.extract_tool_calls(model_output, None)
|
||||
assert result.tools_called
|
||||
args = json.loads(result.tool_calls[0].function.arguments)
|
||||
assert args == {
|
||||
"wait": {"type": "for", "minutes": 2880},
|
||||
"patches": [{"op": "replace", "path": "/schedule", "value": "quiet"}],
|
||||
}
|
||||
assert isinstance(args["wait"], dict)
|
||||
assert isinstance(args["patches"], list)
|
||||
|
||||
@pytest.mark.skip_global_cleanup
|
||||
def test_string_attr_true_preserves_literal_for_composed_schema(self):
|
||||
tool = ChatCompletionToolsParam(
|
||||
function=FunctionDefinition(
|
||||
name="set_timer",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"wait": {
|
||||
"anyOf": [
|
||||
{"type": "object"},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
parser = make_parser(tools=[tool])
|
||||
model_output = (
|
||||
f"{FC_START}\n"
|
||||
f'{INV_START}set_timer">\n'
|
||||
f'{PARAM_START}wait" string="true">'
|
||||
f'{{"type":"for","minutes":2880}}'
|
||||
f"{PARAM_END}\n"
|
||||
f"{INV_END}\n"
|
||||
f"{FC_END}"
|
||||
)
|
||||
result = parser.extract_tool_calls(model_output, None)
|
||||
assert result.tools_called
|
||||
args = json.loads(result.tool_calls[0].function.arguments)
|
||||
assert args == {"wait": '{"type":"for","minutes":2880}'}
|
||||
|
||||
def test_arguments_wrapper_repaired(self):
|
||||
"""A single 'arguments' wrapper parameter must be unwrapped when it
|
||||
is not part of the tool schema and the inner object matches schema fields."""
|
||||
@@ -581,6 +674,50 @@ class TestExtractToolCallsStreaming:
|
||||
assert args == {"value": "42"}
|
||||
assert isinstance(args["value"], str)
|
||||
|
||||
@pytest.mark.skip_global_cleanup
|
||||
def test_composed_schema_conversion_in_streaming(self):
|
||||
tool = ChatCompletionToolsParam(
|
||||
function=FunctionDefinition(
|
||||
name="set_timer",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"wait": {
|
||||
"anyOf": [
|
||||
{"type": "object"},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
"patches": {
|
||||
"oneOf": [
|
||||
{"type": "array", "items": {"type": "object"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
parser = make_parser(tools=[tool])
|
||||
full_text = (
|
||||
f"{FC_START}\n"
|
||||
f'{INV_START}set_timer">\n'
|
||||
f'{PARAM_START}wait" string="false">'
|
||||
f'{{"type":"for","minutes":2880}}'
|
||||
f"{PARAM_END}\n"
|
||||
f'{PARAM_START}patches" string="false">'
|
||||
f'[{{"op":"replace","path":"/schedule","value":"quiet"}}]'
|
||||
f"{PARAM_END}\n"
|
||||
f"{INV_END}\n"
|
||||
f"{FC_END}"
|
||||
)
|
||||
deltas = self._stream(parser, full_text)
|
||||
args = json.loads(self._reconstruct_args(deltas))
|
||||
assert args == {
|
||||
"wait": {"type": "for", "minutes": 2880},
|
||||
"patches": [{"op": "replace", "path": "/schedule", "value": "quiet"}],
|
||||
}
|
||||
|
||||
def test_multiple_tools_streaming(self, parser):
|
||||
full_text = (
|
||||
f"{FC_START}\n"
|
||||
|
||||
@@ -236,3 +236,52 @@ def test_extract_tool_calls_arguments_wrapper():
|
||||
assert result.tools_called
|
||||
args = json.loads(result.tool_calls[0].function.arguments)
|
||||
assert args == {"location": "Beijing"}
|
||||
|
||||
|
||||
@pytest.mark.skip_global_cleanup
|
||||
def test_composed_schema_converts_object_and_array_params():
|
||||
tool = ChatCompletionToolsParam(
|
||||
type="function",
|
||||
function={
|
||||
"name": "set_timer",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"wait": {
|
||||
"anyOf": [
|
||||
{"type": "object"},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
"patches": {
|
||||
"allOf": [
|
||||
{"type": "array", "items": {"type": "object"}},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
parser = make_parser(tools=[tool])
|
||||
request = make_request(tools=[tool])
|
||||
model_output = (
|
||||
f"{TC_START}\n"
|
||||
f'{INV_START}set_timer">\n'
|
||||
f'{PARAM_START}wait" string="false">'
|
||||
f'{{"type":"for","minutes":2880}}'
|
||||
f"{PARAM_END}\n"
|
||||
f'{PARAM_START}patches" string="false">'
|
||||
f'[{{"op":"replace","path":"/schedule","value":"quiet"}}]'
|
||||
f"{PARAM_END}\n"
|
||||
f"{INV_END}\n"
|
||||
f"{TC_END}"
|
||||
)
|
||||
|
||||
result = parser.extract_tool_calls(model_output, request)
|
||||
|
||||
assert result.tools_called
|
||||
args = json.loads(result.tool_calls[0].function.arguments)
|
||||
assert args == {
|
||||
"wait": {"type": "for", "minutes": 2880},
|
||||
"patches": [{"op": "replace", "path": "/schedule", "value": "quiet"}],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user