Commit a02944a
fix(llamafile): harden the malformed-500 rescue gate and drop skeleton calls (#128)
* fix(llamafile): drop skeleton tool calls in rescue to stop tool-error exhaustion
The malformed-tool-call 500 rescue shim was lenient: it returned every
parseable <tool_call> block, including skeleton/preview blocks missing
required parameters. On a write-stutter (a skeleton block immediately
followed by the complete call), both were returned; the skeleton was
dispatched, raised [ToolError] on the tool channel, and the model retried
into consecutive-tool-error exhaustion (observed on rig-04).
_rescue_tool_calls now filters on `required.issubset(args)`, returning only
complete calls. The write-stutter yields the complete call alone; a
skeleton-only body yields no calls, so the caller falls through to the
_STUTTER_RETRY_TEXT nudge (bounded by the retry budget upstream) instead of
a broken dispatched call. Malformed args are still kept raw (not dropped) to
match the native path — a garbled optional param must not suppress an
otherwise-complete call.
Also close three raw_body leaks: the fallback BackendError(500, ...) calls
in send_stream, _send_native, and the "no choices" path passed the raw
backend body positionally as `detail`, landing it in the logged/returned
message; they now pass it as raw_body= (kept on exc.body).
Tests: rewrite the three tests that locked in the lenient behavior (incl.
inverting skeleton-only to expect the nudge), add native coverage for
no-required-params and multi-call cases, and add streaming-path rescue
tests (previously zero) covering the stutter, skeleton-only, no-blocks,
unknown-tool, multi-line body reassembly, and arbitrary-500 cascade.
110 llamafile tests pass; full unit suite 1386 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CokRDPXHYQ4c65MUfdjTAG
* fix(llamafile): gate rescue on structural tool-call XML, not the error phrase
The malformed-tool-call 500 gate was pinned to the literal llama.cpp string
"Failed to parse input". Later llama.cpp builds rename that message (e.g.
beac5309f -> "...does not match the expected ... format"), so the gate
silently stopped matching on newer backends and a rescuable malformed call
cascaded as a hard BackendError instead.
Gate on the structural signal instead: a 500 is a rescuable tool-call artifact
iff its body carries generated tool-call XML (<tool_call>/<function=). Only the
model generating such a call and the backend choking on it puts that syntax in
a 500 body — genuine faults (OOM/slot/context/CUDA) never echo generation, and
a request-parse failure dumps JSON schema, not this XML (and so still cascades).
This is robust to every llama.cpp error-string rename. rescue itself only
returns calls for full <tool_call>...<function=NAME>... blocks naming a tool in
the request, so a broad gate never fabricates a call. Shared helper, so both the
streaming and native paths broaden together.
Tests: add phrase-independence coverage (a renamed-phrase body embedding a
complete block still rescues, native + streaming twin; renamed-phrase skeleton
still nudges) and a tools-schema-dump-cascades test documenting that a
request-parse 500 carries no XML token and therefore cascades.
114 llamafile tests pass; full unit suite 1390 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CokRDPXHYQ4c65MUfdjTAG
* docs(llamafile): record llama.cpp version boundary for the malformed-500 rescue
The structural gate in _is_malformed_tool_call_500 depends on llama.cpp
echoing the raw rejected generation into the 500 response body. That
behavior ended at llama.cpp commit 581e8eca8 ("chat: harden peg-native
tool call parsing", PR #24329), first shipped in tag b9656 (2026-06-15):
the parse-failure exception became a generic "does not match the expected
<format> format" and the raw generation moved to server logs only. On
b9656+ the gate can never match (nothing to rescue from the body), and the
grammar hardening in the same series makes the malformed call rare anyway,
so the 500 just cascades. The rescue is therefore effective only on builds
<= b9647. Correct the prior "robust to every error-string rename" comment,
which missed that the rename bundled removal of the raw body.
Comment-only; no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(llamafile): match truncated tool-call open tags in the rescue gate
An echo cut off mid-open-tag (EOS or token budget landing inside
"<tool_call") is the same re-sampleable artifact as a complete block;
gate on the unclosed prefix so it returns the generic retry nudge
instead of cascading as BackendError. Pin the b9656+ no-echo generic
parse-500 as a cascade on both send() and send_stream().
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent f7749df commit a02944a
2 files changed
Lines changed: 474 additions & 53 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
53 | 70 | | |
54 | | - | |
| 71 | + | |
55 | 72 | | |
56 | 73 | | |
57 | 74 | | |
| |||
60 | 77 | | |
61 | 78 | | |
62 | 79 | | |
63 | | - | |
64 | | - | |
65 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
66 | 83 | | |
67 | | - | |
68 | | - | |
| 84 | + | |
| 85 | + | |
69 | 86 | | |
70 | 87 | | |
71 | 88 | | |
| |||
135 | 152 | | |
136 | 153 | | |
137 | 154 | | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
147 | 168 | | |
148 | 169 | | |
149 | | - | |
150 | | - | |
151 | | - | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
152 | 174 | | |
153 | 175 | | |
154 | 176 | | |
| |||
165 | 187 | | |
166 | 188 | | |
167 | 189 | | |
168 | | - | |
| 190 | + | |
169 | 191 | | |
170 | 192 | | |
171 | 193 | | |
172 | 194 | | |
173 | 195 | | |
174 | 196 | | |
| 197 | + | |
| 198 | + | |
175 | 199 | | |
176 | 200 | | |
177 | 201 | | |
| |||
560 | 584 | | |
561 | 585 | | |
562 | 586 | | |
563 | | - | |
564 | | - | |
565 | | - | |
566 | | - | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
567 | 591 | | |
568 | 592 | | |
569 | 593 | | |
| |||
586 | 610 | | |
587 | 611 | | |
588 | 612 | | |
589 | | - | |
590 | | - | |
| 613 | + | |
| 614 | + | |
591 | 615 | | |
592 | 616 | | |
593 | 617 | | |
| |||
757 | 781 | | |
758 | 782 | | |
759 | 783 | | |
760 | | - | |
761 | | - | |
762 | | - | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
763 | 788 | | |
764 | 789 | | |
765 | 790 | | |
| |||
775 | 800 | | |
776 | 801 | | |
777 | 802 | | |
778 | | - | |
779 | | - | |
| 803 | + | |
| 804 | + | |
780 | 805 | | |
781 | 806 | | |
782 | 807 | | |
783 | 808 | | |
784 | 809 | | |
785 | 810 | | |
786 | 811 | | |
787 | | - | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
788 | 817 | | |
789 | 818 | | |
790 | 819 | | |
| |||
0 commit comments