ec17516 fix: native function calling for REST API consumers
📊 Changes
1 file changed (+70 additions, -7 deletions)
View changed files
📝backend/open_webui/main.py (+70 -7)
📄 Description
Two fixes for native function calling, especially for REST API consumers that bypass the frontend WebSocket session:
Pass through the function_calling value as-is instead of only checking for 'native' and falling back to 'default'. This respects values set via DEFAULT_MODEL_PARAMS or per-model params.
Add server-side tool-calling loop for API calls (no session_id). The native tool execution loop is driven by the frontend via WebSocket. REST API calls never enter that loop, so tool_calls are returned raw and tools are never executed. This adds a loop that executes tools and re-invokes the LLM until finish_reason != 'tool_calls' or CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES is reached.
Ref: #9435
Replaces PR: #23515 (closed - updated based on bot review feedback)
Pull Request Checklist
Target branch:dev
Description: See above and changelog below
Changelog: See below
Documentation: N/A - no new env vars or user-facing config; uses existing CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES
Dependencies: No new dependencies
Testing: Tested on a deployment environment (AWS ECS Fargate + Bedrock Access Gateway) with Claude models. Verified via REST API (/api/chat/completions) with both stream: true and stream: false. Tool calls are executed server-side and the LLM produces a final response incorporating tool results. Frontend WebSocket flow unaffected.
Agentic AI Code: AI-assisted with human review and manual testing on deployment environment
Code review: Self-reviewed; addressed both findings from automated review on #23515
Design & Architecture: No new settings; uses existing CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES
Git Hygiene: Single atomic commit rebased on dev
Title Prefix:fix:
Changelog Entry
Description
Fix native function calling for REST API consumers (/api/chat/completions) that don't use a WebSocket session
Fixed
function_calling parameter now passes through the configured value instead of only recognizing 'native' and falling back to 'default'
REST API calls with native function calling now execute tools server-side and re-invoke the LLM with tool results, matching the behavior of the frontend WebSocket flow
Falsy tool results (0, False, "") are preserved correctly via is not None check
Breaking Changes
None. Behavioral change is scoped to REST API requests with function_calling: native and tools - these previously returned raw tool_calls without execution.
Additional Information
Related discussion: #9435 ("The model does nothing with the result of a native tool call") - reported by 30+ users since Feb 2025
The server-side loop only activates when: no session_id (REST API), function_calling == 'native', and tools are present in metadata
Loop is bounded by existing CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES setting
The loop forces stream=False to parse tool_calls from JSON. After the loop, the final LLM call is only made when:
Tool results are pending (LLM hasn't seen them), OR
The caller requested streaming (loop response is JSON, caller expects SSE)
Non-streaming callers reuse the loop's response directly - no extra LLM call
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.
Note
Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
🔄 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/23581
**Author:** [@PedroFCM](https://github.com/PedroFCM)
**Created:** 4/10/2026
**Status:** ❌ Closed
**Base:** `dev` ← **Head:** `fix/native-tool-calling-api-loop`
---
### 📝 Commits (1)
- [`ec17516`](https://github.com/open-webui/open-webui/commit/ec175166c677bbad051cf4dc36c5be51541f92f4) fix: native function calling for REST API consumers
### 📊 Changes
**1 file changed** (+70 additions, -7 deletions)
<details>
<summary>View changed files</summary>
📝 `backend/open_webui/main.py` (+70 -7)
</details>
### 📄 Description
<!--
⚠️ CRITICAL CHECKS FOR CONTRIBUTORS (READ, DON'T DELETE) ⚠️
1. Target the `dev` branch. PRs targeting `main` will be automatically closed.
2. Do NOT delete the CLA section at the bottom. It is required for the bot to accept your PR.
-->
Two fixes for native function calling, especially for REST API consumers that bypass the frontend WebSocket session:
1. **Pass through the `function_calling` value as-is** instead of only checking for `'native'` and falling back to `'default'`. This respects values set via `DEFAULT_MODEL_PARAMS` or per-model params.
2. **Add server-side tool-calling loop for API calls** (no `session_id`). The native tool execution loop is driven by the frontend via WebSocket. REST API calls never enter that loop, so `tool_calls` are returned raw and tools are never executed. This adds a loop that executes tools and re-invokes the LLM until `finish_reason != 'tool_calls'` or `CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES` is reached.
Ref: #9435
Replaces PR: #23515 (closed - updated based on bot review feedback)
# Pull Request Checklist
- [x] **Target branch:** `dev`
- [x] **Description:** See above and changelog below
- [x] **Changelog:** See below
- [x] **Documentation:** N/A - no new env vars or user-facing config; uses existing `CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES`
- [x] **Dependencies:** No new dependencies
- [x] **Testing:** Tested on a deployment environment (AWS ECS Fargate + Bedrock Access Gateway) with Claude models. Verified via REST API (`/api/chat/completions`) with both `stream: true` and `stream: false`. Tool calls are executed server-side and the LLM produces a final response incorporating tool results. Frontend WebSocket flow unaffected.
- [x] **Agentic AI Code:** AI-assisted with human review and manual testing on deployment environment
- [x] **Code review:** Self-reviewed; addressed both findings from automated review on #23515
- [x] **Design & Architecture:** No new settings; uses existing `CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES`
- [x] **Git Hygiene:** Single atomic commit rebased on `dev`
- [x] **Title Prefix:** `fix:`
# Changelog Entry
### Description
- Fix native function calling for REST API consumers (`/api/chat/completions`) that don't use a WebSocket session
### Fixed
- `function_calling` parameter now passes through the configured value instead of only recognizing `'native'` and falling back to `'default'`
- REST API calls with native function calling now execute tools server-side and re-invoke the LLM with tool results, matching the behavior of the frontend WebSocket flow
- Falsy tool results (`0`, `False`, `""`) are preserved correctly via `is not None` check
### Breaking Changes
- None. Behavioral change is scoped to REST API requests with `function_calling: native` and tools - these previously returned raw `tool_calls` without execution.
---
### Additional Information
- Related discussion: #9435 ("The model does nothing with the result of a native tool call") - reported by 30+ users since Feb 2025
- The server-side loop only activates when: no `session_id` (REST API), `function_calling == 'native'`, and tools are present in metadata
- Loop is bounded by existing `CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES` setting
- The loop forces `stream=False` to parse tool_calls from JSON. After the loop, the final LLM call is only made when:
- Tool results are pending (LLM hasn't seen them), OR
- The caller requested streaming (loop response is JSON, caller expects SSE)
- Non-streaming callers reuse the loop's response directly - no extra LLM call
### Contributor License Agreement
<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.
Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->
- [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.
> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
---
<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/23581
Author: @PedroFCM
Created: 4/10/2026
Status: ❌ Closed
Base:
dev← Head:fix/native-tool-calling-api-loop📝 Commits (1)
ec17516fix: native function calling for REST API consumers📊 Changes
1 file changed (+70 additions, -7 deletions)
View changed files
📝
backend/open_webui/main.py(+70 -7)📄 Description
Two fixes for native function calling, especially for REST API consumers that bypass the frontend WebSocket session:
Pass through the
function_callingvalue as-is instead of only checking for'native'and falling back to'default'. This respects values set viaDEFAULT_MODEL_PARAMSor per-model params.Add server-side tool-calling loop for API calls (no
session_id). The native tool execution loop is driven by the frontend via WebSocket. REST API calls never enter that loop, sotool_callsare returned raw and tools are never executed. This adds a loop that executes tools and re-invokes the LLM untilfinish_reason != 'tool_calls'orCHAT_RESPONSE_MAX_TOOL_CALL_RETRIESis reached.Ref: #9435
Replaces PR: #23515 (closed - updated based on bot review feedback)
Pull Request Checklist
devCHAT_RESPONSE_MAX_TOOL_CALL_RETRIES/api/chat/completions) with bothstream: trueandstream: false. Tool calls are executed server-side and the LLM produces a final response incorporating tool results. Frontend WebSocket flow unaffected.CHAT_RESPONSE_MAX_TOOL_CALL_RETRIESdevfix:Changelog Entry
Description
/api/chat/completions) that don't use a WebSocket sessionFixed
function_callingparameter now passes through the configured value instead of only recognizing'native'and falling back to'default'0,False,"") are preserved correctly viais not NonecheckBreaking Changes
function_calling: nativeand tools - these previously returned rawtool_callswithout execution.Additional Information
session_id(REST API),function_calling == 'native', and tools are present in metadataCHAT_RESPONSE_MAX_TOOL_CALL_RETRIESsettingstream=Falseto parse tool_calls from JSON. After the loop, the final LLM call is only made when:Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.