From ea3e8e30e1588a381d49cc3e8ed0ef2e0cd210e5 Mon Sep 17 00:00:00 2001 From: Yap Sok Ann Date: Tue, 3 Mar 2026 21:39:16 +0700 Subject: [PATCH] Allow arbitrary arguments order for Q3C, Q3CN, and Qwen3.5 (#1352) This should fix the read file at offset/limit issue, where the tool definition has offset before limit, while the model sets limit before offset. --- common/chat-parser-xml-toolcall.cpp | 31 ++++++++++++++++++++--------- common/chat-parser-xml-toolcall.h | 3 +++ common/chat.cpp | 23 +++++++++++---------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/common/chat-parser-xml-toolcall.cpp b/common/chat-parser-xml-toolcall.cpp index 8848c7e4..16fe2661 100644 --- a/common/chat-parser-xml-toolcall.cpp +++ b/common/chat-parser-xml-toolcall.cpp @@ -245,15 +245,28 @@ void build_grammar_xml_tool_call(common_chat_params & data, const json & tools, auto next_arg_with_sep = builder.add_rule(name + "-last-arg-end", form.last_val_end ? gbnf_format_literal(*form.last_val_end) : gbnf_format_literal(form.val_end)); decltype(next_arg_with_sep) next_arg = "\"\""; - for (auto i = arg_rules.size() - 1; /* i >= 0 && */ i < arg_rules.size(); --i) { - std::string include_this_arg = arg_rules[i].symbol_name + " " + next_arg_with_sep; - next_arg = builder.add_rule(name + "-arg-after-" + std::to_string(i), arg_rules[i].is_required ? - include_this_arg : "( " + include_this_arg + " ) | " + next_arg - ); - include_this_arg = gbnf_format_literal(form.val_end) + " " + include_this_arg; - next_arg_with_sep = builder.add_rule(name + "-arg-after-" + std::to_string(i) + "-with-sep", arg_rules[i].is_required ? - include_this_arg : "( " + include_this_arg + " ) | " + next_arg_with_sep - ); + if (form.relax_arg) { + if (!arg_rules.empty()) { + std::vector arg_symbols; + arg_symbols.reserve(arg_rules.size()); + for (const auto & rule : arg_rules) { + arg_symbols.push_back(rule.symbol_name); + } + auto any_arg = builder.add_rule(name + "-any-arg", string_join(arg_symbols, " | ")); + auto any_arg_with_end = builder.add_rule(name + "-any-arg-with-end", any_arg + " " + next_arg_with_sep); + next_arg = builder.add_rule(name + "-args-relaxed", "( " + any_arg_with_end + " )*"); + } + } else { + for (auto i = arg_rules.size() - 1; /* i >= 0 && */ i < arg_rules.size(); --i) { + std::string include_this_arg = arg_rules[i].symbol_name + " " + next_arg_with_sep; + next_arg = builder.add_rule(name + "-arg-after-" + std::to_string(i), arg_rules[i].is_required ? + include_this_arg : "( " + include_this_arg + " ) | " + next_arg + ); + include_this_arg = gbnf_format_literal(form.val_end) + " " + include_this_arg; + next_arg_with_sep = builder.add_rule(name + "-arg-after-" + std::to_string(i) + "-with-sep", arg_rules[i].is_required ? + include_this_arg : "( " + include_this_arg + " ) | " + next_arg_with_sep + ); + } } std::string quoted_name = name; diff --git a/common/chat-parser-xml-toolcall.h b/common/chat-parser-xml-toolcall.h index b309fb66..fed28d86 100644 --- a/common/chat-parser-xml-toolcall.h +++ b/common/chat-parser-xml-toolcall.h @@ -32,6 +32,9 @@ struct xml_tool_call_format { std::optional last_tool_end = std::nullopt; bool trim_raw_argval = false; bool allow_toolcall_in_think = false; + // Set true to allows function arguments in arbitrary order and without + // enforcing required field. + bool relax_arg = false; }; // make a GBNF that accept any strings except those containing any of the forbidden strings. diff --git a/common/chat.cpp b/common/chat.cpp index 2248c474..c272d741 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -1250,16 +1250,19 @@ static common_chat_params common_chat_params_init_qwen3_coder_xml(const common_c } // build grammar for tool call - static const xml_tool_call_format form { - /* form.scope_start = */ "", - /* form.tool_start = */ "\n\n\n", - /* form.key_start = */ "\n", - /* form.val_end = */ "\n\n", - /* form.tool_end = */ "\n", - /* form.scope_end = */ "", - }; + static const xml_tool_call_format form = ([]() { + xml_tool_call_format form {}; + form.scope_start = ""; + form.tool_start = "\n\n