Skip to content

fix(ollama): preserve tool call IDs to fix repeated same-tool calls#3321

Merged
sjrl merged 5 commits into
deepset-ai:mainfrom
SyedShahmeerAli12:fix/ollama-tool-call-ids
May 21, 2026
Merged

fix(ollama): preserve tool call IDs to fix repeated same-tool calls#3321
sjrl merged 5 commits into
deepset-ai:mainfrom
SyedShahmeerAli12:fix/ollama-tool-call-ids

Conversation

@SyedShahmeerAli12
Copy link
Copy Markdown
Contributor

@SyedShahmeerAli12 SyedShahmeerAli12 commented May 15, 2026

Related Issues

Proposed Changes:

OllamaChatGenerator was silently dropping tool call IDs, which broke any scenario where the same tool is called more than once in a single assistant turn. In the streaming accumulator, both calls mapped to the same key (the tool name since ID was None), so the second call overwrote the first and it was lost.

Fix: generate a synthetic ID (call_N) for each tool call in both the streaming and non-streaming paths, and propagate it to the final ToolCall objects. ollama_tc.get("id") is checked first so the code will use server-assigned IDs automatically if a future Ollama SDK version exposes them.

Affected locations:

  • _convert_ollama_response_to_chatmessage non-streaming path
  • _build_chunk streaming chunk builder
  • _handle_streaming_response + _handle_streaming_response_async streaming reconstruction

How did you test it?

  • Reproduced the bug: two streaming chunks calling weather with different cities only the second call survived before the fix
  • After the fix both calls are returned with distinct IDs
  • Added test_convert_ollama_response_to_chatmessage_with_repeated_tool
  • Added test_handle_streaming_response_repeated_tool_calls
  • Updated existing tests that previously asserted id=None on ToolCallDelta and ToolCall objects
  • All 41 unit tests pass, ruff clean

Notes for the reviewer

The Ollama Python SDK (0.6.1) drops the id field from the server JSON response during Pydantic parsing Message.ToolCall has no id field defined. Synthetic IDs (call_0, call_1, …) are the only option today. The get("id") fallback is a forward-compatible hook for when the SDK adds the field.

Checklist

@SyedShahmeerAli12 SyedShahmeerAli12 requested a review from a team as a code owner May 15, 2026 11:34
@SyedShahmeerAli12 SyedShahmeerAli12 requested review from sjrl and removed request for a team May 15, 2026 11:34
@github-actions github-actions Bot added integration:ollama integration:falkordb type:documentation Improvements or additions to documentation labels May 15, 2026
@SyedShahmeerAli12 SyedShahmeerAli12 force-pushed the fix/ollama-tool-call-ids branch from 351b59d to 7c71d37 Compare May 15, 2026 11:35
@github-actions
Copy link
Copy Markdown
Contributor

Coverage report (falkordb)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/falkordb/src/haystack_integrations/document_stores/falkordb
  document_store.py 524-525, 560, 563, 567, 615-616
Project Total  

This report was generated by python-coverage-comment-action

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

Coverage report (ollama)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/ollama/src/haystack_integrations/components/generators/ollama/chat
  chat_generator.py 498
Project Total  

This report was generated by python-coverage-comment-action

@SyedShahmeerAli12
Copy link
Copy Markdown
Contributor Author

SyedShahmeerAli12 commented May 15, 2026

Quick reproduction showing the bug and the fix

Before two streaming calls to the same tool, one silently lost:

tool_calls: 1
  tool='weather'  args={'city': 'London'}   ← Paris overwritten

After both calls preserved with distinct IDs:

tool_calls: 2
  id='call_1'  tool='weather'  args={'city': 'Paris'}
  id='call_2'  tool='weather'  args={'city': 'London'}

The root cause was that _build_chunk created ToolCallDelta with no id, so the streaming accumulator fell back to tool_name as the deduplication key collapsing two calls to the same tool into one.

@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented May 15, 2026

Please remove all changes unrelated to the PR. So all changes in falkdordb should be removed.

@SyedShahmeerAli12 SyedShahmeerAli12 force-pushed the fix/ollama-tool-call-ids branch from 7c71d37 to af73962 Compare May 15, 2026 11:45
@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented May 15, 2026

@SyedShahmeerAli12 could you add information about when Ollama started returning tool call IDs (e.g. which version of ollama is this supported?). Also were you able to reproduce the bug with a live ollama server and not just through the unit tests?

