mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-14 18:02:52 +02:00
chat : improve parsing of complex types in qwen3-coder (#28742)
* chat : improve schema support in qwen3 parser * cont : clean up grammar a bit
This commit is contained in:
@@ -104,9 +104,34 @@ common_chat_params common_chat_params_init_qwen3_coder(const common_chat_templat
|
||||
|
||||
auto arg_open = p.tool_arg_open("<parameter=" + p.tool_arg_name(p.literal(param.name)) + ">\n");
|
||||
|
||||
auto arg_value = param.schema->may_be_string() ?
|
||||
arg_string :
|
||||
p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
|
||||
auto types = param.schema->value_types();
|
||||
|
||||
auto arg_value = p.eps();
|
||||
if (!types.has(common_chat_schema::TYPE_STRING)) {
|
||||
arg_value = p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
|
||||
} else if (types.is_only(common_chat_schema::TYPE_STRING)) {
|
||||
arg_value = arg_string;
|
||||
} else {
|
||||
// The string alternative accepts any text, so the grammar only keeps the raw string
|
||||
// rule. The parser still tries the JSON alternatives first to type the value.
|
||||
auto json_value = p.choice();
|
||||
if (types.has(common_chat_schema::TYPE_OBJECT)) {
|
||||
json_value |= p.json_object();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_ARRAY)) {
|
||||
json_value |= p.json_array();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_NUMBER) || types.has(common_chat_schema::TYPE_INTEGER)) {
|
||||
json_value |= p.json_number();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_BOOLEAN)) {
|
||||
json_value |= p.json_bool();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_NULL)) {
|
||||
json_value |= p.json_null();
|
||||
}
|
||||
arg_value = p.gbnf(p.atomic(p.tool_arg_json_value(json_value) + arg_close) | arg_string, "xml-arg-string");
|
||||
}
|
||||
|
||||
auto arg_rule = p.rule(rule_name, p.tool_arg(arg_open + arg_value));
|
||||
|
||||
|
||||
@@ -846,6 +846,25 @@ static common_chat_tool nullable_int_tool{
|
||||
})",
|
||||
};
|
||||
|
||||
static common_chat_tool string_union_tool{
|
||||
/* .name = */ "set_union",
|
||||
/* .description = */ "Set values whose types are unions with string",
|
||||
/* .parameters = */ R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": ["string", "object"],
|
||||
"description": "A string or object value"
|
||||
},
|
||||
"amount": {
|
||||
"type": ["string", "integer"],
|
||||
"description": "A string or integer value"
|
||||
}
|
||||
},
|
||||
"required": ["value", "amount"]
|
||||
})",
|
||||
};
|
||||
|
||||
static common_chat_tool enum_no_type_tool{
|
||||
/* .name = */ "set_unit",
|
||||
/* .description = */ "Set a temperature unit",
|
||||
@@ -3805,6 +3824,46 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
|
||||
})
|
||||
.run();
|
||||
|
||||
// nullable string given null - parses as JSON null, not the string "null"
|
||||
tst.test(
|
||||
"<tool_call>\n"
|
||||
"<function=set_nullable_str>\n"
|
||||
"<parameter=name>\nnull\n</parameter>\n"
|
||||
"</function>\n"
|
||||
"</tool_call>")
|
||||
.tools({ nullable_string_tool })
|
||||
.expect_tool_calls({
|
||||
{ "set_nullable_str", R"({"name": null})", {} },
|
||||
})
|
||||
.run();
|
||||
|
||||
// unions with string - JSON values of the other types are typed, everything else is a string
|
||||
tst.test(
|
||||
"<tool_call>\n"
|
||||
"<function=set_union>\n"
|
||||
"<parameter=value>\n{\"a\": 1}\n</parameter>\n"
|
||||
"<parameter=amount>\n2 dollars\n</parameter>\n"
|
||||
"</function>\n"
|
||||
"</tool_call>")
|
||||
.tools({ string_union_tool })
|
||||
.expect_tool_calls({
|
||||
{ "set_union", R"({"value": {"a": 1}, "amount": "2 dollars"})", {} },
|
||||
})
|
||||
.run();
|
||||
|
||||
tst.test(
|
||||
"<tool_call>\n"
|
||||
"<function=set_union>\n"
|
||||
"<parameter=value>\n{not valid json\n</parameter>\n"
|
||||
"<parameter=amount>\n42\n</parameter>\n"
|
||||
"</function>\n"
|
||||
"</tool_call>")
|
||||
.tools({ string_union_tool })
|
||||
.expect_tool_calls({
|
||||
{ "set_union", R"({"value": "{not valid json", "amount": 42})", {} },
|
||||
})
|
||||
.run();
|
||||
|
||||
// enum without explicit type key - should infer string from enum values
|
||||
tst.test(
|
||||
"<tool_call>\n"
|
||||
|
||||
Reference in New Issue
Block a user