Skip to content

feat(server): parse <function_calls> XML tool emissions - #627

Open
dpavlin wants to merge 4 commits into
Luce-Org:mainfrom
dpavlin:feat/parse-function-calls-minimal
Open

feat(server): parse <function_calls> XML tool emissions#627
dpavlin wants to merge 4 commits into
Luce-Org:mainfrom
dpavlin:feat/parse-function-calls-minimal

Conversation

@dpavlin

@dpavlin dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

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

  1. Tool Parser (tool_parser.cpp):
    • Registers <function_calls> opener in find_tool_syntax_start and holdback calculation.
    • Adds Pattern 4d to parse <function_calls> containers, extracting XML <param name="...">...</param> and inline JSON bodies.
    • Coerces string parameters according to JSON Schema property definitions.
  2. Streaming Emitter (sse_emitter.cpp):
    • Intercepts <function_calls> occurring in StreamMode::REASONING to buffer tool calls without leaking into reasoning stream.
    • In emit_finish, splits parsed.cleaned_text across </think> to maintain clean separation between reasoning and content deltas.
  3. Unit Tests (test_server_unit.cpp):
    • Adds unit tests covering XML parameter extraction, inline JSON bodies, and thinking-mode streaming.

Verification

  • 364 / 364 unit tests pass (ctest -R test_server_unit).
  • Verified live streaming inference with DeepSeek-V4-Flash-ROCMFP2-STRIX on ROCm hardware.

Review in cubic

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.
@dpavlin

dpavlin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@dpavlin I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread server/src/server/sse_emitter.cpp
Comment thread server/src/server/sse_emitter.cpp
Comment thread server/src/server/tool_parser.cpp
args[k] = convert_param_value(v, k, find_tool_properties(tools, fn_name));
}
}
block_calls.push_back({fn_name, std::move(args)});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread server/src/server/sse_emitter.cpp
if (!tool_allowed(tools, fn_name)) continue;
std::string body = trim_ws((*it)[2].str());
json args = json::object();
if (!body.empty() && body.front() == '{') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread server/src/server/sse_emitter.cpp
Comment thread server/src/server/tool_parser.cpp
…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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread server/src/server/sse_emitter.cpp Outdated
Record first_content_token_index when </think> is observed in TOOL_BUFFER
to ensure accurate content_tokens counts when content follows a tool call.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread server/src/server/sse_emitter.cpp Outdated
Comment thread server/src/server/sse_emitter.cpp Outdated
Comment thread server/src/server/sse_emitter.cpp Outdated
…_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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant