Skip to content

Fail closed on schema-invalid Qwen tool calls - #1162

Open
Baiju Meswani (baijumeswani) wants to merge 1 commit into
mainfrom
baijumeswani/qwen-tool-schema-fail-closed
Open

Baiju Meswani (baijumeswani) wants to merge 1 commit into
mainfrom
baijumeswani/qwen-tool-schema-fail-closed

Conversation

@baijumeswani

Copy link
Copy Markdown
Collaborator

Why

In tool_choice=auto, a Qwen model can produce a recognizable XML tool call that does not match the tool schema supplied by the client. In a Copilot BYOK reproduction, Copilot advertised grep(paths=...), but the model emitted grep(path=...):

<tool_call>
<function=grep>
<parameter=pattern>
json
</parameter>
<parameter=path>
src
</parameter>
</function>
</tool_call>

path is not declared by Copilot's grep tool. Previously, a rejected tool-call batch could be surfaced as assistant text, exposing the XML to Copilot instead of producing a valid structured call or a clear failure.

Scenarios this fixes

  1. Invalid argument alongside a valid tool call. The model emits grep(path="src") followed by a correctly formed powershell call in the same batch. Previously, the batch could appear as raw XML in assistant text. Now neither call is published, including the valid sibling; Foundry attempts bounded guided recovery if eligible or returns an explicit error. No invalid arguments are returned for execution.
  2. Missing or mistyped arguments. The model omits a required parameter or supplies 1.5 where a tool requires an integer. These recognizable but schema-invalid calls are withheld instead of being presented as assistant text or treated as executable calls.
  3. A mixed tool list. Copilot supplies a supported powershell tool alongside a tool whose nested schema the Qwen decoder cannot validate. Previously, the unsupported declaration could disable decoding for the entire list. Now supported calls still become structured calls, while an attempted call to the unsupported tool is withheld. If the unsupported call is adjacent to a valid call, the whole batch is rejected.
  4. Unsupported-only schemas. A declared tool has an unsupported array-root schema or allows arbitrary properties via additionalProperties: true. The decoder cannot safely validate such a call, so it withholds the attempted call instead of returning it for execution or exposing its XML as assistant text. It does not incorrectly classify a permitted dynamic argument as an undeclared property.
  5. Token-by-token streaming. An invalid call arrives in many small chunks, including the grep(path=...) example above. Neither fragments of that tool-call batch nor an adjacent valid call are streamed before batch validation finishes. Valid grep(paths=...) calls and ordinary prose still work.

What changed

  • Treat recognized schema-invalid Qwen XML calls as failed tool attempts, not visible assistant text. Validate the whole adjacent batch before publishing any of its calls.
  • Keep the decoder active when a request mixes supported tools with recognizable but unsupported schemas. Calls to unsupported schemas, including open-ended properties and unsupported root types, are withheld rather than assumed valid.
  • Reuse the existing single guided-recovery attempt when the Engine turn is eligible. Otherwise, report an explicit invalid-tool-call error. Do not rewrite or execute the model's invalid arguments.
  • Preserve normal text and valid tool calls, including correctly formed grep(paths=...) calls.

When guided recovery is eligible and produces a valid call, only that recovered call is returned; otherwise the request fails explicitly.

Validation

  • The focused native suite passed 146 tests, covering mixed Copilot tools, invalid parameters, unsupported schemas, adjacent-call atomicity, streaming, and guided recovery.
  • An offline Copilot BYOK coding task completed without visible tool-call XML and passed its six fixture tests.

Scope and remaining limitation

This change makes Foundry fail safely; it does not make the model reliably choose paths instead of path. A later reproduction of an issue-analysis prompt still generated grep(path=...) and failed explicitly. Text or reasoning already streamed before the invalid call cannot be retracted, so Copilot may display that prefix again when it retries the whole request. Improving model/schema adherence and retry UX are separate follow-ups.

Keep recognized unsupported tool schemas on the decoding path without admitting invalid calls. Withhold schema-invalid XML batches, reuse bounded guidance retry when eligible, and return an explicit error otherwise. Cover mixed Copilot tools, root schemas, and streaming behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 21:45
@vercel

vercel Bot commented Sep 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
foundry-local Ready Ready Preview Sep 30, 2026 9:45pm UTC

Request Review

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Boundary-marker and oversized-payload paths can still expose rejected XML or publish an adjacent call.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Makes Qwen XML tool-call handling fail closed when generated arguments violate declared schemas.

Changes:

  • Distinguishes schema violations from ordinary rejected text.
  • Supports mixed valid and unsupported schemas with bounded guided recovery.
  • Expands streaming, schema-validation, and recovery tests.
File Description
tool_call_stream_accumulator_test.cc Adds schema and streaming regression coverage.
chat_session_test.cc Tests recovery and explicit failure behavior.
tool_call_payload_parser.h Clarifies malformed-call semantics.
qwen_xml_tool_call_decoder.h Documents fail-closed decoder behavior.
qwen_xml_tool_call_decoder.cc Implements schema-violation detection and mixed-schema parsing.
chat_session.cc Reports explicit errors when recovery is unavailable.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +583 to +584
if (!schema_it->second.valid) {
return BlockResult(ParseState::kSchemaViolation);
Comment on lines +20 to +21
/// Unsupported but recognizable schemas remain ineligible for admission, while attempted calls cannot become text.
/// Calls that violate a declared schema are withheld from visible text and signal recovery or an error.

This branch was successfully deployed

1 active deployment
Preview — 11ce35a8 Deployed Sep 30, 2026 by vercel[bot]
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.

2 participants