Comment thread integrations/ollama/tests/test_chat_generator.py
@SyedShahmeerAli12
Copy link
Copy Markdown
Contributor Author

SyedShahmeerAli12 commented May 15, 2026

Ollama version: Tool call IDs were added in v0.12.10 (released Nov 5, 2025) the release notes explicitly say "Ollama will now return tool call IDs from the /api/chat API".

Live reproduction: Yes, tested with Ollama v0.24.0. The raw HTTP response confirms IDs are present:

[
  {"id": "call_lyywui55", "function": {"name": "get_weather", "arguments": {"city": "Paris"}}},
  {"id": "call_0scw2dos", "function": {"name": "get_weather", "arguments": {"city": "London"}}}
]

However, the Ollama Python SDK (0.6.1) silently drops the id field Message.ToolCall does not define it, so Pydantic strips it during parsing. model_dump() never includes it. This means even against a live server, tool_call.id is always None through the current SDK.

I'll address the inline comments about synthetic IDs and update the PR accordingly.

@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented May 15, 2026

@SyedShahmeerAli12 do you know if there is an open issue for the SDK to add the tool call ID support?

@SyedShahmeerAli12
Copy link
Copy Markdown
Contributor Author

SyedShahmeerAli12 commented May 18, 2026

No open issue found in ollama/ollama-python for this. The SDK's Message.ToolCall Pydantic model only defines function: Function with no id field, so server-returned IDs are silently dropped during parsing.

Also addressed the inline comments removed synthetic ID generation. The streaming accumulator now keys by tool_call.index (the positional counter assigned per chunk, always unique even for repeated tool names), so the duplicate-tool-call bug is fixed without fabricating IDs. Server-provided IDs are passed through as-is; when Ollama doesn't return one, id is None.

@SyedShahmeerAli12 SyedShahmeerAli12 requested a review from sjrl May 20, 2026 14:03
@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented May 20, 2026

No open issue found in ollama/ollama-python for this. The SDK's Message.ToolCall Pydantic model only defines function: Function with no id field, so server-returned IDs are silently dropped during parsing.

Also addressed the inline comments removed synthetic ID generation. The streaming accumulator now keys by tool_call.index (the positional counter assigned per chunk, always unique even for repeated tool names), so the duplicate-tool-call bug is fixed without fabricating IDs. Server-provided IDs are passed through as-is; when Ollama doesn't return one, id is None.

Hey @SyedShahmeerAli12 if this is the case that the ollama python SDK ignores the tool call ID then your changes here won't have any effect right? The tool call ID will always be None or am I missing something?

@SyedShahmeerAli12
Copy link
Copy Markdown
Contributor Author

SyedShahmeerAli12 commented May 20, 2026

The PR fixes two things:

  1. The actual bug the streaming accumulator was keyed by tool_name, so two calls to the same tool (e.g. two get_weather) collapsed into one. Now it keys by tool_call.index which is always unique. That fix has nothing to do with IDs.

  2. The ID cleanup the old code invented fake IDs (call_0, call_1). Since the SDK drops real ones anyway, those were just noise. Now id=None honestly when the server doesn't provide one and when the SDK eventually adds ID support, the pass-through will just work.

So yes, IDs are always None right now but the bug this PR fixes was never about IDs.

@SyedShahmeerAli12 SyedShahmeerAli12 force-pushed the fix/ollama-tool-call-ids branch from 43e8535 to a3f6035 Compare May 20, 2026 14:32
@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented May 21, 2026

@SyedShahmeerAli12 looking good and thanks for the additional tests!

Last comment would be to add async versions of the unit test test_handle_streaming_response_repeated_tool_calls and then it should be good to go.

@SyedShahmeerAli12
Copy link
Copy Markdown
Contributor Author

SyedShahmeerAli12 commented May 21, 2026

Added test_handle_streaming_response_async_repeated_tool_calls mirrors the sync test, feeds the same two-city chunks into _handle_streaming_response_async via an async generator, and asserts both tool calls are preserved with correct names and arguments.

Copy link
Copy Markdown
Contributor

@sjrl sjrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@sjrl sjrl merged commit c051029 into deepset-ai:main May 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration:ollama type:documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OllamaChatGenerator drops Ollama tool call IDs, breaking repeated same-tool calls

2 participants