Repository navigation
[#21014][fix] DeepSeek DSML tool parsers: match closing tags structurally - #21018
Draft
lucifer1004 wants to merge 1 commit into
Draft
lucifer1004 wants to merge 1 commit into
lucifer1004 wants to merge 1 commit into
Conversation
…ructurally A DSML parameter, invoke or calls closing tag only ends its element when the text after it is a valid continuation, so parameter values may contain the literal tags. The V3.2 parser derives every tag from class attributes so the V4 and V4.1 parsers inherit the fix. finish() also emits an invoke whose closing tag ends the stream, parse_partial reports only confirmed calls, and the non-streaming path keeps the end-of-sentence token inside parameter values. Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
lucifer1004
force-pushed
the
upstream/dsml-literal-close-tag
branch
from
October 27, 2026 03:36
9a77243 to
7d29ebe
Compare
This branch has not been 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.
Description
Fixes #21014.
The DeepSeek V3.2, V4 and V4.1 tool parsers end a parameter, an invoke or the whole calls block at the first matching DSML closing tag. A tool argument that contains such a tag as text (an agent writing notes about the tool-call format, or editing code that handles it) is cut there, and the rest of the call is dropped or leaks as content. Streaming and non-streaming parsing also fail in different ways.
A closing tag now ends its element only when the text after it, ignoring whitespace, is a valid continuation:
<|end▁of▁sentence|>, a new calls block, or the end of the output<|end▁of▁sentence|>, a new calls block, or the end of the outputIf no parameter closing tag is confirmed, the first one is used. If no invoke closing tag is confirmed at the end of the output, the last one is used. In the normal case without literal tags, the result is the same as before.
The fix is in
DeepSeekV32Parser. Every tag is now derived from class attributes (_CALLS_TAG,_INVOKE_TAG,_PARAMETER_TAG), soDeepSeekV4ParserandDeepSeekV41Parserset only their tag names and inherit everything else.Other behavior changes that come with the structural matching:
finish()now emits the call. Outside a calls block,finish()still returns the held-back text as content.parse_partial(used on unfinished output in the Responses API) reports only calls whose closing tag is confirmed.<|end▁of▁sentence|>is removed only from normal text and from after the calls block. Before, the non-streaming path stripped it everywhere, including from parameter values, while streaming kept it.parameter_regexis removed, and_INVOKE_HEADER_PREFIX/_INVOKE_BEGIN_TEMPLATEbecome the instance attributes_invoke_header_prefix/_invoke_begin_template. Nothing else in the repository uses them.String parameter values stay verbatim (#21025), and the layout newlines around tool calls stay out of the content (#20873). This PR is rebased onto both and does not change either behavior.
Limitations
</|DSML|parameter>\n</|DSML|invoke>\n</|DSML|calls>sequence followed by the end of the output. These values are still cut at that point, as before. In streaming, the text after the cut can surface as content.finish().Relation to open PRs
#20990 (scan only new text while a tool call streams) changes
parse_streaming_incrementin the same file. It skips the search when no new invoke closing tag has arrived. With this PR, a closing tag can be found but not yet confirmed, and the delta that confirms it (for example, the next invoke header) contains no new closing tag. The shortcut must not move its search start past an unconfirmed closing tag. Whichever of the two merges second needs to reconcile this.Test Coverage
New tests in
tests/unittest/llmapi/apps/test_tool_parsers.py(already inl0_cpu.yml), parametrized over V3.2, V4 and V4.1:TestDeepSeekDsmlLiteralTags, 13 cases × 3 formats. The cases include:<|end▁of▁sentence|>in a value and after the block;Each case checks:
detect_and_parse;finish(), which must give the same normal text and calls as the non-streaming result and leak no DSML or EOS text;apply_tool_parser, streaming withfinished=Trueon the last delta and non-streaming.TestDeepSeekDsmlStreamingFinish:finish()once confirmed;finish()flushes an invoke without a closing calls tag and is idempotent;parse_partialwaits for confirmation.Existing tests, including those added by #20873 and #21025, are unchanged.
TestDeepSeekV32Parser::test_parse_streaming_increment_multiple_tools_streamingends an invoke with a new calls block, so a new calls block is accepted as a valid continuation.The full
test_tool_parsers.pywas run CPU-only on a TensorRT-LLM 1.4.0rc1 container, with this branch'stensorrt_llm/serve/tool_parser/and tests mounted:The 4 failures are
TestQwen3CoderRequiredTools/TestQwen3CoderParameterRecoverycases with thinking enabled. They depend on main's reasoning parser, which is newer than the container's package.pre-commit on the changed files: clean.
PR Checklist