Skip to content

feat(gemini): thinking as Open WebUI's native, localized reasoning block (1.19.0) - #198

Merged
owndev merged 7 commits into
mainfrom
feature/gemini-localized-thinking
Oct 10, 2026
Merged

owndev merged 7 commits into
mainfrom
feature/gemini-localized-thinking

Conversation

@owndev

@owndev owndev commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Why

Since 1.18.0, Gemini's thinking looked different depending on the chat:

  • Tool rounds: shown as Open WebUI's own reasoning item, localized ("Gedanke für 5 Sekunden").
  • Normal answers: the pipe wrote its own English block <details><summary>Thought (5s)</summary> and showed the English status Thinking… <preview>. The duration also counted the answer time.

#160 (@swarnat) proposed instance-wide valves for translating those texts. It was closed in favour of this change: Open WebUI's frontend already renders and localizes <details type="reasoning"> blocks per user, so no valve is needed. Thanks to @swarnat for raising it.

Change (Google Gemini pipeline 1.18.0 → 1.19.0)

  • Native reasoning block. _thinking_details_block writes <details type="reasoning" done="true" duration="N">. Open WebUI shows "Thought for N seconds" in the user's language ("Nachgedacht für N Sekunden").
  • Live while thinking. In streamed chat answers the pipe shows a done="false" block: Open WebUI's "Thinking..." with a spinner, plus the thoughts so far when expanded.
    • It is sent through replace events when new thoughts arrive, at most every 0.4 s.
    • On the first answer part, one replace switches the block to done="true". The answer then continues as chat:message:delta.
    • The English Thinking… <preview> status is gone.
    • If round one ends with tool calls, the live block is cleared before Open WebUI's reasoning item takes over.
  • Duration is measured from the first thought to the first answer part. Before, it ran to the end of the stream. Non-streaming answers keep the request duration.
  • Search statuses. Google Search grounding now emits Open WebUI's own localized statuses:
    • web_search_queries_generated: "Suche..." with the query chips; each query is sent once, because Open WebUI's keyed list throws on duplicates.
    • web_search: "N Seiten durchsucht", with items and no urls, as Open WebUI's backend does.
  • Unchanged:
    • tool rounds and live rounds of the tool loop (reasoning_content / reasoning_details);
    • background tasks (no thinking block);
    • the image, video and error texts;
    • all valves;
    • the API path, apart from the block shape and the duration.
  • Docs: "Thinking Summaries in Conversation History", the Web search section and the README bullet.

Verification

Run Result
All suites, v0.11.4-slim, --strict-known (merged with main 2fc7bcc) 547 PASS, 0 FAIL, 0 KNOWN, no obsolete marker (1151 s)
gemini, v0.11.3-slim, --strict-known 165 PASS, 0 FAIL, 0 KNOWN
Meta-test (E2E_MOCK_FAULT=500) 57 PASS, 386 FAIL, 0 KNOWN
Smoke runner --dry-run-mock 15 PASS, 0 FAIL, 2 SKIP; the key in no output file
check_owui_api.sh, ruff 0.11.10 check + format clean
Pipeline without the query deduplication (--file) gemini.grounding.status FAIL (queries sent twice)
  • Mutation round: 15 mutants of the new behaviour, all killed. They cover:

    • the block attributes, the done flag and the throttle;
    • the switch, the duration end and the regex;
    • urls/items and the statuses;
    • the tool-round cleanup.
  • Against 1.18.0: 12 of the 32 checks in the affected groups fail.

  • Independent review: no blocking finding. The duplicate-query and docs findings are fixed in be4dab5.

  • Verified against the Open WebUI v0.11.4 sources:

    • the frontend parser (utils/marked/extension.ts) and Collapsible.svelte;
    • StatusItem.svelte and the de-DE translations;
    • the backend middleware status shapes;
    • which socket events are persisted.

    Collapsible.svelte of v0.9.0 has the same reasoning block, so required_open_webui_version stays 0.9.0.

Not verified yet

A real browser with a German UI and the real Gemini API (the maintainer tests before merge):

  1. gemini-2.5-flash streamed:

    • "Denke nach..." with a spinner while thinking, then "Nachgedacht für N Sekunden", where N is the thinking time only;
    • no English "Thinking…" status.
  2. Non-streaming (GOOGLE_STREAMING_ENABLED=false): the finished block only.

  3. A grounded answer:

    • "Suche..." with the queries, then "N Seiten durchsucht" with the list;
    • [n] citations and sources still there.

    Every site shows the same favicon, because Gemini's links are Google redirect URLs.

  4. Stop while thinking: "Gedanke" without a spinner; the next message works.

  5. An image model: the localized block above the image.

