Repository navigation
tool_parse: unclosed namespaced tool-call openers no longer rescan the reply - #913
jishnuvenugopal wants to merge 2 commits into
Conversation
cwschroeder
left a comment
There was a problem hiding this comment.
Reviewed a8786a7b against main 46a08c18; no blocking issue found. The productive caller advances from monotonically, the ASCII-lowered lookup retains the previous name matching, and the new envelope end equals the old closer length. The index removes repeated suffix searches in this path; the other scans and envelope sort retain their existing costs.
I added an independent full-output comparison on M5 Max, macOS 26.6.2, Zig 0.17.0, ReleaseSafe. All 100,000 deterministic cases match, including nested/overlapping namespaces, distinct non-ASCII bytes, damaged tags, other tool formats, empty tools and call limits. There are 59,087 cases with calls. Content, normalized calls, closeCall, both hideToolCalls modes and finishedProse are compared byte for byte; only generated call IDs are excluded.
The reproducible harness and runner pin both commits. A negative control shortening the candidate's envelope end by one byte fails with a content mismatch at case 10, covering residue handling beyond the closer-position oracle. The unmodified comparison passes with output digest f72c1dab522f51e891056de0c94565fb491b6388a0dfd7e699b677f4666d8101.
All four current-head CI checks pass. This additional receipt checks host parser correctness; I made no new timing or device measurement.
Thank you, @ashhart, for spotting this scan in your review of #882, and for the care you put into every review and
into TensorFold itself. This is the follow-up for the scan you named there.
What this changes
envelopes(zig/src/server/tool_parse.zig) paired each namespaced opener<NAME:tool_call>with its closer bysearching from the opener, so an opener with no closer searched to the end of the reply, once per opener.
Closers(new,
zig/src/server/tool_closers.zig) reads every</NAME:tool_call>once, by ASCII-lowered name, the first time anamespaced opener appears, and gives each opener the first closer at or after it: the one
findIgnoreCasePosfound.Openers come in order, so each name's closers are passed once, also when every opener has a different name; the other
scans and the envelope sort are unchanged.
Verified outcome
<ns:tool_call>repeated to 128 KiB took 64 ms to parse at the end of the reply, against 1.2 ms for 16 KiB.the reply per opener.
byte-identical results, and a test checks the new lookup against the old search on 5,000 random texts.
Receipt
Environment:
mainat c7137bb, Zig 0.17.0, Apple M4, macOS 27.0.1. Host code: no model or GPU.Exactness: n/a. No engine change; the reply's tokens and text are the same.
Decode speed before and after: n/a.
bench_openai.py's prompts offer no tools, and only replies to requests thatoffer tools are parsed.
Prompt speed before and after: n/a, for the same reason.
Speed on
<ns:tool_call>repeated, in ReleaseSafe as the server ships, fastest of three:parsefinishedProseSame output: a local fuzz harness (not part of this change), with namespaced openers and closers added to its
inputs, ran 50,000 edited replies through
parse,closeCall,hideToolCallsandfinishedProseon c7137bb andon this branch. A digest of everything they returned, call ids left out, is equal (2411aa090ffa1aff).
Tests run:
namespaced openers without closers read in linear time(tool_parse_test.zig):parseon 1,000 openers andon 8,000, once with one name and once with a new name per opener. 8x the openers may take at most 24x the time
(or under 20 ms), the bound fuzz-cortheon's linear-time test uses. It fails on c7137bb and passes on this branch.
a closer found by name is the one a search for it finds: on 5,000 random texts of closers in mixed case,near-misses and non-ASCII names,
Closers.nextequalsfindIgnoreCasePosfor every name at every start. Itfails with the cursor off by one and with the names not lowered.
zig build test test-golden -Dcpu=apple_m1, uncached: 164 of 164 steps, 1983 of 1991 tests pass, 8 skip,0 fail (c7137bb: 1981 of 1989, 8 skip); the two more are the new tests.
zig build fuzz-cortheonpasses.zig fmt --check build.zig zigis clean. Lean: 527 problems on the base and 527 on this branch, 0 new(
lean_new.py).every reader in the local harness passes its linear-time check on every shape, the harness digest is unchanged,
and the checks above pass.
Replies without a namespaced opener never read the closers, so
parse's other paths do no new work.Not run, and why: no CUDA host here. The parser is server code that both backends use, and its tests need no GPU.
Checklist
change.)
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (Noengine change; not re-run.)
arithmetic changes.)
fails before. The equivalence test calls
Closers, which this change adds.)