Skip to content

fix(deepseek_v4): recover tool calls trapped by a dangling </think> (rebased) - #2

Closed
procr1337 wants to merge 59 commits into
mainfrom
dsv4-toolcall-workaround-HEAD
Closed

procr1337 wants to merge 59 commits into
mainfrom
dsv4-toolcall-workaround-HEAD

Conversation

@procr1337

@procr1337 procr1337 commented Jun 14, 2026 •

Copy link
Copy Markdown
Owner

Now upstream: vllm-project#45862

DeepSeek-V4-Flash occasionally omits the closing </think> before emitting a <|DSML|tool_calls> block. This is a model bug: the chat template / encoder explicitly instructs it to output its complete reasoning inside <think>... </think> BEFORE any tool calls or final response, and the model intermittently fails to follow that instruction. Reasoning extraction is gated on </think>, so when it is missing the entire tool-call block stays in the reasoning channel, the tool parser never sees it, and the call is silently lost (finish_reason=stop, empty content). This is malformed model output, not a parser/template bug: it reproduces on the upstream DeepSeek API for V4-Flash and is invariant to MTP/cutlass/concurrency. DeepSeek's own encoding README notes the model can emit malformed output and that production needs "additional error handling". See bench/dangling-think/ for the full investigation and repro tooling.

Workaround: a narrowly-scoped, opt-in recovery in DelegatingParser that promotes a tool-call block trapped in reasoning to a real tool call, framed explicitly as malformed-output recovery (NOT making the tool-call token a reasoning terminator).

  • ToolParser gains tool_call_start_token / tool_call_end_token and an opt-in recovers_tool_calls_in_reasoning flag, so the generic composition code keys off the declared interface instead of sniffing subclass internals. DeepSeekV32ToolParser (and thus V4) opts in.
  • Non-streaming: when auto tool-choice parsed zero tool calls and produced no content, and the reasoning tail contains a balanced start/end tool-call block, re-run the tool parser over it and keep the preceding prose as reasoning. The balance requirement avoids promoting half-formed output or a marker the model merely mentioned.
  • Streaming: watch the reasoning stream for the start marker, buffering partial markers across chunks so they never leak as reasoning; on a full marker, end reasoning right before it and route the remainder to the tool-call phase.
  • Tests exercise the composed reasoning+tool DelegatingParser (CPU-only, stub tokenizer) for both the non-streaming and streaming dangling-</think> shapes.

@procr1337 procr1337 changed the title fix(deepseek_v4): recover tool calls trapped by a dangling </think> fix(deepseek_v4): recover tool calls trapped by a dangling </think> (rebased) Jun 14, 2026
@procr1337
procr1337 force-pushed the dsv4-toolcall-workaround-HEAD branch from 0afb83c to d1751ce Compare June 15, 2026 18:54
yewentao256 and others added 2 commits June 15, 2026 19:50
@procr1337
procr1337 force-pushed the dsv4-toolcall-workaround-HEAD branch from d1751ce to 7073690 Compare June 15, 2026 20:07
jeejeelee and others added 24 commits June 15, 2026 13:17
Signed-off-by: Jee Jee Li <pandaleefree@gmail.com>
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
Signed-off-by: Woosuk Kwon <woosuk@inferact.ai>
Co-authored-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Woosuk Kwon <woosuk@inferact.ai>
vllm-project#44955)

