fix(modelmgr): keep id-less streamed tool calls (Ollama) (#2262)

Ollama's OpenAI-compatible streaming endpoint emits a tool-call delta
carrying an `index` and a `function` payload but never an OpenAI-style
`id`. `_normalize_stream_tool_calls` dropped any tool call without an
`id`, so a tool-only turn yielded neither content nor a tool call: the
stream "completed" with 0 chars, the tool never ran, and the chat
appeared stuck. Models on standard OpenAI APIs (e.g. SiliconFlow) were
unaffected because they always send a `call_...` id.

Synthesize a stable per-index id (`call_<index>`) when the provider
omits one but a function name is present. Providers that do send ids
keep theirs, and parallel id-less calls keep distinct ids.

Adds regression tests for the single and multi id-less tool-call cases.

Fixes #2261
This commit is contained in:
Junyan Chin
2026-06-19 18:07:25 +08:00
committed by GitHub
parent 83623f6afe
commit 3d5b70cc5d
2 changed files with 122 additions and 0 deletions
@@ -392,6 +392,17 @@ class LiteLLMRequester(requester.ProviderAPIRequester):
elif not isinstance(arguments, str):
arguments = str(arguments)
# Some OpenAI-compatible providers (notably Ollama's
# /v1/chat/completions) stream a tool-call delta with an `index` and
# a `function` payload but never emit an OpenAI-style `id`. Without
# an id the call used to be dropped here, so the whole tool call
# silently vanished: a tool-only turn then yielded no content and no
# tool call, the stream "completed" with 0 chars, and the chat
# appeared stuck. Synthesize a stable per-index id so named-but-idless
# tool calls survive. Providers that do send ids keep theirs.
if not state['id'] and state['name']:
state['id'] = f'call_{index}'
if not state['id'] or not state['name']:
continue