Skip to content

[#19225][fix] Keep markdown fences in DeepSeek V3 streamed content - #19226

Open
Yigtwxx wants to merge 1 commit into
NVIDIA:mainfrom
Yigtwxx:fix/deepseekv3-streaming-markdown-fences
Open

Yigtwxx wants to merge 1 commit into
NVIDIA:mainfrom
Yigtwxx:fix/deepseekv3-streaming-markdown-fences

Conversation

@Yigtwxx

@Yigtwxx Yigtwxx commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #19225.

When a streamed delta carries no tool-call markup, DeepSeekV3Parser.parse_streaming_increment
flushes it as content after stripping the closing tokens. The list it strips also contains
the markdown fence, so every fenced code block in a streamed answer loses its fences while
detect_and_parse returns the same text intact:

from tensorrt_llm.serve.tool_parser.deepseekv3_parser import DeepSeekV3Parser

text = "Here is the code:\n```python\nprint(1)\n```\nDone."

DeepSeekV3Parser().parse_streaming_increment(text, tools).normal_text
# before: 'Here is the code:\npython\nprint(1)\n\nDone.'
# after:  'Here is the code:\n```python\nprint(1)\n```\nDone.'
DeepSeekV3Parser().detect_and_parse(text, tools).normal_text
# 'Here is the code:\n```python\nprint(1)\n```\nDone.'  (unchanged)

The fence is part of the tool-call syntax only inside a
<|tool▁call▁begin|> ... <|tool▁call▁end|> block. This branch is reached only when no
start token is present in the buffer, so the text it sees is model content. A completed
call is removed from the buffer together with its fences (deepseekv3_parser.py:188-194),
so nothing from the markup can reach this branch either.

The change drops "```" from the list, leaving the two closing tokens. That is exactly what
the sibling DeepSeekV31Parser does on the same path (deepseekv31_parser.py:109), and
the DSML parsers (deepseek_v32, deepseek_v4) also stream such text verbatim, so
deepseek_v3 was the only parser in the package that altered content here. There is no
API change and no change to the non-streaming path.

Test Coverage

tests/unittest/llmapi/apps/test_tool_parsers.py, already registered as cpu_only in
tests/integration/test_lists/test-db/l0_cpu.yml:

  • test_deepseek_streaming_keeps_markdown_fences — 4 parametrizations, one per DeepSeek
    parser class. Each streams a fenced code block as a single delta and asserts the content
    comes back verbatim and equal to what detect_and_parse returns for the same string.
    The V3 case fails on main; the other three pin the sibling parsers so the strip is not
    reintroduced there.

Runtime on this box, measured over 2000 repetitions each: 9.7/12.0/25.6/24.2 us, 0.071 ms
for all four together. The file's full suite is otherwise unchanged: 351 passed locally
versus 347 before, with an identical set of 50 pre-existing failures that need the full
openai_protocol/postprocess_handlers import chain, which this machine cannot load.

PR Checklist

  • Commit is signed off (DCO)
  • PR title follows [#issue][type] description
  • Pre-commit hooks run on the changed files
  • Test added, verified red on main and green here
  • Single concern

@tongyuantongyu this is the same no-tool-call branch of deepseekv3_parser.py that #17573
and #17903 touched, one line in the strip list. Would you mind taking a look and triggering
the pipeline when you have a moment?

Dev Engineer Review

  • DeepSeekV3Parser.parse_streaming_increment now removes only tool-call closing tokens from normal streamed content. Markdown fences remain unchanged.
  • The non-streaming detect_and_parse path and public APIs remain unchanged.
  • The change matches the intended DeepSeekV31Parser behavior and limits regression risk to the affected streaming path.

QA Engineer Review

  • Updated tests/unittest/llmapi/apps/test_tool_parsers.py with a parameterized regression test for DeepSeek parser classes.
  • The test compares streamed and non-streamed output and verifies that fenced code blocks and other normal content remain unchanged.
  • Coverage verdict: sufficient for the reported regression. Test execution results are not provided.

Per-File QA Perspective

  • tensorrt_llm/serve/tool_parser/deepseekv3_parser.py: Verify that streamed normal content preserves Markdown fences while tool-call closing tokens remain filtered. Confirm completed tool calls and detect_and_parse behavior remain unchanged.
  • tests/unittest/llmapi/apps/test_tool_parsers.py: Covers the streaming/non-streaming equivalence regression across the DeepSeek parser classes. No test-list change is reported.

DeepSeekV3Parser.parse_streaming_increment removes every "```" from a
delta that carries no tool-call markup, so a streamed answer containing
a fenced code block loses its fences while detect_and_parse returns the
same text intact. The fence is only part of the tool-call syntax inside
a <|tool▁call▁begin|> ... <|tool▁call▁end|> block, which this branch
never sees. Strip only the closing tokens, as DeepSeekV31Parser does.

Signed-off-by: Yiğit ERDOĞAN <yigiterdogan023@gmail.com>
@Yigtwxx
Yigtwxx requested a review from a team as a code owner September 15, 2026 16:37
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f7fac592-0509-4ea5-85f1-9c1cde74513b

📥 Commits

Reviewing files that changed from the base of the PR and between f7f596b and c1c286c.

📒 Files selected for processing (2)
  • tensorrt_llm/serve/tool_parser/deepseekv3_parser.py
  • tests/unittest/llmapi/apps/test_tool_parsers.py

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


Walkthrough

The DeepSeek V3 streaming parser no longer removes standalone Markdown code fences from normal text. Parameterized tests verify fence preservation across DeepSeek V3, V3.1, V3.2, and V4 parsers.

Changes

DeepSeek fence preservation

Layer / File(s) Summary
Streaming text cleanup and regression coverage
tensorrt_llm/serve/tool_parser/deepseekv3_parser.py, tests/unittest/llmapi/apps/test_tool_parsers.py
The streaming parser removes only tool-call termination tokens from normal text. Parameterized tests verify that Markdown fences and other content remain unchanged.

Priority: ➖ Normal

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: brnguyen2

Merge Risk: ⚪ Minimal · up to c1c28

No actionable merge-blocking risk remains in the reviewed change.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the issue, fix type, and main change: preserving Markdown fences in DeepSeek V3 streamed content.
Description check ✅ Passed The description includes the required Description, Test Coverage, and PR Checklist sections. It explains the issue, implementation, test coverage, performance results, limitations, and checklist statu…
Linked Issues check ✅ Passed The change satisfies #19225. DeepSeekV3Parser.parse_streaming_increment now removes only <|tool▁calls▁end|> and <|tool▁call▁end|> in the no-tool-call path. It preserves Markdown fences and other…
Out of Scope Changes check ✅ Passed The diff is limited to the parser fix required by #19225 and its regression test in tests/unittest/llmapi/apps/test_tool_parsers.py. The changes do not add API changes or unrelated behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 2 files.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

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.

[Bug]: DeepSeek V3 streaming tool parser strips markdown fences from content

1 participant