feat(vertex): add native Gemini support to GeminiNativeClient (mirrors Anthropic Vertex pattern) - #21480
Closed
TAMdrew wants to merge 1 commit into
Closed
feat(vertex): add native Gemini support to GeminiNativeClient (mirrors Anthropic Vertex pattern)#21480TAMdrew wants to merge 1 commit into
TAMdrew wants to merge 1 commit into
Conversation
…s Anthropic Vertex pattern)
Hermes already routes Claude-on-Vertex through agent/anthropic_adapter.py
(":rawPredict" / ":streamRawPredict" with GCE bearer auth). This patch
adds the symmetric path for Gemini-on-Vertex: when a custom_provider is
pointed at the publisher endpoint
https://aiplatform.googleapis.com/v1/projects/<P>/locations/global/
publishers/google/models/<MODEL>:generateContent
GeminiNativeClient is now selected (instead of the OpenAI SDK) and uses a
GCE metadata bearer token instead of x-goog-api-key.
Without this gate the OpenAI SDK takes over, appends /chat/completions to
a URL ending in :generateContent, and every auxiliary request (compress,
title, vision, curator, web_extract, browser_automation) silently 404s —
the user-visible symptom is broken /compress, missing session titles, and
empty curator runs even though the main agent still succeeds (the main
agent uses a different code path that wasn't affected).
Changes
-------
agent/gemini_native_adapter.py (+138)
- is_native_gemini_base_url() now accepts aiplatform.googleapis.com URLs
that contain /publishers/google
- new is_vertex_gemini_base_url() helper for code that needs to branch
- _strip_vertex_model_suffix() removes /models/<NAME>:generateContent
(or :streamGenerateContent) at adapter init — the OpenAI SDK treats
base_url as a prefix and would otherwise produce malformed paths
- _fetch_gce_metadata_token() — minimal GCE metadata-server client,
same pattern as anthropic_adapter
- GeminiNativeClient.__init__ records ._is_vertex and strips the
suffix; ._headers() injects "Authorization: Bearer <token>" on
Vertex and drops any inherited x-goog-api-key (which would trigger
GCP's "Multiple authentication credentials received" 400)
agent/auxiliary_client.py (+27 −0)
- resolve_provider_client._wrap_if_needed() gains a Gemini-native gate
that runs FIRST — before the Codex/Anthropic/etc rewrap checks —
because Vertex Gemini URLs match no other adapter's predicate but
always need GeminiNativeClient
run_agent.py (+95 −14)
- main agent's client construction routes Vertex Gemini base URLs
through GeminiNativeClient (mirrors the auxiliary path)
Tests
-----
tests/agent/test_gemini_vertex_native.py (new, 11 tests)
- URL recognition: vertex publisher URLs match, GenLang still matches,
unrelated URLs (api.openai.com, vertex Anthropic) don't
- Suffix stripping: idempotent, handles :generateContent and
:streamGenerateContent
- Auth headers: Vertex injects bearer + drops x-goog-api-key;
GenLang keeps x-goog-api-key + no bearer
- End-to-end: resolve_provider_client(provider='custom', explicit_base_url=
<vertex>) returns a GeminiNativeClient (the regression that would
cause silent 404s without the gate)
All 11 new tests pass; no regressions in existing
test_gemini_native_adapter.py / test_gemini_*.py / test_run_agent
suite (the 2 pre-existing failures in test_concurrent_interrupt.py
fail identically on pristine main without this patch).
Validated against my live Hermes runtime since 2026-05-05; auxiliary
title generation correctly routes to the Vertex URL and aux compress /
vision / curator all work without 404s.
Refs Issue NousResearch#13484, NousResearch#12639. Complementary to PRs NousResearch#8427 / NousResearch#16010 (which
add a first-class "vertex" provider using the OpenAI-compatible
endpoint) — this PR keeps the existing custom_providers path working
when pointed at the native :generateContent endpoint, which gives
better multimodal + native tool support than the compat shim.
|
@TAMdrew anything left to do on this PR that we can help with? My company is eager to get hermes working with gemini+vertex |
1 task
Contributor
|
Superseded by #56363, which adds Google Vertex AI as a first-class Gemini provider on current That PR salvaged the earliest submission in this cluster (#8427 by @slawt) and preserved its authorship. Closing this as a duplicate — thanks for working on the same gap; the merged implementation covers the Gemini-via-Vertex use case. If you spot something it misses, please open a focused follow-up. |
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.
What does this PR do?
Adds Vertex AI support to the existing
GeminiNativeClientso users withcustom_providersconfigured against the publisher endpoint stop hitting silent 404s on every auxiliary call.This is the symmetric companion to
agent/anthropic_adapter.py, which already handles Claude-on-Vertex (:rawPredict/:streamRawPredictwith GCE bearer auth). Same approach, applied to Gemini.The bug this fixes
When you configure a
custom_providerpointing at the native Vertex publisher endpoint:…the OpenAI SDK takes over (because the URL doesn't match any existing adapter predicate), appends
/chat/completionsto a URL that already ends in:generateContent, and the request silently 404s. The main agent path happens to use a different code branch and still works, but every auxiliary call breaks:/compress→ "Compression summary failed: Error code: 404"vision_analyzeon Gemini → "Generation failed"web_extract/browser_automation→ silent failuresThe user-visible symptom is "everything mostly works but the side features are broken", which is hard to root-cause without reading the auxiliary client routing code.
Fix
Three small changes that mirror the existing Anthropic Vertex pattern:
agent/gemini_native_adapter.py(+138)is_native_gemini_base_url()now recognisesaiplatform.googleapis.com/.../publishers/googleURLs (it previously only matchedgenerativelanguage.googleapis.com)is_vertex_gemini_base_url()helper for code that needs to branch on Vertex specifically_strip_vertex_model_suffix()removes/models/<NAME>:generateContent(or:streamGenerateContent) at adapter init — the OpenAI SDK treatsbase_urlas a prefix and would otherwise produce malformed paths_fetch_gce_metadata_token()— minimal GCE metadata-server client, copy of the pattern inanthropic_adapterGeminiNativeClient.__init__records._is_vertexand strips the suffix;._headers()injectsAuthorization: Bearer <token>on Vertex and drops any inheritedx-goog-api-key(which would otherwise trigger GCP's400 Multiple authentication credentials received)agent/auxiliary_client.py(+27 −0)resolve_provider_client._wrap_if_needed()gains a Gemini-native gate that runs first — before the Codex/Anthropic/etc rewrap checks — because Vertex Gemini URLs match no other adapter's predicate but always needGeminiNativeClientrun_agent.py(+95 −14)GeminiNativeClient(mirrors the auxiliary path so all routes are consistent)Tests
tests/agent/test_gemini_vertex_native.py(new, 11 tests, all passing)::generateContentand:streamGenerateContentx-goog-api-key; GenLang keepsx-goog-api-key+ no bearerresolve_provider_client(provider='custom', explicit_base_url=<vertex>)returns aGeminiNativeClient(the regression that would cause silent 404s without the gate)The 2 pre-existing failures in
tests/run_agent/test_concurrent_interrupt.py(AttributeError: '_Stub' object has no attribute '_tool_guardrails') reproduce identically on pristinemainwithout this patch — they're unrelated to this change.Manual validation
I've been running this patch on my live Hermes runtime since 2026-05-05 against a real GCP project. Auxiliary title generation correctly routes to the Vertex URL with no 404 follow-up:
/compress,vision_analyze, curator, andweb_extractall work without 404s on Gemini-on-Vertex endpoints.Type of Change
custom_providerspointed at the native Vertex publisher endpoint)custom_providerspath, no new top-level provider added)Related Issues / PRs
custom_providers)vertexprovider that uses Vertex's OpenAI-compatible endpoint (/endpoints/openapi/chat/completions). This PR keeps the existingcustom_providerspath working when pointed at the native:generateContentendpoint, which has better multimodal support and native function-calling than the OpenAI compat shim. The two paths can coexist.:rawPredictinterceptor inagent/anthropic_adapter.py(already merged) — same shape, different model familyBackwards compatibility
is_native_gemini_base_url()retains its existing behaviour forgenerativelanguage.googleapis.comURLs (test asserts this)GeminiNativeClient.__init__for non-Vertex URLs is unchanged (_is_vertex=Falsebranch is a no-op)_wrap_if_needed()— the new Vertex Gemini gate runs first but only fires whenis_native_gemini_base_url()matches, so non-Gemini URLs flow through the original adapter chain unchangedcustom_providersmechanismurllib)Checklist
agent/anthropic_adapter.pypatterns for Vertex GCE auth)tests/agent/test_gemini_vertex_native.py, 11 tests)