mirror of
https://github.com/open-webui/open-webui.git
synced 2026-05-08 12:40:32 -05:00
[GH-ISSUE #21802] issue: chat_completion_tools_handler called with empty tools_dict, causing duplicate model execution
#35104
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @ashkenazzio on GitHub (Feb 23, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/21802
Check Existing Issues
Installation Method
Docker
Open WebUI Version
0.8.5 (commit
1ac3dd4)Ollama Version (if applicable)
No response
Operating System
NixOS
Browser (if applicable)
Zen Browser
Confirmation
README.md.Expected Behavior
Description
When a model has no tools configured (no MCP servers, no builtin tools, no function tools),
process_chat_payload()still callschat_completion_tools_handler()with an emptytools_dict. This sends a non-streaming request to the same model asking it to choose tools — effectively running the model twice for every user message.For fast LLM models (GPT-4o, Claude Haiku, etc.), this adds a few seconds of latency that may go unnoticed. For agent-style backends exposed as OpenAI-compatible endpoints (which can take minutes to process), this is catastrophic — the agent runs its entire pipeline twice, with the first run's output silently discarded.
Actual Behavior
Root Cause
In
backend/open_webui/utils/middleware.py, line ~2525:The
elseis at the same indentation asif tools_dict:, so it fires whentools_dictis empty (falsy) — not when function calling is non-native. The comment says "If the function calling is not native" but the code does the opposite: it runs when there are no tools at all.Inside
chat_completion_tools_handler(), the function builds a tool-calling prompt (with an empty tools list), sends it as a non-streaminggenerate_chat_completion()call to the model, waits for the full response, parses it for tool calls (finds none), and returns. The actual user request hasn't even been sent yet.Steps to Reproduce
Logs & Screenshots
Suggested Fix:
Guard the
elsebranch sochat_completion_tools_handleris only called whentools_dictis non-empty:This moves the
elseinside theif tools_dict:block, so it correctly means "tools exist but function calling is not native" rather than "no tools exist."Note: Commit
8f0658e("fix: payload tools handling") restructured this area but may not have addressed this specific case. Our testing confirms the bug is present in v0.8.5.Additional Information
No response
@tjbck commented on GitHub (Feb 23, 2026):
Investigating.
@tjbck commented on GitHub (Feb 23, 2026):
Addressed in dev.
@blakkd commented on GitHub (Feb 26, 2026):
Was driving me mad! I thought my config was corrupted. Nice to see the cause was identified 👍
@ashkenazzio commented on GitHub (Feb 26, 2026):
Yeah, same. I was chasing a ghost bug in my code for 3 hours or so 🥲