fix(server): accept malformed Qwen tool-call variants#552
Conversation
4f7ecb4 to
66c5afa
Compare
|
Discovered a few more issues with the parser this is over 100-200M tokens generated in the last few days. 2 of the issues has happened more than 2x. |
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="server/src/server/tool_parser.cpp">
<violation number="1" location="server/src/server/tool_parser.cpp:852">
P1: Mixed compatibility variants are returned grouped by parser pattern rather than response order, so dependent calls can execute in the wrong sequence. Preserve source-position ordering before returning `result.tool_calls`.</violation>
<violation number="2" location="server/src/server/tool_parser.cpp:892">
P2: Wrapped attribute-style calls still leak `<tool_call>`/`</tool_call>` as assistant content. Extend this removal span over an adjacent wrapper, matching the handling in pattern 3d/3e.</violation>
</file>
<file name="server/test/test_server_unit.cpp">
<violation number="1" location="server/test/test_server_unit.cpp:1227">
P3: Add an `accumulated_text()` equality assertion to `test_emitter_funcname_tool_buffer_detection` matching the pattern in the sibling emitter tests, verifying that the `\n\n` preamble before `<funcname>` is preserved as assistant content rather than silently stripped or leaked.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // tool and the entire body consists of fully closed parameter elements. | ||
| // This strict body check prevents partial or guessed arguments. | ||
| if (tools.is_array() && !tools.empty()) { | ||
| auto begin = std::sregex_iterator(text.begin(), text.end(), re_attribute_tool_xml()); |
There was a problem hiding this comment.
P1: Mixed compatibility variants are returned grouped by parser pattern rather than response order, so dependent calls can execute in the wrong sequence. Preserve source-position ordering before returning result.tool_calls.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/server/tool_parser.cpp, line 852:
<comment>Mixed compatibility variants are returned grouped by parser pattern rather than response order, so dependent calls can execute in the wrong sequence. Preserve source-position ordering before returning `result.tool_calls`.</comment>
<file context>
@@ -781,6 +843,113 @@ ToolParseResult parse_tool_calls(const std::string & text, const json & tools) {
+ // tool and the entire body consists of fully closed parameter elements.
+ // This strict body check prevents partial or guessed arguments.
+ if (tools.is_array() && !tools.empty()) {
+ auto begin = std::sregex_iterator(text.begin(), text.end(), re_attribute_tool_xml());
+ auto end = std::sregex_iterator();
+ for (auto it = begin; it != end; ++it) {
</file context>
| if (!trim_ws(body.substr(cursor)).empty()) valid = false; | ||
| if (!valid || !found_param) continue; | ||
|
|
||
| add_call(fn_name, args, pos, pos + it->length()); |
There was a problem hiding this comment.
P2: Wrapped attribute-style calls still leak <tool_call>/</tool_call> as assistant content. Extend this removal span over an adjacent wrapper, matching the handling in pattern 3d/3e.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/server/tool_parser.cpp, line 892:
<comment>Wrapped attribute-style calls still leak `<tool_call>`/`</tool_call>` as assistant content. Extend this removal span over an adjacent wrapper, matching the handling in pattern 3d/3e.</comment>
<file context>
@@ -781,6 +843,113 @@ ToolParseResult parse_tool_calls(const std::string & text, const json & tools) {
+ if (!trim_ws(body.substr(cursor)).empty()) valid = false;
+ if (!valid || !found_param) continue;
+
+ add_call(fn_name, args, pos, pos + it->length());
+ }
+ }
</file context>
| static void test_emitter_funcname_tool_buffer_detection() { | ||
| auto em = make_emitter(ApiFormat::OPENAI_CHAT, read_tools()); | ||
| em.emit_start(); | ||
| em.emit_token("\n\n<func"); |
There was a problem hiding this comment.
P3: Add an accumulated_text() equality assertion to test_emitter_funcname_tool_buffer_detection matching the pattern in the sibling emitter tests, verifying that the \n\n preamble before <funcname> is preserved as assistant content rather than silently stripped or leaked.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/test/test_server_unit.cpp, line 1227:
<comment>Add an `accumulated_text()` equality assertion to `test_emitter_funcname_tool_buffer_detection` matching the pattern in the sibling emitter tests, verifying that the `\n\n` preamble before `<funcname>` is preserved as assistant content rather than silently stripped or leaked.</comment>
<file context>
@@ -1048,6 +1196,78 @@ static void test_emitter_bare_function_tool_buffer_detection() {
+static void test_emitter_funcname_tool_buffer_detection() {
+ auto em = make_emitter(ApiFormat::OPENAI_CHAT, read_tools());
+ em.emit_start();
+ em.emit_token("\n\n<func");
+ em.emit_token("name>read\n<parameter=limit>\n50\n</parameter>\n"
+ "<parameter=offset>\n1\n</parameter>\n");
</file context>
Some Qwen agent responses contain recognizable tool calls but vary from the
documented
<function=NAME>XML form. The server previously streamed thesevariants as visible assistant text instead of returning structured
tool_calls.This change adds strict compatibility for three observed forms:
The streaming detector now holds each opener until parsing completes, so tool
syntax does not leak into assistant content.
Safety
syntax are rejected rather than partially parsed or guessed.
Reproduction
The newest failure was a two-call response emitted as visible text:
After this change, the preamble remains assistant content and the two blocks
become ordered
readandbashtool calls.