10ac911 fix: strip orphan tool_calls in convert_output_to_messages
📊 Changes
1 file changed (+22 additions, -1 deletions)
View changed files
📝backend/open_webui/utils/misc.py (+22 -1)
📄 Description
Pull Request Checklist
Linked Issue/Discussion:Closes #24758
Target branch:dev
Description: Concise description down below.
Changelog: Added at the bottom.
Documentation: N/A — backend logic fix, no new user-facing API or config.
Dependencies: N/A — no new dependencies.
Testing: Logic validated by code review; issue root cause confirmed.
Agentic AI Code: Human reviewed and manually verified.
Code review: Performed self-review.
Design & Architecture: Minimal, targeted fix; no new settings or state introduced.
Git Hygiene: Single atomic commit.
Title Prefix:fix
Description
When resuming a chat that previously triggered tool calls, the conversation can fail with a 400 error from strict LLM providers (AWS Bedrock, Anthropic). The root cause is in convert_output_to_messages() in misc.py:
output[] traversal:
'function_call' → accumulates in pending_tool_calls
'function_call_output' → flush_pending() → appends tool role message,
clears pending_tool_calls
...
final flush_pending() → if no function_call_output was ever encountered,
emits an assistant message with tool_calls but no
trailing tool role message
Strict providers enforce that every tool_use id has a matching tool_result block immediately after. If function_call_output is missing from stored history (e.g., KB state changed between turns), the orphaned tool_calls cause:
ValidationException: messages.N: tool_use ids were found without tool_result blocks
immediately after: tooluse_xxx, tooluse_yyy.
Each tool_use block must have a corresponding tool_result block in the next message.
Fix
After flush_pending() at the end of convert_output_to_messages, scan the produced messages and collect all tool_call_id values from tool role messages. Then walk the messages again and strip any tool_calls whose id has no match. If stripping leaves an assistant message with empty content and no tool_calls, drop it entirely. Well-formed sequences are unaffected since the check is a no-op when all ids match.
Root Cause
convert_output_to_messages assumes every function_call will be followed by a function_call_output before the function returns. When that assumption is violated (DB never wrote it, KB update cleared it, etc.), the final flush_pending() emits a malformed assistant message.
Validation
ruff format --check backend/open_webui/utils/misc.py
ruff check backend/open_webui/utils/misc.py
Changelog Entry
Fixed
convert_output_to_messages now strips orphan tool_calls that lack a matching tool role message, preventing 400 errors from strict providers (AWS Bedrock, Anthropic) when chat history is resumed with missing function_call_output entries.
Screenshots or Videos
Not applicable; backend logic fix, no UI change.
Contributor License Agreement
By submitting this pull request, I confirm that I have read and fully agree to the Contributor License Agreement (CLA), and I am providing my contributions under its terms.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/open-webui/open-webui/pull/24835
**Author:** [@kindredzhang](https://github.com/kindredzhang)
**Created:** 5/17/2026
**Status:** ❌ Closed
**Base:** `dev` ← **Head:** `fix/24758-orphan-tool-calls-v2`
---
### 📝 Commits (1)
- [`10ac911`](https://github.com/open-webui/open-webui/commit/10ac91136276062b5daa23123bb035e475c8f73e) fix: strip orphan tool_calls in convert_output_to_messages
### 📊 Changes
**1 file changed** (+22 additions, -1 deletions)
<details>
<summary>View changed files</summary>
📝 `backend/open_webui/utils/misc.py` (+22 -1)
</details>
### 📄 Description
# Pull Request Checklist
- [x] **Linked Issue/Discussion:** `Closes #24758`
- [x] **Target branch:** `dev`
- [x] **Description:** Concise description down below.
- [x] **Changelog:** Added at the bottom.
- [x] **Documentation:** N/A — backend logic fix, no new user-facing API or config.
- [x] **Dependencies:** N/A — no new dependencies.
- [x] **Testing:** Logic validated by code review; issue root cause confirmed.
- [x] **Agentic AI Code:** Human reviewed and manually verified.
- [x] **Code review:** Performed self-review.
- [x] **Design & Architecture:** Minimal, targeted fix; no new settings or state introduced.
- [x] **Git Hygiene:** Single atomic commit.
- [x] **Title Prefix:** `fix`
---
## Description
When resuming a chat that previously triggered tool calls, the conversation can fail with a 400 error from strict LLM providers (AWS Bedrock, Anthropic). The root cause is in `convert_output_to_messages()` in `misc.py`:
```
output[] traversal:
'function_call' → accumulates in pending_tool_calls
'function_call_output' → flush_pending() → appends tool role message,
clears pending_tool_calls
...
final flush_pending() → if no function_call_output was ever encountered,
emits an assistant message with tool_calls but no
trailing tool role message
```
Strict providers enforce that every `tool_use` id has a matching `tool_result` block immediately after. If `function_call_output` is missing from stored history (e.g., KB state changed between turns), the orphaned `tool_calls` cause:
```
ValidationException: messages.N: tool_use ids were found without tool_result blocks
immediately after: tooluse_xxx, tooluse_yyy.
Each tool_use block must have a corresponding tool_result block in the next message.
```
### Fix
After `flush_pending()` at the end of `convert_output_to_messages`, scan the produced messages and collect all `tool_call_id` values from tool role messages. Then walk the messages again and strip any `tool_calls` whose id has no match. If stripping leaves an assistant message with empty content and no `tool_calls`, drop it entirely. Well-formed sequences are unaffected since the check is a no-op when all ids match.
### Root Cause
`convert_output_to_messages` assumes every `function_call` will be followed by a `function_call_output` before the function returns. When that assumption is violated (DB never wrote it, KB update cleared it, etc.), the final `flush_pending()` emits a malformed assistant message.
### Validation
```bash
ruff format --check backend/open_webui/utils/misc.py
ruff check backend/open_webui/utils/misc.py
```
---
## Changelog Entry
### Fixed
- `convert_output_to_messages` now strips orphan `tool_calls` that lack a matching `tool` role message, preventing 400 errors from strict providers (AWS Bedrock, Anthropic) when chat history is resumed with missing `function_call_output` entries.
---
## Screenshots or Videos
Not applicable; backend logic fix, no UI change.
---
## Contributor License Agreement
- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24835
Author: @kindredzhang
Created: 5/17/2026
Status: ❌ Closed
Base:
dev← Head:fix/24758-orphan-tool-calls-v2📝 Commits (1)
10ac911fix: strip orphan tool_calls in convert_output_to_messages📊 Changes
1 file changed (+22 additions, -1 deletions)
View changed files
📝
backend/open_webui/utils/misc.py(+22 -1)📄 Description
Pull Request Checklist
Closes #24758devfixDescription
When resuming a chat that previously triggered tool calls, the conversation can fail with a 400 error from strict LLM providers (AWS Bedrock, Anthropic). The root cause is in
convert_output_to_messages()inmisc.py:Strict providers enforce that every
tool_useid has a matchingtool_resultblock immediately after. Iffunction_call_outputis missing from stored history (e.g., KB state changed between turns), the orphanedtool_callscause:Fix
After
flush_pending()at the end ofconvert_output_to_messages, scan the produced messages and collect alltool_call_idvalues from tool role messages. Then walk the messages again and strip anytool_callswhose id has no match. If stripping leaves an assistant message with empty content and notool_calls, drop it entirely. Well-formed sequences are unaffected since the check is a no-op when all ids match.Root Cause
convert_output_to_messagesassumes everyfunction_callwill be followed by afunction_call_outputbefore the function returns. When that assumption is violated (DB never wrote it, KB update cleared it, etc.), the finalflush_pending()emits a malformed assistant message.Validation
Changelog Entry
Fixed
convert_output_to_messagesnow strips orphantool_callsthat lack a matchingtoolrole message, preventing 400 errors from strict providers (AWS Bedrock, Anthropic) when chat history is resumed with missingfunction_call_outputentries.Screenshots or Videos
Not applicable; backend logic fix, no UI change.
Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.