Signed-off-by: factnn <166481866+factnn@users.noreply.github.com>
…tion (vllm-project#45588)

Signed-off-by: Ben Browning <bbrownin@redhat.com>
Co-authored-by: Flora Feng <4florafeng@gmail.com>
… scheduling + PD KV consumer (vllm-project#45357)

Signed-off-by: llx-08 <2596671364@qq.com>
Signed-off-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Jiangyun Zhu <riverclouds.zhu@qq.com>
Signed-off-by: Itay Alroy <ialroy@nvidia.com>
Signed-off-by: Itay Alroy <ialroy@nvidia.com>
Signed-off-by: Itay Alroy <ialroy@nvidia.com>
Signed-off-by: Woosuk Kwon <woosuk@inferact.ai>
…BERT (jinaai/jina-colbert-v2) (vllm-project#45210)

Signed-off-by: thomas <thomas.varghese@columbia.edu>
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Signed-off-by: Isotr0py <mozf@mail2.sysu.edu.cn>
Signed-off-by: zhenwei-intel <zhenwei.liu@intel.com>
…leak, and chat template sync (vllm-project#45553)

Signed-off-by: Luciano Martins <lucianommartins@users.noreply.github.com>
Co-authored-by: Luciano Martins <lucianommartins@users.noreply.github.com>
…oject#40183)

Signed-off-by: Git Bisector <gitbisector@gmail.com>
Signed-off-by: gitbisector <gitbisector@gmail.com>
Signed-off-by: git bisector <gitbisector@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
)

Signed-off-by: Bortlesboat <bortstheboat@gmail.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Signed-off-by: Jimmy Lee <hirejimmylee@gmail.com>
Signed-off-by: jiang1.li <jiang1.li@intel.com>
…ng (vllm-project#44784)

Signed-off-by: joshua <joshua.abraham@multicorewareinc.com>
Co-authored-by: joshua <joshua.abraham@multicorewareinc.com>
Co-authored-by: Or Ozeri <oro@il.ibm.com>
HanHan009527 and others added 26 commits June 16, 2026 04:35
…ed serving with Mooncake connector (vllm-project#44528)

Signed-off-by: hanhan.hank <hanhan.hank@bytedance.com>
Signed-off-by: Hank Han <hanhan7630@outlook.com>
…45463)

Signed-off-by: yewentao256 <zhyanwentao@126.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
…2726)

Signed-off-by: Lalithnarayan C <Lalithnarayan.C@amd.com>
Signed-off-by: Tyler Michael Smith <tlrmchlsmth@gmail.com>
Co-authored-by: Tyler Michael Smith <tlrmchlsmth@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tyler Michael Smith <tyler@neuralmagic.com>
)

Signed-off-by: Mike G <180722391+mikekg@users.noreply.github.com>
vllm-project#45707)

Signed-off-by: Netanel Haber <58652339+netanel-haber@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
…llm-project#42425)

Signed-off-by: Francesco Fusco <ffu@zurich.ibm.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Matthew Bonanni <mbonanni@redhat.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Tahsin Tunan <tahsintunan@gmail.com>
…padding (vllm-project#36616)

Signed-off-by: AjAnubolu <anuboluajay@gmail.com>
Signed-off-by: Matthew Bonanni <mbonanni@redhat.com>
Co-authored-by: Matthew Bonanni <mbonanni@redhat.com>
…llm-project#36895)

Signed-off-by: Sean Chen <seachen@redhat.com>
Co-authored-by: Yanan Cao <gmagogsfm@gmail.com>
Signed-off-by: Carl You <4531192+carlyou@users.noreply.github.com>
…ect#44178)

Signed-off-by: Tuukka Sarvi <tuukka.sarvi@amd.com>
Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
…llm-project#43098)

Signed-off-by: Wuyifei <wuyifei@me.com>
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
)

Signed-off-by: Micah Williamson <micah.williamson@amd.com>
Signed-off-by: Xianbao QIAN <xianbao.qian@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
…ined containers (vllm-project#45438)

Signed-off-by: Ting Sun <suntcrick@gmail.com>
Co-authored-by: Shengqi Chen <harry-chen@outlook.com>
…roject#45488)

Signed-off-by: Kourosh Hakhamaneshi <kourosh@anyscale.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lm-project#45767)

Signed-off-by: Dao Le <Dao007forever@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oject#45763)

Signed-off-by: Alex Bilichenko <alexbi29@users.noreply.github.com>
Co-authored-by: Alex Bilichenko <alexbi29@users.noreply.github.com>
Signed-off-by: jesse <szxfml@gmail.com>
Signed-off-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Nick Hill <nickhill123@gmail.com>
Signed-off-by: sfeng33 <4florafeng@gmail.com>
@procr1337
procr1337 force-pushed the dsv4-toolcall-workaround-HEAD branch from 7073690 to 8b9cca7 Compare June 16, 2026 19:58
DeepSeek-V4-Flash occasionally omits the closing </think> before emitting a
<|DSML|tool_calls> block. This is a model bug: the chat template / encoder
explicitly instructs it to output its complete reasoning inside <think>...
</think> BEFORE any tool calls or final response, and the model intermittently
fails to follow that instruction. Reasoning extraction is gated on </think>, so
when it is missing the entire tool-call block stays in the reasoning channel,
the tool parser never sees it, and the call is silently lost
(finish_reason=stop, empty content). DeepSeek's own encoding README
notes the model can emit malformed output and that production needs "additional
error handling".

Workaround: a narrow, opt-in recovery in DelegatingParser that salvages a
tool-call block left in the reasoning stream and promotes it to a real tool
call. This is deliberately scoped as recovery from malformed model output:
</think> remains the only reasoning terminator; the tool-call marker is not
added to the reasoning grammar.

- ToolParser gains tool_call_start_token / tool_call_end_token and an opt-in
  recovers_tool_calls_in_reasoning flag, so the generic composition code keys
  off the declared interface instead of sniffing subclass internals.
  DeepSeekV32ToolParser (and thus V4) opts in.
- Non-streaming: when auto tool-choice parsed zero tool calls and produced no
  content, and the reasoning tail contains a *balanced* start/end tool-call
  block, re-run the tool parser over it and keep the preceding prose as
  reasoning. The balance requirement avoids promoting half-formed output or a
  marker the model merely mentioned.
- Streaming: watch the reasoning stream for the start marker, buffering partial
  markers across chunks so they never leak as reasoning; on a full marker, end
  reasoning right before it and route the remainder to the tool-call phase.
- Tests exercise the composed reasoning+tool DelegatingParser with a stub tokenizer
  for both the non-streaming and streaming missing-</think> shapes.

Co-authored-by: Claude Opus
Signed-off-by: Procr <193802945+procr1337@users.noreply.github.com>
@procr1337
procr1337 force-pushed the dsv4-toolcall-workaround-HEAD branch from 8b9cca7 to f651dba Compare June 16, 2026 20:16
@procr1337 procr1337 closed this Jun 17, 2026
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.