Fail closed on schema-invalid Qwen tool calls - #1162
Open
Baiju Meswani (baijumeswani) wants to merge 1 commit into
Open
Baiju Meswani (baijumeswani) wants to merge 1 commit into
Baiju Meswani (baijumeswani) wants to merge 1 commit into
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Copilot started reviewing on behalf of
Baiju Meswani (baijumeswani)
September 30, 2026 21:46
View session
Contributor
There was a problem hiding this comment.
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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 advertisedgrep(paths=...), but the model emittedgrep(path=...):pathis not declared by Copilot'sgreptool. 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
grep(path="src")followed by a correctly formedpowershellcall 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.1.5where 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.powershelltool 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.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.grep(path=...)example above. Neither fragments of that tool-call batch nor an adjacent valid call are streamed before batch validation finishes. Validgrep(paths=...)calls and ordinary prose still work.What changed
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
Scope and remaining limitation
This change makes Foundry fail safely; it does not make the model reliably choose
pathsinstead ofpath. A later reproduction of an issue-analysis prompt still generatedgrep(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.