[Misc] Add 20 regression tests for 11 tool parser bug fixes (#38172)
Signed-off-by: Ben Browning <bbrownin@redhat.com> Co-authored-by: Chauncey <chaunceyjiang@gmail.com>
This commit is contained in:
@@ -974,3 +974,157 @@ fahrenheit
|
||||
assert args["city"] == "Dallas"
|
||||
assert args["state"] == "TX"
|
||||
assert args["unit"] == "fahrenheit"
|
||||
|
||||
|
||||
def test_malformed_xml_no_gt_delimiter(qwen3_tool_parser, sample_tools):
|
||||
"""Regression: malformed XML without '>' must not crash (PR #36774)."""
|
||||
model_output = (
|
||||
"<tool_call>\n"
|
||||
"<function=get_current_weather\n"
|
||||
"<parameter=city>Dallas</parameter>\n"
|
||||
"</function>\n"
|
||||
"</tool_call>"
|
||||
)
|
||||
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[], tools=sample_tools)
|
||||
result = qwen3_tool_parser.extract_tool_calls(model_output, request=request)
|
||||
assert result is not None
|
||||
assert isinstance(result.tool_calls, list)
|
||||
assert all(tc is not None for tc in result.tool_calls)
|
||||
|
||||
|
||||
def test_none_tool_calls_filtered(qwen3_tool_parser, sample_tools):
|
||||
"""Regression: None tool calls filtered from output (PR #36774)."""
|
||||
model_output = (
|
||||
"<tool_call>\n"
|
||||
"<function=bad_func_no_gt\n"
|
||||
"</function>\n"
|
||||
"</tool_call>\n"
|
||||
"<tool_call>\n"
|
||||
"<function=get_current_weather>\n"
|
||||
"<parameter=city>Dallas</parameter>\n"
|
||||
"<parameter=state>TX</parameter>\n"
|
||||
"</function>\n"
|
||||
"</tool_call>"
|
||||
)
|
||||
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[], tools=sample_tools)
|
||||
result = qwen3_tool_parser.extract_tool_calls(model_output, request=request)
|
||||
assert all(tc is not None for tc in result.tool_calls)
|
||||
assert result.tools_called
|
||||
assert len(result.tool_calls) == 1
|
||||
assert result.tool_calls[0].function.name == "get_current_weather"
|
||||
args = json.loads(result.tool_calls[0].function.arguments)
|
||||
assert args["city"] == "Dallas"
|
||||
assert args["state"] == "TX"
|
||||
|
||||
|
||||
def test_anyof_parameter_not_double_encoded(qwen3_tokenizer):
|
||||
"""Regression: anyOf parameters must not be double-encoded (PR #36032)."""
|
||||
tools = [
|
||||
ChatCompletionToolsParam(
|
||||
type="function",
|
||||
function={
|
||||
"name": "update_record",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"anyOf": [{"type": "object"}, {"type": "null"}],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
parser = Qwen3CoderToolParser(qwen3_tokenizer, tools=tools)
|
||||
|
||||
model_output = (
|
||||
"<tool_call>\n"
|
||||
"<function=update_record>\n"
|
||||
'<parameter=data>{"key": "value", "count": 42}</parameter>\n'
|
||||
"</function>\n"
|
||||
"</tool_call>"
|
||||
)
|
||||
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[], tools=tools)
|
||||
result = parser.extract_tool_calls(model_output, request=request)
|
||||
|
||||
assert result.tools_called
|
||||
assert len(result.tool_calls) == 1
|
||||
args = json.loads(result.tool_calls[0].function.arguments)
|
||||
assert isinstance(args["data"], dict)
|
||||
assert args["data"] == {"key": "value", "count": 42}
|
||||
|
||||
|
||||
def test_streaming_multi_param_single_chunk(
|
||||
qwen3_tool_parser, qwen3_tokenizer, sample_tools
|
||||
):
|
||||
"""Regression: speculative decode delivering multiple params at once (PR #35615)."""
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[], tools=sample_tools)
|
||||
|
||||
deltas = [
|
||||
"<tool_call>",
|
||||
"\n<function=get_current_weather>",
|
||||
"\n", # triggers json_started -> sends "{"
|
||||
# This single delta delivers all three parameters at once
|
||||
"<parameter=city>\nDallas\n</parameter>"
|
||||
"\n<parameter=state>\nTX\n</parameter>"
|
||||
"\n<parameter=unit>\nfahrenheit\n</parameter>",
|
||||
"\n</function>",
|
||||
"\n</tool_call>",
|
||||
]
|
||||
|
||||
from tests.tool_parsers.utils import (
|
||||
run_tool_extraction_streaming,
|
||||
)
|
||||
|
||||
reconstructor = run_tool_extraction_streaming(
|
||||
qwen3_tool_parser,
|
||||
deltas,
|
||||
request,
|
||||
assert_one_tool_per_delta=False,
|
||||
)
|
||||
|
||||
assert len(reconstructor.tool_calls) == 1
|
||||
args = json.loads(reconstructor.tool_calls[0].function.arguments)
|
||||
assert args["city"] == "Dallas"
|
||||
assert args["state"] == "TX"
|
||||
assert args["unit"] == "fahrenheit"
|
||||
|
||||
|
||||
def test_no_double_serialization_string_args(qwen3_tool_parser):
|
||||
"""Regression: string arguments must not be double-serialized (PR #35615)."""
|
||||
tools = [
|
||||
ChatCompletionToolsParam(
|
||||
type="function",
|
||||
function={
|
||||
"name": "greet",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
model_output = (
|
||||
"<tool_call>\n"
|
||||
"<function=greet>\n"
|
||||
"<parameter=message>hello world</parameter>\n"
|
||||
"</function>\n"
|
||||
"</tool_call>"
|
||||
)
|
||||
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[], tools=tools)
|
||||
result = qwen3_tool_parser.extract_tool_calls(model_output, request=request)
|
||||
|
||||
assert result.tools_called
|
||||
assert len(result.tool_calls) == 1
|
||||
raw_arguments = result.tool_calls[0].function.arguments
|
||||
args = json.loads(raw_arguments)
|
||||
assert args["message"] == "hello world"
|
||||
assert '\\"hello world\\"' not in raw_arguments
|
||||
|
||||
Reference in New Issue
Block a user