[PR #21133] [CLOSED] fix: reconstruct tool_calls from history to prevent model hallucinations #64799

Closed
opened 2026-05-06 10:29:26 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/open-webui/pull/21133
Author: @Yambr
Created: 2/3/2026
Status: Closed

Base: mainHead: fix/tool-calls-reconstruction


📝 Commits (1)

  • 7f6e7c8 fix: reconstruct tool_calls from history to prevent model hallucinations

📊 Changes

2 files changed (+99 additions, -22 deletions)

View changed files

📝 backend/open_webui/utils/middleware.py (+94 -0)
📝 src/lib/utils/index.ts (+5 -22)

📄 Description

Summary

Fixes #20600 - Tool call results not decoded from HTML entities before sending to LLM

This PR addresses a critical bug where models (especially Qwen3) hallucinate/fake tool calls as text after 2-3 iterations of actual tool use.

Root Cause

  1. processDetails() converts <details type="tool_calls"> to plain text with HTML-escaped entities
  2. removeAllDetails() strips ALL <details> tags before sending to backend
  3. Model receives history without proper tool call structure and mimics the pattern

Solution

Frontend (src/lib/utils/index.ts):

  • removeAllDetails(): Add negative lookahead to preserve <details type="tool_calls"> tags
  • processDetails(): Simplify to only remove reasoning and code_interpreter details, leaving tool_calls intact

Backend (backend/open_webui/utils/middleware.py):

  • Add reconstruct_tool_messages() function to parse preserved <details type="tool_calls"> tags
  • Reconstruct proper OpenAI-compatible tool_calls field on assistant messages
  • Create tool role messages with results

How It Works

  1. Frontend preserves <details type="tool_calls" id="..." name="..." arguments="..." result="..."> in message content
  2. Backend parses these tags before sending to LLM
  3. Converts flat assistant messages to proper structure:
    • Assistant message with tool_calls array
    • Tool role messages with tool_call_id and result content

Model Compatibility

Model Support
Qwen3/Qwen3-VL Native (primary fix target)
GPT-4/GPT-4o Native
Claude Via LiteLLM
Gemini Native

Testing

  1. Enable tools for a model
  2. Trigger a tool call (e.g., web search)
  3. Send follow-up message in the same conversation
  4. Verify model makes REAL tool calls, not text imitations like:
    <tool_call>{"name": "web_search", "arguments": {...}}</tool_call>
    
  • #20600 - HTML entities encoding bug (this PR fixes)
  • #9435 - Native mode doesn't call model again after tool call (related)

🤖 Generated with Claude Code


🔄 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/21133 **Author:** [@Yambr](https://github.com/Yambr) **Created:** 2/3/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/tool-calls-reconstruction` --- ### 📝 Commits (1) - [`7f6e7c8`](https://github.com/open-webui/open-webui/commit/7f6e7c8fb1ad8dbd112bc63eea0ae0094f23d86a) fix: reconstruct tool_calls from history to prevent model hallucinations ### 📊 Changes **2 files changed** (+99 additions, -22 deletions) <details> <summary>View changed files</summary> 📝 `backend/open_webui/utils/middleware.py` (+94 -0) 📝 `src/lib/utils/index.ts` (+5 -22) </details> ### 📄 Description ## Summary Fixes #20600 - Tool call results not decoded from HTML entities before sending to LLM This PR addresses a critical bug where models (especially Qwen3) hallucinate/fake tool calls as text after 2-3 iterations of actual tool use. ### Root Cause 1. `processDetails()` converts `<details type="tool_calls">` to plain text with HTML-escaped entities 2. `removeAllDetails()` strips ALL `<details>` tags before sending to backend 3. Model receives history without proper tool call structure and mimics the pattern ### Solution **Frontend (`src/lib/utils/index.ts`):** - `removeAllDetails()`: Add negative lookahead to preserve `<details type="tool_calls">` tags - `processDetails()`: Simplify to only remove `reasoning` and `code_interpreter` details, leaving `tool_calls` intact **Backend (`backend/open_webui/utils/middleware.py`):** - Add `reconstruct_tool_messages()` function to parse preserved `<details type="tool_calls">` tags - Reconstruct proper OpenAI-compatible `tool_calls` field on assistant messages - Create `tool` role messages with results ### How It Works 1. Frontend preserves `<details type="tool_calls" id="..." name="..." arguments="..." result="...">` in message content 2. Backend parses these tags before sending to LLM 3. Converts flat assistant messages to proper structure: - Assistant message with `tool_calls` array - Tool role messages with `tool_call_id` and result content ### Model Compatibility | Model | Support | |-------|---------| | Qwen3/Qwen3-VL | Native (primary fix target) | | GPT-4/GPT-4o | Native | | Claude | Via LiteLLM | | Gemini | Native | ### Testing 1. Enable tools for a model 2. Trigger a tool call (e.g., web search) 3. Send follow-up message in the same conversation 4. Verify model makes REAL tool calls, not text imitations like: ``` <tool_call>{"name": "web_search", "arguments": {...}}</tool_call> ``` ### Related Issues - #20600 - HTML entities encoding bug (this PR fixes) - #9435 - Native mode doesn't call model again after tool call (related) --- 🤖 Generated with [Claude Code](https://claude.ai/code) --- <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-06 10:29:26 -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#64799