Conversation
Tool-calling worked only for the JSON-`<tool_call>` dialect (Qwen3). Two other families failed silently: - Phi: its chat template reads tools from a system message's `tools` key, not the top-level `tools` variable, and it emits `<|tool_call|>` markers. So the model was never told about the tools and, even if it emitted a call, the detector did not recognize the markers. - Qwen3-Coder: shares Qwen3's `<tool_call>` markers (so it is detected) but emits an XML `<function=NAME><parameter=K>V</parameter></function>` body, which the JSON-only parser dropped, leaving finish_reason=stop with no tool call. Fixes: expose the tools JSON on the system message so `message['tools']` templates render them (top-level `tools` models are unaffected); probe the `<|tool_call|>` marker family; add an XML `.xmlFunction` parse format, with the JSON path falling back to it when the body is `<function=…>` (Qwen3-Coder is detected as JSON via the shared markers). Adds 5 parser/detection tests incl. a Qwen3 JSON regression guard. Found via the pi coding-agent campaign: Qwen3-Coder-30B scored 0/3 not from a capability gap but because the server dropped its tool calls.
… scoped tool injection
- Coerce XML-function parameters to bool/Int/Double/array/object so JSON-schema
types survive (was stringifying, e.g. {"count":"5"} instead of {"count":5}).
- Route <function=…> bodies through the .xmlFunction switch branch via
effectiveToolCallFormat; drop the substring fallback in parseJSONToolCalls.
- Scope system-message tool injection to the Phi dialect (toolsInSystemMessage)
so top-level-tools families (Qwen3) are not perturbed by a synthetic system
message. Extract applyToolsToSystemMessage helper and cover it in tests.
Extract toolsJSONForSystemMessage next to applyToolsToSystemMessage and call it from both ChatHandler and CoreAILanguageModel.makeTokens, gated on toolCallDetection.toolsInSystemMessage. Qwen3-style dialects keep top-level tools.
stikves
marked this pull request as ready for review
September 22, 2026 19:30
stikves
requested review from
alejandro-isaza,
carinapeng,
kevchengcodes,
srjoglekar246 and
tjia1818
September 22, 2026 19:31
…en Phi assertion Drop the bespoke DialectTokenizer/CapturingTokenizer structs in ToolCallDialectTests in favor of the shared TestUtilities versions. CapturingTokenizer moves to TestUtilities, wrapping MockTokenizer and delegating everything except the captured applyChatTemplate overload. Also tighten fmInjectsToolsForPhi to assert the injected system tools JSON equals toolsJSONForSystemMessage's output exactly, instead of a substring contains check.
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.
Tool-calling covers the Phi and Qwen3-Coder dialects across both the server (
llm-server) and the FoundationModels (CoreAILanguageModel) paths.Dialects
toolskey, not the top-leveltoolsvariable, and emits<|tool_call|>markers.<tool_call>markers (detected as JSON) but emits an XML<function=NAME><parameter=K>V</parameter></function>body.Changes
ToolCallParser.swift):detectToolCallFormatprobes the<|tool_call|>/<|/tool_call|>family;ToolCallParseradds an XML.xmlFunctionformat for<function=…>bodies, and the JSON path routes to it viaeffectiveToolCallFormatwhen the body is<function=…>. Both the server and the FM path build their parser from this.toolsJSONForSystemMessageserializes the tool specs (sorted keys) andapplyToolsToSystemMessagefolds them into the system message, gated onToolCallDetection.toolsInSystemMessage. The server'sChatHandlerand the FM path'smakeTokensboth call these; top-leveltoolsis still passed for Qwen3-style models.Tests
<|tool_call|>detection, XML-function parse (explicit + JSON→XML fallback),<tool_call>JSON regression guard,toolsInSystemMessagegating.toolsJSONForSystemMessagesorted-keys serialization.makeTokenswith a capturing tokenizer: Phi injects into the system message, Qwen3 keeps tools top-level.On-device Phi validation of the rendered template output is still pending.