[PR #24835] [CLOSED] fix: strip orphan tool_calls in convert_output_to_messages #131548

Closed
opened 2026-05-21 17:11:05 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/open-webui/pull/24835
Author: @kindredzhang
Created: 5/17/2026
Status: Closed

Base: devHead: fix/24758-orphan-tool-calls-v2


📝 Commits (1)

  • 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


🔄 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>
GiteaMirror added the pull-request label 2026-05-21 17:11:05 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#131548