Skip to content

fix(tools): defensively recover concatenated streamed tool arguments#66

Open
walcz-de wants to merge 1 commit into
mudler:mainfrom
walcz-de:fix/streamed-toolargs-upstream
Open

fix(tools): defensively recover concatenated streamed tool arguments#66
walcz-de wants to merge 1 commit into
mudler:mainfrom
walcz-de:fix/streamed-toolargs-upstream

Conversation

@walcz-de

Copy link
Copy Markdown

Problem

When streamed tool-call argument deltas are accumulated into Function.Arguments, some providers re-emit the full arguments object rather than incremental fragments — most notably an OpenAI-compatible proxy that maps Anthropic/Gemini tool-call streaming into OpenAI delta.tool_calls. The accumulated string then becomes a concatenation, e.g. {"a":1}{"a":1}.

json.Unmarshal rejects that with invalid character '{' after top-level value, so decisionWithStreaming / the non-streaming path set allParsed = false and the tool loop cannot proceed. Result: agentic tool-calling is unusable through such providers, while local backends that emit correct incremental deltas are unaffected.

Fix

Add parseStreamedToolArgs which keeps the direct json.Unmarshal fast path and, only on failure, recovers the first complete top-level JSON object via json.Decoder. Both parse sites (streaming + non-streaming) route through it. Truly malformed input still returns an error (no silent garbage).

Test

parse_defensive_test.go covers: plain-valid, concatenated-duplicate, concatenated-different (first wins), malformed (errors).

Signed-off-by included (DCO).

When streamed tool-call argument deltas are accumulated into Function.Arguments, some
providers (e.g. an OpenAI-compat proxy that maps Anthropic/Gemini tool-call streaming into
OpenAI deltas) re-emit the full arguments object, so the accumulated string becomes a
concatenation like {"a":1}{"a":1}. A plain json.Unmarshal then fails with
'invalid character { after top-level value' and the tool loop cannot proceed — agentic
tool calling through such providers is unusable, while local backends with correct
incremental deltas are unaffected.

Recover the FIRST complete top-level JSON object via json.Decoder on that specific failure;
the happy path keeps the direct Unmarshal. Adds parseStreamedToolArgs + a regression test
(plain / concatenated-duplicate / concatenated-different / malformed) and routes both the
streaming and non-streaming parse sites through it.

Signed-off-by: stefanwalcz <stefan.walcz@walcz.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant