Skip to content

fix(config): reasoning toggle for nested Gemini custom provider routes - #4165

Closed
b3nw wants to merge 1 commit into
nesquena:masterfrom
b3nw:fix/reasoning-custom-providers
Closed

b3nw wants to merge 1 commit into
nesquena:masterfrom
b3nw:fix/reasoning-custom-providers

Conversation

@b3nw

@b3nw b3nw commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

fix: reasoning toggle for custom provider nested Gemini routes (vertex/, gemini_cli/)

Related context: Follow-up to closed PR #3431 (always-show reasoning chip). Direction in this comment: keep the selector only when the model actually supports reasoning — improve resolve_model_reasoning_efforts detection for custom providers and aggregator-shaped ids, not a default-off chip on every model. This PR is scoped to that detection fix for nested Gemini routes (vertex/, gemini_cli/).

Thinking Path

  • Custom OpenAI-compatible aggregators often list Gemini as nested routes (vertex/gemini-…, gemini_cli/gemini-…) rather than OpenRouter-style google/gemini-*.
  • The WebUI shows the reasoning/thinking effort chip only when resolve_model_reasoning_efforts() returns a non-empty list; PR fix: generalized reasoning capability heuristics (#3377) #3379 fixed bare and dot-separated ids but not these Gemini route prefixes. That aligns with the feat: always show reasoning effort selector, default off for unrecognized models (#3377) #3431 maintainer feedback: fix false negatives in capability detection, not universal chip visibility.
  • Named custom:* providers often lack authoritative models_dev capability mapping, so the heuristic fallback in api/config.py must recognize these gateway shapes.
  • Embedding and image-preview Gemini routes under the same prefixes must stay hidden; an early deny in resolve_model_reasoning_efforts covers registry false positives.

What Changed

  • api/config.py
    • Added _nested_gateway_route_reasoning() for vertex/gemini- and gemini_cli/gemini-, wired into _heuristic_reasoning_efforts().
    • Added _nested_route_reasoning_denied() and an early return in resolve_model_reasoning_efforts() for embedding / image / imagine Gemini routes.
  • tests/test_custom_provider_bare_model_reasoning.py
    • Parametrized positive/negative cases for vertex/ and gemini_cli/ via custom:newapi.

Why It Matters

Users on custom gateways lose the reasoning effort control for capable Gemini models when the catalog uses vertex/ or gemini_cli/ prefixes. This restores the toggle without affecting embeddings or image-only routes.

Verification

  • Targeted checks: vertex/gemini-3.1-pro-preview, gemini_cli/gemini-3-pro-preview → non-empty efforts; vertex/gemini-embedding-001, vertex/gemini-3-pro-image-preview[].
  • Manual UAT on a named custom provider profile (nested Gemini route ids) — passed.
  • Recommended before merge: pytest tests/test_custom_provider_bare_model_reasoning.py -v --timeout=60 and python3 scripts/ruff_lint.py --diff upstream/master (CI runs full matrix).

Risks / Follow-ups

  • Other nested Gemini gateway prefixes may need the same pattern if catalogs diverge further.
  • google/gemini-3* on custom providers still depends on existing google/gemini-2 prefix rules unless ids use vertex/ / gemini_cli/.
  • No UI/CSS change.

Model Used

  • Implementation & PR text: xAI grok-composer-2.5-fast (Hermes WebUI developer session).
  • Read-only code review: Google Antigravity CLI, model gemini-3.5-flash-high

Recognize vertex/gemini- and gemini_cli/gemini- model ids in heuristic
reasoning resolution. Deny embedding and image-preview routes before
models_dev fallback. Grok uses existing x-ai/ slash-prefix list.

Regression tests for custom:newapi; CHANGELOG [Unreleased].
@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends the resolve_model_reasoning_efforts heuristic in api/config.py to recognise nested Gemini gateway routes (vertex/gemini-…, gemini_cli/gemini-…) used by custom OpenAI-compatible aggregators, restoring the reasoning-effort toggle for capable models while keeping embedding and image-only routes excluded.

  • Adds _nested_gateway_route_reasoning() wired into the heuristic fallback and _nested_route_reasoning_denied() as an early guard against registry false positives — both share the same prefix/exclusion logic but are defined separately.
  • Adds parametrized tests for positive and negative cases via custom:newapi.

Confidence Score: 4/5

Safe to merge; the new code paths are additive and well-scoped, and the current implementation is internally consistent.

Both helpers reproduce the same embedding/image/imagine exclusion in separate function bodies. If future maintenance adds a new exclusion pattern to one but not the other, the early-deny guard that protects against registry false positives silently stops working. The test suite also leaves the gemini_cli/ deny branch uncovered. Neither is a current breakage, but the coupling and coverage gap are worth addressing before the duplication grows.

api/config.py — the two new helper functions share exclusion logic defined independently; tests/test_custom_provider_bare_model_reasoning.py — negative parametrization does not exercise the gemini_cli/ deny path.

Important Files Changed

Filename Overview
api/config.py Adds two new helper functions (_nested_route_reasoning_denied, _nested_gateway_route_reasoning) that handle vertex/ and gemini_cli/ prefixed model IDs; duplication of exclusion criteria between the two is a maintenance risk.
tests/test_custom_provider_bare_model_reasoning.py New parametrized positive and negative test cases for nested Gemini gateway routes; negative cases only cover the vertex/ prefix, leaving the gemini_cli/ branch untested for the deny path.
CHANGELOG.md CHANGELOG entry added under [Unreleased] for the nested Gemini gateway route fix; no issues.

Reviews (1): Last reviewed commit: "fix(config): reasoning toggle for nested..." | Re-trigger Greptile

Comment thread api/config.py
Comment on lines +2485 to +2513
def _nested_route_reasoning_denied(model: str) -> bool:
"""Hard deny for nested Gemini gateway routes that must never show a reasoning toggle."""
lower = str(model or "").strip().lower()
if not lower:
return False
for prefix in ("vertex/gemini-", "gemini_cli/gemini-"):
if lower.startswith(prefix):
tail = lower[len(prefix) :]
if tail.startswith("embedding") or "image" in tail or "imagine" in tail:
return True
return False


def _nested_gateway_route_reasoning(model: str) -> bool:
"""Recognize nested ``vertex/gemini-`` and ``gemini_cli/gemini-`` routes on custom providers.

The slash-prefix heuristic list includes ``google/gemini-2`` but not gateway-prefixed
Gemini ids, so capable models behind custom aggregators stayed hidden.
"""
lower = str(model or "").strip().lower()
if not lower:
return False
for prefix in ("vertex/gemini-", "gemini_cli/gemini-"):
if lower.startswith(prefix):
tail = lower[len(prefix) :]
if tail.startswith("embedding") or "image" in tail or "imagine" in tail:
return False
return True
return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicated exclusion criteria between the two helpers

_nested_route_reasoning_denied and _nested_gateway_route_reasoning each independently define the same embedding/image/imagine exclusion logic. If a new exclusion pattern (e.g. "audio") is added to _nested_gateway_route_reasoning but not to _nested_route_reasoning_denied, the early-deny guard in resolve_model_reasoning_efforts — which exists specifically to override registry false positives from _models_dev_reasoning_efforts — would silently fail to fire for that new pattern. Extracting the exclusion predicate into a single shared helper (e.g. _is_gemini_non_reasoning_tail(tail)) called by both functions would remove the coupling.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +272 to +278
@pytest.mark.parametrize(
"model_id",
[
"vertex/gemini-embedding-001",
"vertex/gemini-3-pro-image-preview",
],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The negative parametrization only exercises the vertex/ branch of _nested_route_reasoning_denied; the gemini_cli/ branch is never exercised in the deny path. A regression that accidentally removed "gemini_cli/gemini-" from the deny loop would not be caught by these tests.

Suggested change
@pytest.mark.parametrize(
"model_id",
[
"vertex/gemini-embedding-001",
"vertex/gemini-3-pro-image-preview",
],
)
@pytest.mark.parametrize(
"model_id",
[
"vertex/gemini-embedding-001",
"vertex/gemini-3-pro-image-preview",
"gemini_cli/gemini-embedding-001",
"gemini_cli/gemini-3-pro-image-preview",
],
)

nesquena-hermes added a commit that referenced this pull request Jun 14, 2026
Release NU (v0.51.408): reasoning selector for nested Gemini custom-provider routes (#4165)
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
Release NU (v0.51.408): reasoning selector for nested Gemini custom-provider routes (nesquena#4165)
@claw-io
claw-io deleted the fix/reasoning-custom-providers branch July 6, 2026 02:31
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