Repository navigation
server: a streamed tool reply sends whitespace the whole reply does not have - #489
Merged
Merged
Conversation
…ot have With tools, the stream holds back trailing whitespace before a call and drops it once a call is hidden, so streamed content matches the whole reply. Three stream_preflight tests cover chat, Messages and Responses; three frozen stream goldens are listed as known diffs.
Contributor
|
Receipt from a DGX Spark (GB10, Nemotron 3.5 Lightning 4-bit @
So the stream now matches the whole reply on all three routes. |
Owner
|
Thank you, @Nipale-ai, for tracing the whitespace mismatch across chat, Messages and Responses. The three new stream regressions pass, and the change keeps trailing whitespace pending until it can distinguish ordinary prose from a hidden tool call. The known golden differences record that deliberate protocol correction; reasoning whitespace and prose around calls remain separate work. |
BlivionIaG
pushed a commit
to BlivionIaG/TensorFold
that referenced
this pull request
Oct 20, 2026
Merged
7 of 12 tasks
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.
Problem
When a reply makes a tool call, the whole (
stream: false) reply shows no text. The stream still sends the whitespace around the call blocks as text.Nemotron 3.5 Lightning writes
\n<tool_call>…</tool_call>\n. On 1.0.1 this gives (measured with the model). The code path is unchanged in 1.0.2, and the new tests fail the same way there:/v1/chat/completionscontent: nullcontent: "\n"deltas. One lands between two argument deltas of the same call ("…\n…})./v1/messages[tool_use], no text block[text "\n", tool_use, text "\n"]/v1/responses[function_call], no message item[message "\n", function_call, message "\n"]Clients that stream send this text back as history. Nemotron's template does not trim earlier assistant turns. The next prompt therefore has
<think></think>\n\n\n<tool_call>where a non-streaming client sends<think></think>\n<tool_call>. The goldens show the same mismatch:tools/two_toolshascontent: null, andtools/two_tools-streamstreams"\n".Change
Server code only. Tokens, engine and kernels do not change.
reply_text.zig: two small helpers.heldBack: with tools, the streamed text without its trailing whitespace. It returns a prefix, so the stream's pointer checks still hold.finishedProse:hideToolCallsof the finished reply. Once a call is hidden, its trailing whitespace is not sent either.chat.zig: two one-line changes.Generation.commitusesheldBackandGeneration.finishusesfinishedProse. A reply with tools but no call still sends its whitespace at the end, so plain prose does not change.stream_preflight_test.zig: a fake engine (CallEngine) answers two calls byte by byte, with a\nbetween them. Three new tests send this reply through the chat, Messages and Responses request translations. Each test checks that the streamed chat content equals the whole reply's content and that both have 2 calls.golden_check.zig: three frozen 0.6.6 goldens now differ on purpose and are listed as known diffs:tools/two_tools-stream,tools/two_tools-single-streamandanthropic/tool-use-stream. The stream no longer sends"\n", so the twotwo_toolsgoldens also lose their secondroleline. The new tests catch a revert. The known-diff entries alone would not."\n<tool_call>{nope}</tool_call>\n". The stock stream sends only"\n\n", but the whole reply has the full text. With this change, the stream sends the full text at the end, the same as the whole reply.Tests (f8fe17d, Zig 0.17.0, aarch64 Linux)
"\n", the whole reply has none.build.zigadds the server tests andtest-goldenonly on macOS (metalTargets→nativeServer).zig build test test-goldentherefore stops withno such step: test-golden, on stock and with this change.zig build teston Linux runs only the CUDA host tests, not the server tests. Its one failure (direct_io, on stock and with this change) is fixed by the open core: the direct_io test's reference read goes through the page cache #486.zig/tests/server/zig_test.sh(results above).tool_parse_test.zig: 14 pass on stock and with this change.test-goldengraph frombuild.zig, built by hand:fake_serve+golden_check, plus-lcbecause Linux needs it. Stock: 312/319 equal + 7 known. This change: 309/319 equal + 10 known. 0 unexpected on both.zig build test test-golden -Dcpu=apple_m1) and the Metal model check were not run for this change. The server code is the same for both engines.tools/zig/lean_check.py: 512 problems on f8fe17d, 512 with this change. chat.zig has 599 lines.zig fmt --check: clean on the 4 changed files.Measured on 1.0.1 (041d14a)
Setup:
TensorFold/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-MLX-4bit, revisiond9d758fb83953437f7263256b0d96157e2a348b8.serve <checkpoint> --parallel 8 --context 262144 --temperature 0 --no-thinking.Results:
zig build native -Dtarget=aarch64-linux-gnu.2.28 -Dversion=1.0.1, with the 16 fatbins taken from the 1.0.1 binary andstripon as in the release.1ff1f6ea…).ff116abb…, 128 bytes larger, with the same 16 fatbins.token_shachecks.\n\n.Not covered
"\nText<tool_call>…"streams the\n, and<tool_call>…</tool_call>\nDone.streams"\nDone.".reasoning_contentkeeps a trailing\nthat the whole reply strips (tools/xml_tool-stream). This is the same pattern and belongs in a separate change.Reproduce
On 1.0.1 this prints two
"content": "\n"deltas. With this change it prints none.