Skip to content

chat : accept packed XML tool calls in the Qwen3-Coder parser - #231

Open
danielhanchen wants to merge 2 commits into
masterfrom
mimo-packed-xml-tool-calls
Open

danielhanchen wants to merge 2 commits into
masterfrom
mimo-packed-xml-tool-calls

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Problem

MiMo-V2.6-Distill-Qwen-9B uses the Qwen3-Coder XML tool-call format and is routed to common_chat_params_init_qwen3_coder, but it emits the tags packed, with no newlines:

<tool_call><function=get_weather><parameter=city>Paris</parameter></function></tool_call>

The parser hardcoded "\n</parameter>\n", ">\n", "</function>\n" and "<tool_call>\n". On packed output, the first string argument scanned forward for "\n</parameter>\n" and consumed every later tag, so the grammar never accepted. The server returned the call as raw content with tool_calls: null, and generation ran until max_tokens. This matches the reports in the XiaomiMiMo/MiMo-V2.6-Distill-Qwen-9B discussions (#6, #7).

Change

common/chat.cpp, Qwen3-Coder tool-call parser only:

  • The newline after <tool_call>, <function=...> and <parameter=...> is now optional (a single \n, so any further leading whitespace stays part of the value).
  • String arguments end at "\n</parameter>" or "</parameter>", scanned longest first. Output from models that emit the newlines parses exactly as before, and the trailing \n stays out of the value.
  • Whitespace after </parameter> and </function> is absorbed with p.space() rather than a required \n.
  • The optional leading <tool_call> for Qwen3-Coder uses the same relaxed opener.

The generated grammar accepts both the newline and packed forms, so constrained sampling does not force either one.

Tests

tests/test-chat.cpp, three new parse cases next to the existing Qwen3-Coder ones:

  • fully packed call
  • half-packed call (newline after the opening tags, none after </parameter>)
  • packed parallel calls across two functions, one with an optional parameter

test-chat passes on this branch. With only the parser change reverted, the new cases fail.

End to end on llama-server with the same change on upstream master, MiMo-V2.6-Distill-Qwen-9B BF16 with sampling: 4/4 tool calls parsed into tool_calls, 4/4 follow-up turns stopped cleanly, and no runaway generations. Without the change, every call came back as raw content and ran to the token cap.

MiMo-V2.6-Distill-Qwen-9B uses the Qwen3-Coder XML tool-call format but
emits it with no newlines between tags:

  <tool_call><function=f><parameter=k>v</parameter></function></tool_call>

The parser hardcoded "\n</parameter>\n", ">\n", "</function>\n" and
"<tool_call>\n", so the first argument of a packed call consumed every
later tag, the grammar never accepted, and generation ran to max_tokens
with tool_calls empty.

Make the newlines around the tags optional. Argument values are scanned
for "\n</parameter>" before "</parameter>", so output from models that
emit the newlines parses exactly as before.

Add parse tests for the packed, half-packed, and packed parallel forms.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-22T23:05:54.097065Z 4361082 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

A string value now ends at </parameter> only when the next tag follows,
so a literal </parameter> inside the value is kept, as it was before the
packed-format change. The closing tag is confirmed with a lookahead so a
partial stream does not close the value early.

The grammar accepts the original newline form or the packed form, so
constrained sampling for newline-emitting models is unchanged.

Shorten comments and add a test for a literal </parameter> in a value.

This branch has not been deployed

No deployments
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