Repository navigation
Parse each Seed-OSS parallel call in its own <seed:tool_call> block (#1540) - #1542
Merged
Merged
Conversation
…1540) With parallel tool calls, Seed-OSS replies put each call in its own <seed:tool_call> scope. The parser read calls inside one scope only, so a second scope failed the whole parse and returned no calls; llama.cpp 620d2225 returns one call per scope. The grammar wrapped every call in one scope with no whitespace between tags, and its lazy trigger <seed:tool_call><function= never fired on the line-broken form the template shows and llama.cpp parses. Seed-OSS now repeats scopes as Qwen3-Coder XML does since #1530: the parser continues into whitespace-separated scopes, and the grammar admits one call per scope, repeated only with parallel tool calls. It starts on "<seed:tool_call>\n", llama.cpp's trigger. The Qwen3-Coder parallel upstream test now reads both fixtures, and its fixture records llama.cpp's tool calls as the Seed-OSS one does.
Owner
Author
|
Coordinator decision: accept the 352 Seed oracle rows (592 with the auditor's extra outputs) as documented leniency. All are |
Contributor
|
Chat app preview removed for |
Merged
13 tasks done
This was referenced Nov 9, 2026
Closed
Owner
Author
|
Maintainer decisions for 0.19.4 (#1874), checked against llama.cpp
|
leehack
added a commit
that referenced
this pull request
Nov 10, 2026
14 tasks done
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.
Summary
Closes #1540. With parallel tool calls, Seed-OSS replies put each call in its own
<seed:tool_call>block. llamadart now returns one call per block, and its grammar lets the model emit those blocks, as llama.cpp620d2225does. Same failure class as #1530, fixed for Qwen3-Coder XML in #1539; this extends that mechanism to Seed-OSS.Root cause. Two defects, both pre-existing:
parseXmlToolCallsread calls only inside the first scope.XmlToolCallFormat.seedOssdid not setrepeatsScope, so after the first</function>the parser found the next<function=behind</seed:tool_call>\n<seed:tool_call>\n, treated that as a malformed prelude and failed the whole parse: 0 calls, everything as content.root ::= "<seed:tool_call>" tool-call+ "</seed:tool_call>", with no whitespace between tags. It rejects the line-broken form the template shows and llama.cpp parses, allows only one scope, and ignoredparallelToolCalls. Its lazy trigger<seed:tool_call><function=never fired on that line-broken form.Change.
seedOsssetsrepeatsScope, so it gets Parse each Qwen3-Coder XML parallel call in its own <tool_call> block (#1530) #1539's parser path: a whitespace-separated scope holding a valid call continues the calls, text between scopes ends them, and a later scope without a valid call stays content after the earlier calls.buildXmlToolCallGrammaruses the scoped grammar for everyrepeatsScopeformat, not onlyqwen3Coder:root ::= tool-call-scope+, one call per scope, ortool-call-scopewith parallel tool calls off, as llama.cpp's Seed-OSS grammar does. The shared value rule is renamedqwen3-coder-value→xml-value; the Qwen grammar is otherwise unchanged.SeedOssHandlerpasses the request's parallel flag to the grammar and triggers on"<seed:tool_call>\n", llama.cpp's trigger.Production-readiness scope
<seed:tool_call>blocks, and parse and stream return each one. With parallel tool calls off, the grammar admits one call.<seed:tool_call><function=without a line break; llama.cpp does not start on it either, and the parser still reads that form.<seed:tool_call><function=scopes without the line break now parse as calls, as one such scope already did, where llama.cpp keeps any such scope as content (see Matrix Evidence); text after the last call stays content (llama.cpp rejects the reply); an unclosed last scope stays content (llama.cpp returns its partial call); a</seed:think>without its opening tag parses as reasoning (llama.cpp keeps it as content); with thinking off, a streamed thought is not reported as reasoning; the required-tool-choice grammar does not admit a thought or text before the call, as llama.cpp's does. Not filed yet.Completeness checklist
template-engine-internals.mddoes not describe this behavior.)PR type guidance
Bugfix PR: Root cause and fix are above. Regression tests:
test/integration/core/engine/xml_parallel_tool_calls_upstream_test.dart(renamed fromqwen3_coder_parallel_upstream_test.dart) now reads both upstream fixtures. Newtest/fixtures/seed_oss_parallel_upstream.json: 40 upstream cases on the newly vendoredByteDance-Seed-OSS.jinja(pinned refb10549, identical at620d2225), thinking on and off, 2 and 3 calls separated by nothing,\n,\n\n, a space or a tab, a trailing newline, and text and thought prefixes. Each case checks parse plus whole, byte-by-byte and 3-character streams against llama.cpp's content, reasoning and tool calls. The Qwen fixture keeps its 160 cases and now also records llama.cpp's tool calls (regenerated from the same probe; content and reasoning unchanged).xml_tool_call_format_test.dart): the consecutive-scope group runs for Qwen3-Coder and Seed-OSS: separators, with and without schemas, forced-open and explicit thoughts, text between scopes, invalid, junk and unclosed later scopes, and an unclosed first scope. The grammar test checks both roots with parallel on and off.seed_oss_handler_test.dart): grammar root per parallel flag, the trigger, and a three-scope parse after a thought.test-gbnf-validatoracceptance and rejection with parallel on and off now includes Seed-OSS (specialized_tool_grammar_validation_e2e_test.dart).On base, 53 of the new or changed unit and integration tests and the grammar validator test fail (one of them only for the
xml-valuerename).Test Plan
dart run tool/prepare_workspace.dartdart format --output=none --set-exit-if-changed .dart analyze lib test tool, plusdart run tool/testing/check_platform_boundaries.dartdart test -p vm -j 1 --exclude-tags local-only(isolatedTMPDIR): 9464 passed, 123 skipped atfede2047edart test -p chrome --exclude-tags local-only test/unit/core/template test/unit/core/engine/chat_completion_stream_parser_test.dart: 1115 passed./tool/testing/run_template_parity_suites.sh, withLLAMA_CPP_GBNF_VALIDATORset to a llama.cpp620d2225(v0.7.0, the runtime pin) validator: detection 74, diagnostic 93, unit template 1609, upstream parser 10, GBNF acceptance 58, all passed atfede2047eMatrix Evidence
root-vmfede2047eroot-chrometemplate-parityb10549templates,620d2225validatorfede2047estructured-output-adversarialcommon_chat_sessionprobe at620d2225<think>tags): 3,664 → 4,384 matching. 352 records match on base only because base rejects the whole reply: all are consecutive<seed:tool_call><function=scopes without the line break, which llama.cpp keeps as content even for one scope, while llamadart on base already parses one such scope as a call (and the old required grammar emitted that form). Head extends that to several scopes; no other record gets worse. Full corpus (94 templates, 183,672 records): identical results to base, 0 transitions. Mutation: 11 parser, grammar, handler and trigger mutants all killed.high-risk-exact-head-independent-qa/opt/UnitySrc/personal/llama/llamadart-evidence/pr-1542/audit/evidence-1542-fede204.jsonReview Notes
High-risk regression review
repeatsScope, keep the scoped grammar Qwen-only, hard-code or drop the handler's parallel flag, restore the old trigger, make the root always or never repeat, allow several calls per scope, drop whitespace after the scope start, or remove the rollback each fail the new tests.620d2225emissions are in the durable fixture.