Skip to content

[Frontend][Parser] Stream long DSML parameter values for DeepSeek tool calls - #64196

Draft
xlshaoscu wants to merge 1 commit into
vllm-project:mainfrom
xlshaoscu:fix/deepseek-dsml-long-arg-streaming
Draft

xlshaoscu wants to merge 1 commit into
vllm-project:mainfrom
xlshaoscu:fix/deepseek-dsml-long-arg-streaming

Conversation

@xlshaoscu

Copy link
Copy Markdown

Overview

DeepSeek models emit tool-call arguments as DSML parameters. With arg_structural_chars = frozenset(">"), ParserEngine._compute_arg_delta() drops every delta that contains no tag boundary, and _dsml_arg_converter only re-serialises the whole parameter list. A long string="true" value contains no '>' at all, so it is buffered until its closing tag arrives: the client receives zero bytes for the entire generation. A 15k-character Write payload keeps the stream silent for 130–220 s, far past any client read timeout, while the engine keeps decoding normally.

This PR wires an arg_value_extender into the DeepSeek V3.2 / V4 / V4.1 configs so the open parameter value streams while it is generated.

Claims

  • Long DSML parameter values stream incrementally instead of being flushed once, when the closing tag lands.
  • Nothing changes for values the engine does not consider streamable (string="false" numbers/objects, keys whose schema type is not string) — they still go through the full converter.
  • The final arguments are unchanged: the streamed text stays prefix-safe and equals what the full conversion produces.

Validation

  • Added tests/parser/engine/test_deepseek_long_arg_streaming.py, parametrised over deepseek_v4 / deepseek_v41 / deepseek_v32. It asserts the value arrives in many fragments, that no fragment carries most of it, and that a value containing < / a partial closing-tag prefix is not corrupted.
  • Offline replay of main's _compute_arg_delta (real _dsml_arg_converter + _safe_arg_prefix + the new extender) over a 3 KB value: 4 argument deltas with a 2168-char silent gap before, 273 deltas with a max 88-char gap after; prefix-safe and parse-equal for all three dialects.
  • ⚠️ Not run locally: the authoring machine has no vLLM runtime, so the new tests still need to run in CI. Please treat the PR as pending CI validation.

Details

  • Root cause. _compute_arg_delta() returns None when arg_structural_chars.isdisjoint(raw_delta). DSML only guarantees a '>' at tag ends, so every delta of a value body is suppressed. _safe_arg_prefix() is already able to stream an unterminated trailing string, but it is only reached when a delta survives that gate.
  • Fix. make_param_value_extender(param_start, param_close, extra_stop_tags) returns an extender that re-serialises only the value tail, using the same json.dumps escaping as _dsml_arg_converter (json.dumps(visible)[1:-1]). A trailing partial tag is held back via the existing partial_tag_overlap helper; when the closing tag (or another tag) appears, the extender returns None and the engine clears it and falls back to the full converter.
  • Dialects. V4.1 uses spaced DSML (<|DSML| parameter), so it rebuilds the extender from its own constants instead of inheriting V4's; V3.2 reuses the V4 parameter format with a different outer wrapper.
  • Limitation. The extender only streams what open_value_streams permits, i.e. the trailing value the schema types as a string. Object/array arguments keep the current behaviour, and a value whose key is not declared as a string in the tool schema is also unchanged.

Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans). — not available in this authoring environment; please treat this as unchecked.

  • AI assistance was used during the creation of this PR.

  • Design Fit: reuses the existing arg_value_extender / open_value_streams machinery already used by gemma4 and glm47_moe; parser-local change, no engine changes.

  • Testing and Validation: adds parser-level streaming tests for all three dialects; CI run still required (see Validation).

  • Code Quality and Style: no new dependencies, reuses partial_tag_overlap, docstrings explain the root cause and the fallback contract.

  • Pull Request Contents: summary, root cause, trade-offs and limitations are documented above.

…l calls

DeepSeek model output carries tool arguments as DSML parameters. The engine's
arg_structural_chars gate (">") drops every delta that contains no tag
boundary, and _dsml_arg_converter only re-serialises the whole parameter list,
so a long string="true" value (which contains no ">") used to be buffered
until its closing tag arrived. The client then received no bytes for the whole
generation: a 15k-character Write payload kept the stream silent for 130-220
seconds, past any client read timeout, while the engine kept decoding.

Wire an arg_value_extender into the DeepSeek V3.2, V4 and V4.1 configs so the
open parameter value is serialised incrementally as it is generated. Only a
trailing partial tag is held back, and the engine falls back to the full
converter once the closing tag arrives (or when the value is not streamable,
e.g. string="false" numbers, where open_value_streams stays False).

Signed-off-by: xlshaoscu <xlshaoscu@foxmail.com>
@mergify mergify Bot added deepseek Related to DeepSeek models DSv4.1 Related to DeepSeek-V4.1 models tool-calling labels Oct 28, 2026
@github-project-automation github-project-automation Bot moved this to Unsorted queue in Tool Calling Oct 28, 2026
@github-project-automation github-project-automation Bot moved this to Unsorted queue in Tool Calling (arpera) Oct 28, 2026
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

xlshaoscu added a commit to xlshaoscu/vllm-ascend that referenced this pull request Oct 28, 2026
…V4.1

DeepSeek V4 and V4.1 stream tool arguments as DSML parameters and configure
arg_structural_chars = {'>'}. ParserEngine._compute_arg_delta() drops every
delta that shares no character with that set, so a long string="true" value
(file body, heredoc, ...) is buffered until its closing tag arrives. The
client then receives no bytes for the whole generation - a 15k-character
payload kept the stream silent for 130-220 seconds, past any client read
timeout, while the engine kept decoding normally.

For the deepseek_v4 / deepseek_v41 parser engines only, let value-body deltas
through while still holding back a trailing partial tag. Conversion, prefix
and diff bookkeeping stay in upstream code.

Verified offline against the engine's own converter/prefix logic: the same
3 KB value produced 4 argument deltas with a 2168-character silent gap before
the patch and 273 deltas with a max 88-character gap after it, prefix-safe.
Upstream fix: vllm-project/vllm#64196

Signed-off-by: xlshaoscu <xlshaoscu@foxmail.com>
xlshaoscu added a commit to xlshaoscu/vllm-ascend that referenced this pull request Oct 28, 2026
…V4.1

DeepSeek V4 and V4.1 stream tool arguments as DSML parameters and configure
arg_structural_chars = {'>'}. ParserEngine._compute_arg_delta() drops every
delta that shares no character with that set, so a long string="true" value
(file body, heredoc, ...) is buffered until its closing tag arrives. The
client then receives no bytes for the whole generation - a 15k-character
payload kept the stream silent for 130-220 seconds, past any client read
timeout, while the engine kept decoding normally.

For the deepseek_v4 / deepseek_v41 parser engines only, let value-body deltas
through while still holding back a trailing partial tag. Conversion, prefix
and diff bookkeeping stay in upstream code.

Verified offline against the engine's own converter/prefix logic: the same
3 KB value produced 4 argument deltas with a 2168-character silent gap before
the patch and 273 deltas with a max 88-character gap after it, prefix-safe.
Upstream fix: vllm-project/vllm#64196

Signed-off-by: xlshaoscu <xlshaoscu@foxmail.com>
@mergify

mergify Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @xlshaoscu.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deepseek Related to DeepSeek models DSv4.1 Related to DeepSeek-V4.1 models needs-rebase tool-calling

Projects

Status: Unsorted queue

Development

Successfully merging this pull request may close these issues.

1 participant