feat(server): parse <function_calls> XML tool emissions - #627
Conversation
Add parser pattern 4d for <function_calls><invoke name="...">...</invoke></function_calls> emissions from DeepSeek models. Handles XML <param name="...">...</param> and inline JSON bodies, captures tool calls occurring in both content and reasoning streams, coerces parameters according to JSON Schema property definitions, and includes unit tests.
|
@cubic-dev-ai review |
@dpavlin I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
2 issues found across 3 files
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:1203">
P2: A malformed/single-source JSON body inside <invoke> is silently ignored (no call, no error), so in a multi-invoke <function_calls> block one bad JSON invoke is dropped while valid siblings still execute; an empty '{}' body also produces a zero-argument call. Parse defensively and collect per-invoke errors or skip only the malformed invoke while reporting the rest.</violation>
<violation number="2" location="server/src/server/tool_parser.cpp:1223">
P2: When an invoke body contains a truncated parameter or extra non-parameter text, Pattern 4d still emits a tool call with partial arguments and removes the entire block. Validate that parameter matches cover the complete body and reject duplicate keys before queuing the call, as the existing strict XML parser does.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| args[k] = convert_param_value(v, k, find_tool_properties(tools, fn_name)); | ||
| } | ||
| } | ||
| block_calls.push_back({fn_name, std::move(args)}); |
There was a problem hiding this comment.
P2: When an invoke body contains a truncated parameter or extra non-parameter text, Pattern 4d still emits a tool call with partial arguments and removes the entire block. Validate that parameter matches cover the complete body and reject duplicate keys before queuing the call, as the existing strict XML parser does.
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 1223:
<comment>When an invoke body contains a truncated parameter or extra non-parameter text, Pattern 4d still emits a tool call with partial arguments and removes the entire block. Validate that parameter matches cover the complete body and reject duplicate keys before queuing the call, as the existing strict XML parser does.</comment>
<file context>
@@ -1174,6 +1177,59 @@ ToolParseResult parse_tool_calls(const std::string & text, const json & tools) {
+ args[k] = convert_param_value(v, k, find_tool_properties(tools, fn_name));
+ }
+ }
+ block_calls.push_back({fn_name, std::move(args)});
+ }
+
</file context>
| if (!tool_allowed(tools, fn_name)) continue; | ||
| std::string body = trim_ws((*it)[2].str()); | ||
| json args = json::object(); | ||
| if (!body.empty() && body.front() == '{') { |
There was a problem hiding this comment.
P2: A malformed/single-source JSON body inside is silently ignored (no call, no error), so in a multi-invoke <function_calls> block one bad JSON invoke is dropped while valid siblings still execute; an empty '{}' body also produces a zero-argument call. Parse defensively and collect per-invoke errors or skip only the malformed invoke while reporting the rest.
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 1203:
<comment>A malformed/single-source JSON body inside <invoke> is silently ignored (no call, no error), so in a multi-invoke <function_calls> block one bad JSON invoke is dropped while valid siblings still execute; an empty '{}' body also produces a zero-argument call. Parse defensively and collect per-invoke errors or skip only the malformed invoke while reporting the rest.</comment>
<file context>
@@ -1174,6 +1177,59 @@ ToolParseResult parse_tool_calls(const std::string & text, const json & tools) {
+ if (!tool_allowed(tools, fn_name)) continue;
+ std::string body = trim_ws((*it)[2].str());
+ json args = json::object();
+ if (!body.empty() && body.front() == '{') {
+ json raw_args = json::parse(body, nullptr, false);
+ if (raw_args.is_discarded() || !raw_args.is_object()) continue;
</file context>
There was a problem hiding this comment.
2 existing issues remain and no new issues found across 3 files
Requires human review: Auto-approval blocked by 6 unresolved issues from previous reviews.
Re-trigger cubic
…parsing Address review feedback: - Restrict reasoning mode tool interception to <function_calls> - Record content boundary accounting when </think> is in buffered tool text - Emit Anthropic thinking_delta for reasoning buffered around tool calls - Support Anthropic input_schema in find_tool_properties - Preserve literal "null" strings when property schema specifies string type - Strictly validate XML parameter body completeness in Pattern 4d - Add corresponding unit tests
There was a problem hiding this comment.
1 existing issue remains and no new issues found across 4 files (changes from recent commits).
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Re-trigger cubic
Record first_content_token_index when </think> is observed in TOOL_BUFFER to ensure accurate content_tokens counts when content follows a tool call.
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…_content_token_index Search for </think> only outside the completed </function_calls> element to avoid false closes on parameters containing literal </think> text, and align first_content_token_index with the first real content token.
Summary
Adds support for parsing
<function_calls><invoke name="...">...</invoke></function_calls>tool call containers emitted by DeepSeek models during multi-tool and file-reading operations.Changes
tool_parser.cpp):<function_calls>opener infind_tool_syntax_startand holdback calculation.<function_calls>containers, extracting XML<param name="...">...</param>and inline JSON bodies.sse_emitter.cpp):<function_calls>occurring inStreamMode::REASONINGto buffer tool calls without leaking into reasoning stream.emit_finish, splitsparsed.cleaned_textacross</think>to maintain clean separation between reasoning and content deltas.test_server_unit.cpp):Verification
ctest -R test_server_unit).DeepSeek-V4-Flash-ROCMFP2-STRIXon ROCm hardware.