[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:
Ace Eldeib
2026-05-21 00:36:16 +00:00
committed by GitHub
co-authored by Flora Feng
parent bde560ed6e
commit 63ea11709b
2 changed files with 186 additions and 0 deletions
@@ -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"}],
}