Supersedes #160.

🤖 Generated with Claude Code

owndev and others added 7 commits October 10, 2026 21:00
…ng block

google_gemini.py 1.19.0. Answers without tool calls (streaming and
non-streaming, API path) now carry the thinking summary as
<details type="reasoning" done="true" duration="N">, so Open WebUI renders
it as its own reasoning item and writes the title in each user's language
("Thought for N seconds", "Nachgedacht fuer N Sekunden"). The summary
"Thought (Ns)" stays in every block for API clients and other renderers,
and the #176 history regex keeps matching both the old plain <details>
blocks and the new shape. No new valve.

- In a chat stream the thoughts so far are shown as a live done="false"
  block through throttled replace events (every 0.4 s at most) instead of
  the English "Thinking... <preview>" status; the first answer part sends
  the finished block plus that part, then deltas as before. The thinking
  status and its close are gone.
- A streamed thinking lasts from the first thought to the first answer
  part (it used to include the answer time); non-streaming keeps the
  request duration.
- A round that ends with tool calls clears its live block before Open
  WebUI's output items take over; tool rounds and live mode are unchanged.
- Google Search grounding uses Open WebUI's own localized web search
  statuses: web_search_queries_generated with the queries, then
  web_search "Searched {{count}} sites" with items (no urls, which the
  frontend would count instead).

Supersedes #160 (thanks @swarnat).

+semver: minor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atuses

- api / browser / thinking checks assert the 1.19.0 block shape
  (type="reasoning", done="true", duration equal to the "Thought (Ns)"
  summary) instead of the literal "<details>"; include-off checks for
  "<details" so it cannot pass vacuously.
- thinking.strip keeps the legacy plain block; thinking.strip-reasoning
  replays a real done="true" answer and a stopped done="false" block.
- thinking.live / thinking.duration (mock trigger paced-thinking): throttled
  done="false" replace events, the switch to done="true" with all thoughts
  on the first answer part, deltas after it, no thinking status, and a
  duration up to the first answer part only.
- thinking.stop (slow-thinking): Stop while thinking saves the done="false"
  block, and the next turn sends neither the block nor its thoughts.
- thinking.tool-round: a first round ending with a tool call clears its
  live block; the saved turn has a reasoning item and no <details>.
- grounding.status: web_search_queries_generated with the queries, then
  web_search "Searched {{count}} sites" with items and no urls.
- toolsapi.unchanged matches the new block shape (one line).

+semver: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
image.browser-preview / image.browser-ga check that the non-streamed answer
of an image model starts with the <details type="reasoning" done="true">
block (duration = summary) followed by the text.

+semver: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…19.0

- gemini 155 checks (6 new: thinking.strip-reasoning, thinking.live,
  thinking.duration, thinking.stop, thinking.tool-round, grounding.status),
  all 537 PASS / 0 KNOWN on v0.11.4-slim in strict mode (main ecac3fb plus
  1.19.0); the gemini suite gives 155 / 0 on v0.11.3-slim too.
- gemini row: the thinking block, the live block, its duration, Stop while
  thinking, the tool round and the search statuses; mock triggers
  paced-thinking and slow-thinking.
- Timing of the full run (1147 s) and of the thinking group's browser
  scenarios (about 20 s); meta-test 57 PASS / 376 FAIL / 0 KNOWN; smoke dry
  run unchanged (15 PASS, 2 SKIP, 38 requests).
- With google_gemini.py 1.18.0, 12 of the 32 checks of the affected groups
  FAIL.

+semver: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
+semver: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	docs/testing.md
…pdates

Gemini can repeat a web search query across the grounding metadata of a
stream. Open WebUI keys the query chips of `web_search_queries_generated` by
their text, and a duplicate key throws in its status component, so the pipe
now removes duplicates. The Gemini mock repeats its query, and
`grounding.status` expects it once.

The docs said the live block updates "about every 0.4 seconds". It updates
when new thoughts arrive, at most every 0.4 s. The tool calling section now
names the `type="reasoning"` block for answers without tool calls.

Both come from the review of the 1.19.0 change.

+semver: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…7bcc

+semver: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@owndev
owndev merged commit 31a4e7d into main Oct 10, 2026
8 checks passed
@owndev
owndev deleted the feature/gemini-localized-thinking branch October 10, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant