You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #295 / PR fix/embedded-tool-call-dialects (2da1b99).
That patch closes the JSON-family embedded tool-call dialects (<tool_call>/<tool_code> wrappers, flat + OpenAI-nested bodies, parameters alias, bare/concatenated JSON) — verified, and it takes the barred-owl leak rate from ~50% to ~0% in a 12-trial sweep. This issue tracks the non-JSON tail it does not cover.
Status: Step 1 (recovery via looksLikeAttemptedToolCall) is done in #298 — log-validated: 0 of 23 real terminal-leak strings leak with zero recovery now. Steps 2–3 (first-pass parsing of the tail) remain. Also note: the merged #296 added a <function=NAME> handler, so that row of the table now parses first-pass (updated below).
Why this matters (don't let 50%→0% read as "solved")
The 0% sweep was a stochastic round where nimbus-qwen happened to emit JSON dialects. In an earlier round the model emitted the bare python-call form and the "Show me barred owl occupancy" question leaked 4/4 — that exact form is still uncovered. Under reasoning-on this model rotates dialects unpredictably, so the tail recurs.
Critically, these forms leaked with zero recovery attempt: parseEmbeddedToolCalls doesn't parse them andlooksLikeAttemptedToolCall returned false, so the #288 re-prompt never fired either. Step 1 (#298) fixes the recovery half — the looksLikeAttemptedToolCall column below is now ✅ for the whole tail; first-pass parsing is still ❌ pending steps 2–3.
Status (unit-tested; recovery column reflects #298)
Dialect
first-pass parse
looksLikeAttemptedToolCall
net
show_layer(layer_id="…") bare python-call (dbl & single quote)
All observed live from qwen / DSE-nimbus on barred-owl with reasoning on. (Reasoning off = clean, but that isn't an option — the app needs reasoning for answer accuracy.)
Proposed fixes, in priority order
Broaden looksLikeAttemptedToolCall (cheapest, do first). ✅ Done in fix(agent): flag the non-JSON tool-call tail so #288 recovery fires (#297 step 1) #298. Flags bare known_tool(, tool-name-as-tag <known_tool>/<known_tool , and the <function=/<parameter=/<invoke/<model_calls> markers so the #288 re-prompt fires for the whole tail. Gated on toolRegistry.has(name); scans all matches (matchAll) so a real tool tag preceded by hallucinated reasoning tags (<antThinking>, </think>) is still caught.
Run the tool_name(...) funcMatch on unwrapped content. Today the funcMatch handler only runs inside a <tool_call>/<tool_code> wrapper (_pushEmbeddedCall). Extend it to scan bare content for known_tool(<args>), signature-gated. Also parse python-style kwarg="val" / kwarg='val' args (both quote styles) — not just JSON — since parseLenientJSON('layer_id="x"') fails.
(Optional) tool-name-as-tag and Claude <invoke name=…>. Recognize <known_tool>…</known_tool> (JSON or <param>…</param> body) and <invoke name="tool"><parameter name="k">v</parameter> and normalize to {name, args}.
Grow stripEmbeddedCalls alongside steps 2–3. Whatever new dialects the first-pass handlers recognize, the display-strip must strip too, or a recovered call echoes back to the user as prose. fix(agent): parse foreign tool-call dialects first-pass instead of re-prompting (#295) #296 wired stripEmbeddedCalls to reuse _scanJSONObjects, but the bare python-call / tool-name-tag / <invoke> forms are non-JSON, so each needs an explicit strip pattern.
Verification
Unit-test each row above → expected {name, args} (mirror the JSON cases already in the patch).
Replay via open-llm-proxy/headless/run.js --model qwen on barred-owl config with reasoning on; the "Show me barred owl occupancy across the range" question is the reliable trigger for the bare-python-call form.
Follow-up to #295 / PR
fix/embedded-tool-call-dialects(2da1b99).That patch closes the JSON-family embedded tool-call dialects (
<tool_call>/<tool_code>wrappers, flat + OpenAI-nested bodies,parametersalias, bare/concatenated JSON) — verified, and it takes the barred-owl leak rate from ~50% to ~0% in a 12-trial sweep. This issue tracks the non-JSON tail it does not cover.Why this matters (don't let 50%→0% read as "solved")
The 0% sweep was a stochastic round where nimbus-qwen happened to emit JSON dialects. In an earlier round the model emitted the bare python-call form and the "Show me barred owl occupancy" question leaked 4/4 — that exact form is still uncovered. Under reasoning-on this model rotates dialects unpredictably, so the tail recurs.
Critically, these forms leaked with zero recovery attempt:
parseEmbeddedToolCallsdoesn't parse them andlooksLikeAttemptedToolCallreturnedfalse, so the#288re-prompt never fired either. Step 1 (#298) fixes the recovery half — thelooksLikeAttemptedToolCallcolumn below is now ✅ for the whole tail; first-pass parsing is still ❌ pending steps 2–3.Status (unit-tested; recovery column reflects #298)
looksLikeAttemptedToolCallshow_layer(layer_id="…")bare python-call (dbl & single quote)<show_layer>{"layer_id":"…"}</show_layer>tool-name-as-tag<show_layer><layer_id>…</layer_id></show_layer>tool-name-as-tag (XML body)<parameter=function>show_layer</parameter><parameter=layer_id>…</parameter>mangled<model_calls><invoke name="get_schema"><parameter name="dataset_id">…</parameter></invoke></model_calls>Claude XML<tool_call>\n<function=query", "arguments":{…}corrupted tag inside wrapperAll observed live from
qwen/ DSE-nimbus on barred-owl with reasoning on. (Reasoning off = clean, but that isn't an option — the app needs reasoning for answer accuracy.)Proposed fixes, in priority order
Broaden✅ Done in fix(agent): flag the non-JSON tool-call tail so #288 recovery fires (#297 step 1) #298. Flags barelooksLikeAttemptedToolCall(cheapest, do first).known_tool(, tool-name-as-tag<known_tool>/<known_tool, and the<function=/<parameter=/<invoke/<model_calls>markers so the#288re-prompt fires for the whole tail. Gated ontoolRegistry.has(name); scans all matches (matchAll) so a real tool tag preceded by hallucinated reasoning tags (<antThinking>,</think>) is still caught.tool_name(...)funcMatch on unwrapped content. Today thefuncMatchhandler only runs inside a<tool_call>/<tool_code>wrapper (_pushEmbeddedCall). Extend it to scan bare content forknown_tool(<args>), signature-gated. Also parse python-stylekwarg="val"/kwarg='val'args (both quote styles) — not just JSON — sinceparseLenientJSON('layer_id="x"')fails.<invoke name=…>. Recognize<known_tool>…</known_tool>(JSON or<param>…</param>body) and<invoke name="tool"><parameter name="k">v</parameter>and normalize to{name, args}.stripEmbeddedCallsalongside steps 2–3. Whatever new dialects the first-pass handlers recognize, the display-strip must strip too, or a recovered call echoes back to the user as prose. fix(agent): parse foreign tool-call dialects first-pass instead of re-prompting (#295) #296 wiredstripEmbeddedCallsto reuse_scanJSONObjects, but the bare python-call / tool-name-tag /<invoke>forms are non-JSON, so each needs an explicit strip pattern.Verification
{name, args}(mirror the JSON cases already in the patch).open-llm-proxy/headless/run.js --model qwenon barred-owl config with reasoning on; the "Show me barred owl occupancy across the range" question is the reliable trigger for the bare-python-call form.