Repository navigation
feat(google): native tool calling through Open WebUI's tool loop (1.18.0) - #193
Merged
Merged
Conversation
Gemini 1.17.0 handed Open WebUI's tool callables to google-genai's automatic function calling, which fails on Open WebUI 0.10+ (string annotations of workspace tools, MCP/OpenAPI callables all named tool_function, direct tools without a callable) and ignored API clients' tools. 1.18.0 declares the tools from body["tools"] (name mapping, JSON Schema clean-up, AFC disabled) and ends the round when Gemini calls a function: the calls go back as OpenAI tool_calls with the thought signatures as reasoning_details, Open WebUI runs the tools (built-in, workspace, MCP, OpenAPI, terminal, direct, approval) and calls the pipe again, and the history is converted back into function_call / function_response contents. API clients get tool_calls in stream and non-stream answers. Gemini 3 on the Gemini API sends Search grounding together with functions; elsewhere grounding takes precedence. Requires google-genai>=1.68.0. +semver: minor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gemini 1.18.0 moves tool calls into Open WebUI's tool loop; these groups pin that contract down and show what 1.17.0 does instead. New groups gemini.tools (browser path: built-in, workspace, OpenAPI, MCP and direct tools, approval, rounds, signatures across turns, grounding with tools) and gemini.toolsapi (API clients: tool_calls, finish_reason, history, tool_choice, name mapping, schemas). The Gemini mock answers a MOCKTOOLS directive with function calls, validates call/response pairing and signatures like the API and records what the pipe sent; tool server mocks (OpenAPI :9111, MCP :9112) are outside E2E_MOCK_FAULT. The browser harness now sends the Socket.IO namespace sid, heartbeats and answers execute:tool. Nine gemini-tools-* known bugs of 1.17.0, gated on 1.18.0, carry mock_ok=True evidence so the meta-test stays at 0 KNOWN. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tive-tool-calling +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups for native tool calling (1.18.0, unreleased): - the stored model content of a grounding + tool round (toolCall / toolResponse parts) is replayed only within its own turn and never on Vertex AI; later turns are rebuilt from the function calls and their signatures, so a message with web search switched off (or a switch to Vertex, where google-genai raises for tool_call parts) keeps working - tool names are checked with fullmatch: "$" let a name with a trailing newline through unchanged, and Gemini rejects the whole request - numeric tool call ids and a tool call "function" that is not an object no longer abort the request - docs: offline Open WebUI 0.9.0-0.11.3 cannot install google-genai>=1.68.0 and switches the function off on save; how to avoid it +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mutation review left 23 mutants of google_gemini.py alive. New or tightened checks for each: response order of parallel calls, no placeholder signature on Gemini 2.x, keyword-named properties and numeric exclusiveMinimum, MALFORMED_FUNCTION_CALL / UNEXPECTED_TOOL_CALL on both API paths, the stored-content item next to the signature item, the sources of both grounding rounds (the mock now cites a separate tool-round source), text answers to API requests with tools, GOOGLE_STREAMING_ENABLED=false on both paths, thoughts of a non-streamed tool round, no reasoning_content for API clients, older-turn edge cases, the default_api. prefix, provider_specific_fields, tool_choice with an undeclared name and with grounding, and a tool result with an image (workspace tool make_image). Checks for the review fixes: the next turn without web search after a grounding round, a tool name with a trailing newline (mock name rules use fullmatch too) and numeric ids / a non-object function. The new checks that fail on 1.17.0 carry the matching gemini-tools-* marker, so main stays at 0 FAIL until the merge; baseline counts in CLAUDE.md, README and docs/testing.md describe the branch. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Harness and fix land in the same PR, so the nine gemini-tools-* markers (gated on google_gemini.py 1.18.0) would only add "drop marker" noise after the merge. Remove them with their known= tags, the known= / since= plumbing of the tool groups' Ctx.check and harness/known_gemini.py with its re-export in known.py. The 42 checks stay and simply have to pass. CLAUDE.md and README.md take the check-count lines of #190 (b1c393a); docs/testing.md loses the 1.17.0 column and the marker table. Evidence of what failed on 1.17.0 (main 4d09a55, v0.11.4-slim, --ref origin/main --only 'gemini.(tools|toolsapi)': 11 PASS / 0 FAIL / 42 KNOWN): - gemini-tools-afc (20): tools.builtin, builtin-nonstream, multiturn, multiturn-other-model, multiturn-back, parallel, parallel-split, rounds, rounds-nosig, text-before, thinking, thinking-off, image-result, approval, approval-reject, approval-parallel, unknown, grounding3-next-turn, nostream, log - gemini-tools-annotations (1): tools.workspace (gemini-tools-afc on v0.11.3-slim, where google-genai 1.66.0 stops after the calls) - gemini-tools-duplicate (1): tools.openapi - gemini-tools-mcp (1): tools.mcp - gemini-tools-direct (1): tools.direct - gemini-tools-api (9): toolsapi.stream, nonstream, nostream, tool-choice, tool-choice-grounding, names, default-api, schema, noid - gemini-tools-history (5): toolsapi.continuation, continuation-psf, continuation-nosig, history-edge, odd-shapes - gemini-tools-grounding (2): tools.grounding3, grounding25 - gemini-tools-malformed (2): tools.malformed, toolsapi.malformed After the drop, with 1.18.0: all suites on v0.11.4-slim 339 PASS / 0 FAIL / 1 KNOWN (infomaniak-name-prefix), normal and --strict-known, no obsolete markers; gemini on v0.11.3-slim 129 / 0 / 0 in both modes; meta-test (main 3ff6cf9, E2E_MOCK_FAULT=500) 39 PASS / 241 FAIL / 0 KNOWN. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mock-based groups cannot confirm what the Gemini API accepts. tests/e2e/smoke/gemini-tools.sh runs one scenario per spec risk R1-R15 against generativelanguage.googleapis.com through a recording pass-through proxy, with bounded retries, a request estimate, evidence per risk and key redaction; --dry-run-mock runs the same scenarios against the e2e mock. run.sh can now be sourced for its container helpers, and the browser session no longer lets engine.io take over SIGINT, which hid interrupted runs. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main brought the Azure 2.8.1 On Your Data retirement warning and its four oyd.notice.* checks (#190). docs/testing.md keeps this branch's gemini row, the Native tool calling section and the smoke section, takes main's azure row, and sets the counts from a run of the merged tree on v0.11.4-slim in strict known mode: gemini 129, azure 75, n8n 51, infomaniak 32, filters 57 = 344 checks, 343 PASS / 1 KNOWN. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… smoke runner A bad GOOGLE_API_KEY failed the model listing, so the setup error only said that both models were missing. It now lists the proxy's failing non-generate requests (e.g. "GET /v1alpha/models http=400 x4: API key not valid"), in the output and in summary.md, still with exit code 2. The printed request count was only an estimate. The proxy now enforces a hard cap (--max-requests, default twice the printed maximum): a generate request beyond it is answered with HTTP 429 locally and never forwarded, the scenario is not retried, the remaining risks are SKIP, and summary.md / smoke.json report budget_hit with a BUDGET FAIL row. The start line labels the number as an estimate and prints the cap. The Vertex credentials file holds secrets too: refresh_token, client_secret, private_key_id and the base64 lines of private_key are now part of the runner's SECRETS check (server log, mocks output, runner output) and of the host scan, which extracts the same values without a JSON parser. With --keep the script says that the container holds the file in plain text. R1 counts make_image in the workspace group (27 built-ins + 8 tools declared). docs/testing.md also gets the meta-test count of the merged suites (42 PASS / 242 FAIL / 0 KNOWN). +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SECRETS row was built before smoke.json, upstream.jsonl and summary.md were redacted, so a secret found only in those files never failed the run. The files are now rendered and redacted first; a hit marks SECRETS as FAIL and the files are rendered again. The host-side Vertex secret extraction also strips JSON-escaped \r, so a PEM key with CRLF line endings yields the same values as the runner. The docs say that a budget or bad-key run also fails the LOG row. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since 1.15.2 a generated image is attached to the answer only as an Open WebUI file. Open WebUI does not pass the files of assistant messages to a pipe, so an edit request in the same chat reached Gemini as text only and Nano Banana drew a new, unrelated image (#194). For a saved chat of the requesting user the image path now loads the message chain from Open WebUI's database (get_messages_map_by_chat_id and get_message_list, like Open WebUI's load_messages_from_db, both available since 0.9.0) and sends the image files of earlier user and assistant messages, plus markdown image links, as history images. API clients, temporary and channel chats and errors keep the request's messages; a guided regeneration keeps the images of the regenerated message. Earlier model turns are not replayed, so no thought signatures are needed. IMAGE_HISTORY_MAX_REFERENCES now drops the oldest history images first and never the current message's images, so the image being edited stays in a long chat. Open WebUI files are read only for their owner or an admin: until now any file id in a message was read. The new e2e group gemini.imgedit covers the two-turn edit on gemini-3.1-flash-image and gemini-nano-banana-2.1, an upload, guided regeneration, the limit, temporary chats and foreign file ids. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…history The review of the #194 fix found that the saved-chat image history went wrong in a few places: - Open WebUI copies the chat's metadata into its background tasks, so the follow-up task of an image model got the chat's images. Tasks now keep using the request's messages. - Guided regeneration was detected by comparing texts, so a user message with only an image, or with a text contained in the guidance, lost its images. It is now detected from the message structure: Open WebUI adds the guidance as one more user message after the saved one. - Deduplication kept the oldest copy of an image and did not compare the history with the current message, so an image attached again was sent twice or dropped as old. The history is now read newest first, each image once, and not again when the current message has it. - Every earlier image was read and re-encoded on the event loop before the limit applied. Only the images that fit are read now, and decoding runs in a worker thread. - The refusal warning for another user's file no longer logs its id, and the docs say that Open WebUI 0.9.0 to 0.9.5 itself puts any file id named in a user message into the request. The imgedit tests now also cover the follow-up task, guided regeneration of an image-only message, another image per turn (a new mock trigger), the default function calling mode, older saved image forms, a database error, an admin in another user's chat, IMAGE_DEDUP_HISTORY=false and file access on the Veo path; images.history checks the cap on current images. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main brought Azure AI Foundry 3.0.0 with the pipeline's own Azure AI Search retrieval (#195): mocks/mock_search.py on 127.0.0.1:9106, the managed identity variables in run.sh and the azure rag group. The merge has two bases (3ff6cf9, 7ba3569); the Azure files, which this branch never changed, are taken from main. config.py and serve_all.py keep search 9106 next to tools 9111 and MCP 9112. BrowserSession.chat() keeps tool_ids, tool_servers, extra_body, until and user_files next to main's files and extra; parse_sse() collects the tool calling fields and main's after_done counters. docs/testing.md keeps the gemini row, the native tool calling section and the smoke section, takes main's azure row and rag paragraphs, and combines the counts of both sides (re-measured after the next merge). +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/gemini-native-tool-calling Brings Time Token Tracker 2.7.0 with the Logs Ingestion API (#191), merged with main 4b80e24, and its filters suite (mock_la.py with the managed identity endpoints on 127.0.0.1/127.0.0.2:9107). config.py keeps search 9106 with #191's comment on 9105/9107 next to tools 9111 and MCP 9112. docs/testing.md keeps the native tool calling section, takes #191's filters row, PROBE_ENV / PROBE_LOG, mock_la setup and output file rows, and combines the counts (filters 101, total 531); the layout lists :9106, :9111, :9112 and mock_la's :443, :9105, :9107. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ogs Ingestion API Measured on the merged tree (main 4b80e24 + #191 + #193): 531 checks, 530 PASS / 1 KNOWN on v0.11.4-slim (normal and strict known mode) and on v0.11.3-slim, about 19 minutes per full run. The sample output, the timing paragraph, the counts header and the meta-test row (57 PASS / 370 FAIL / 0 KNOWN with main's files) now say so. +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s fail fast +semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
+semver: none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Native tool calling in the Gemini pipeline was broken on Open WebUI 0.10+ (#169, #155):
__tools__callables to google-genai's automatic function calling (AFC), which runs the tools inside the pipe:callable(KeyError: 'callable');__signature__(AttributeError);tool_function(Duplicate function declaration);from __future__ import annotations, so workspace tools carry string annotations that AFC cannot coerce.toolsgot notool_callsback. Tool messages in the history were turned into user text.Change (Google Gemini 1.17.0 → 1.18.0)
body["tools"], the OpenAI tool specs Open WebUI builds for every tool type, asFunctionDeclaration(parameters_json_schema=…)with a sanitised schema. Names Gemini does not accept get a stable hash suffix and are mapped back.AutomaticFunctionCallingConfig(disable=True)everywhere.tool_calls. On the browser path, Open WebUI's tool loop executes them and calls the pipe again withassistant.tool_calls+role:"tool"messages. That covers built-in, workspace Python, MCP, OpenAPI, terminal and direct tools, plus tool approval. Browserstream=falseanswers a tool round as a stream so the loop can run.reasoning_detailsand are restored per call id in later rounds and turns. When a client does not echo them, the documented placeholder is used, only for the current turn.function_call/function_responseparts (responses in call order,{"error": …}for Open WebUI's error results). Unmatched calls and results are dropped with a WARNING. Open WebUI's tool-image message is handled.tool_callsstreamed and non-streamed withfinish_reason: "tool_calls", including through the openai SDK's stream reader.tool_choiceis mapped (none,required, a named function);parallel_tool_callsis ignored.include_server_side_tool_invocations. Gemini 2.x and Vertex send grounding only, with no declarations for that request. Image models, background tasks and Legacy mode send no tools.MALFORMED_FUNCTION_CALL/UNEXPECTED_TOOL_CALLgive a clear message instead of an empty answer.google-genai>=1.68.0, google-genai<3, the first version with server-side tool invocations. The docs explain what to do on offline Open WebUI 0.9.0–0.11.3, where pip cannot upgrade the bundled 1.66.0.docs/google-gemini-integration.md, and README.E2E
New groups:
gemini.tools: the browser path through Open WebUI's real tool loop;gemini.toolsapi: API clients.With them the gemini suite grows from 81 to 129 checks. On 1.17.0, 42 of the 48 tool checks fail; the commit body of 04f8a86 lists them per root cause.
New mocks: the
MOCKTOOLS:directive in the Gemini mock, which emulates Gemini's 400 validations for pairing, signatures and names;mock_tools.py(an OpenAPI server and an MCP server); a workspace tool.Harness fix: the browser session now uses the Socket.IO namespace sid and sends a heartbeat. Before, direct tools and
__event_call__failed with "Client session disconnected".Real-API smoke runner (manual, needs a key):
GOOGLE_API_KEY=… tests/e2e/smoke/gemini-tools.sh. It runs one scenario per spec risk R1–R15 through the real tool loop and reports PASS, FAIL or SKIP per risk. Retries are bounded, there is a hard request cap, and the key never appears in any output.Also fixed here: image edit loses the previous image (#194)
Since 1.15.2, a generated image in a browser chat is attached to the answer only as an Open WebUI file. Open WebUI 0.11.x never passes files of earlier assistant messages to a pipe. An edit request ("change the name on the certificate") therefore reached Gemini as text only, and Gemini drew an unrelated image. 1.17.0 and the earlier 1.18.0 state of this branch behaved the same way.
load_messages_from_dbdoes. It sends the images of earlier user and assistant messages as history images: file ids,/api/v1/files/<id>/content,data:URLs and markdown links.IMAGE_HISTORY_MAX_REFERENCESthe oldest history images are dropped first and current images are never dropped. Duplicates keep their newest position, and an earlier image that is attached again to the current message is not sent twice. Guided regeneration keeps the images of the regenerated message._fetch_file_as_base64used to read any file id in a message. It now reads a file only for its owner or an admin, on every path including Veo image-to-video, and logs no foreign file id. Image decoding runs in a worker thread.gemini.imgedit(19 checks: two-turn edit on Nano Banana 2 andgemini-nano-banana-2.1, uploads, guided regeneration variants, limit, dedup, temporary chat, task, admin, foreign file ids on the browser and API path, a database error, Veo). 15 of the 19 fail on the pre-fix file. A first round of mutation testing left 10 mutants alive; the tests were extended, and all 18 mutants are killed now.Verification
--strict-known, v0.11.3-slim--strict-knowninfomaniak-name-prefix, no fix yet), 531 checks, each--strict-known(merged with main 3ff6cf9)infomaniak-name-prefix, no fix yet), 364 checks--strict-knownE2E_MOCK_FAULT=500), after the merges--dry-run-mockThe design is based on a probe of Open WebUI 0.11.4's tool loop: continuation shapes,
reasoning_detailsround trip, the requiredindex, merged rounds, usage summing. Three reviews followed: spec and API, Open WebUI regressions, and mutation testing. All 19 findings were fixed.Before release: run the real-API smoke test
Everything above ran against mocks. These are open risks R1–R15 that only Google's servers can answer:
skip_thought_signature_validatorplaceholder;Please run
GOOGLE_API_KEY=… tests/e2e/smoke/gemini-tools.sh; about 39 small requests, or add--retries 0.Closes #169
Closes #155
Closes #194
Supersedes #168 (thanks @ctroche99) and #159 (thanks @olivier-lacroix). Both addressed parts of this. This PR moves tool execution to Open WebUI's own tool loop instead of running tools inside the pipe.
🤖 Generated with Claude Code