Skip to content

fix(sglang): stream tool_call deltas incrementally instead of bufferi… - #72

Open
guojn1 wants to merge 5 commits into
DingoRouter-basefrom
DingoRouter-t2
Open

guojn1 wants to merge 5 commits into
DingoRouter-basefrom
DingoRouter-t2

Conversation

@guojn1

@guojn1 guojn1 commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

…ng until finish

Client-side TTFT of pure tool-call streaming requests was effectively equal to the whole generation time: the post-processor only attached accumulated tool_calls to the finish chunk, and content-less deltas were suppressed, so no bytes reached the client during generation (Nexus TTFT/total median ~99.6%/97.4% vs ~17.2% for native SGLang).

The post-processor now emits OpenAI tool_call deltas as the parser produces them: the id+name delta goes out with the first argument fragment and argument fragments stream as they arrive (the un-emitted tail of the accumulated arguments is the source of truth, so event granularity cannot produce duplicates).

Correctness guards from the buffered design are retained:

  • names without any argument fragment are never emitted, so a misidentified prompt word cannot leak as a dangling call;
  • unknown tool names (not in the request's tool list) are suppressed mid-stream and purged at finish, exactly as before;
  • the finish-time full-text re-parse remains authoritative, now in merge mode: argument suffixes for partially streamed calls are patched (with streamed indices kept stable), calls the streaming parser missed are appended under fresh indices, and when nothing was streamed the re-parse rebuilds state exactly as before;
  • malformed (non-JSON) accumulated arguments of already-streamed calls can no longer be retracted, so they now trigger the finish-time re-parse for authoritative recovery instead of being silently dropped; finish_reason is still rewritten to tool_calls when a call was emitted.

Tests: existing assertions now reassemble the delta stream per the client contract (semantic regression), plus new cases locking the TTFT contract (deltas arrive before the finish chunk, first entry carries id+name, distinct indices/ids for parallel calls) and the new malformed/unknown-name streaming contract. Not executed locally (no sglang env); run on gpu_1 CI.

Overview:

Details:

Where should the reviewer start?

Related Issues

⚠️ This section is required. Choose one path below and delete the other.

🔗 This PR is linked to an issue:

  • Closes #XXXX

🚫 This PR is NOT linked to an issue:

  • Confirmed — no related issue

@guojn1
guojn1 deployed to external_collaborator September 23, 2026 07:42 — with GitHub Actions Active
@guojn1
guojn1 had a problem deploying to external_collaborator September 24, 2026 06:23 — with GitHub Actions Failure
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 24, 2026
@guojn1
guojn1 had a problem deploying to external_collaborator September 24, 2026 10:11 — with GitHub Actions Failure
@guojn1
guojn1 had a problem deploying to external_collaborator September 24, 2026 10:38 — with GitHub Actions Failure
…ewrites

A $ref below the definition (#/$defs/City/properties/name) must keep
its pointer suffix when the leading definition token is renamed during
hoisting; only the definition name is remapped.  This path
(partition('/') + suffix re-append) had no coverage.  Verified locally
via AST extraction of the shipped functions: the new scenario passes
and all 35 prior checks remain green.  Production code unchanged.
@guojn1
guojn1 had a problem deploying to external_collaborator September 25, 2026 23:09 — with GitHub Actions Failure
Frontend SGLang path discarded logprobs from the backend, causing
logprobs:true requests to return null instead of probability data.

- sglang_processor.py: accumulate pending_log_probs/pending_top_logprobs,
  pass them into mapped_response on flush
- sglang_prepost.py: add _build_logprobs to assemble OpenAI-format
  logprobs, replace hardcoded logprobs:None in process_output
- non-streaming path handled by Rust DeltaAggregator automatically
@guojn1
guojn1 had a problem deploying to external_collaborator September 26, 2026 00:52 — with GitHub Actions Failure
Dynamo forces incremental_streaming_output=True, so SGLang sends
disjoint logprob arrays per chunk. extract_from_sglang_meta was always
slicing with num_output_logprobs_so_far (cumulative mode), causing
all chunks after the first to return None — only the first chunk's
logprobs survived.

- logprobs.py: add incremental param; when True, skip slicing and
  use output_token_logprobs directly as disjoint entries
- decode_handler.py + llm_engine.py: pass incremental=True
- test_logprobs.py: add incremental mode tests
@guojn1
guojn1 had a problem deploying to external_collaborator September 26, 2026 01:23 — with GitHub Actions Failure

This branch had an error being deployed

1 failed deployment
external_collaborator — b206dac0 Deployed Sep 26, 2026 by guojn1 via ok-to-test #139
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant