Skip to content

Let an agent's model name the Vertex AI location it is called in - #95

Merged
haruotsu merged 3 commits into
mainfrom
model-location
Oct 20, 2026
Merged

haruotsu merged 3 commits into
mainfrom
model-location

Conversation

@haruotsu

@haruotsu haruotsu commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Some Gemini models on Vertex AI are offered only in the global location (for example gemini-3-flash-preview and gemini-3.1-pro-preview); a regional endpoint such as us-central1 returns 404 for them. Agent Engine runs in a region, and gete had no way to declare an agent on such a model:

  • GOOGLE_CLOUD_LOCATION is reserved: Agent Engine sets it itself, and gete validate refuses it in runtime.agent_engine.env.
  • The runtime passed model to LlmAgent as a plain string, so ADK's Gemini built its client from the environment's location.
  • A full resource path (projects/<p>/locations/global/publishers/google/models/...) does not help either: the request still goes to the regional endpoint.

One reason to want these models: with gemini-2.5-*, a built-in tool such as url_context or google_search cannot be combined with function declarations in one request (400 Multiple tools are supported only when they are all search tools), while the Gemini 3 models accept the combination. So an agent that wants a builtin: tool next to mcp: / openapi: tools needs one of them.

What changes

  • model also takes an object naming the location to call the model in:

    model:
      id: gemini-3-flash-preview
      location: global

    A model called in the agent's own region is still written as the plain string, as before. This follows the connections entries, whose object form is used only when it selects something.

  • Schema. The object requires both id and location and accepts no other keys. location must look like a Vertex AI location (global, us, us-central1, ...), since it becomes part of the endpoint's host name and URL path. model is typed as string or object rather than a oneOf, so a malformed object is reported by the key at fault (model: 'location' is a required property) instead of as "not of type 'string'".

  • Runtime. The id is resolved through ADK's LLMRegistry, the same registry a plain model name goes through, and the location is handed to that class as client_kwargs={"enterprise": True, "location": location}. client_kwargs is a field on Gemini in every ADK release in the supported range (checked in 2.6.0, 2.7.0 and 2.8.0), and ADK passes it to google.genai.Client when it builds both its generate and its live clients, so no subclass overriding api_client is needed. enterprise=True is the flag ADK itself sets for Vertex AI resource paths and is available in the google-genai versions ADK 2.6 requires.

  • Only Gemini models. The location reaches the model only through the Gemini client. gete validate reports an object-form model whose id ADK does not resolve to Gemini (or a subclass), such as a Claude model on Vertex AI, and the runtime refuses it as well, for a deployment that skipped validate.

  • README describes the object form under agent.yaml, and notes that such a model is always called on Vertex AI, gete run included: a GOOGLE_API_KEY for the Gemini Developer API is not enough there.

Compatibility

Additive. Existing declarations with a string model build exactly the same agent.

An agent that uses the object form has to be deployed with a gete that includes this change, since the runtime inside the archive is what reads it.

Testing

  • Schema: the object form is accepted; an object without id or location, with an empty id, with an unknown key, or with a malformed location is refused, and the message names the key at fault.
  • Validate: a Gemini model with a location passes; a Claude model id, or an id ADK does not know, is reported as not a Gemini model.
  • Runtime: the object form builds a Gemini, and with GOOGLE_CLOUD_LOCATION=us-central1 in the environment, its client is a Vertex AI client for global. A non-Gemini id is refused.
  • pytest, ruff check, ruff format --check and mypy pass locally, except test_generated_file_is_valid_hcl_for_terraform_fmt, which fails the same way on main with the local Terraform version and is unrelated.

Some Gemini models are offered only in the global location, and Agent
Engine runs in a region it also writes into GOOGLE_CLOUD_LOCATION. A
declaration cannot override that variable, and a full resource path in
model still goes to the regional endpoint, so such a model could not be
declared at all.

model now also takes {id, location}. The runtime hands the location to
ADK's Gemini through client_kwargs, a field every ADK release in the
supported range reads when building its clients, so no subclass is
needed. A model called where the agent runs stays a plain string.

🤖 Generated with Claude Code
Under oneOf, a model object missing its location was reported as not
being a string, which says nothing about what to fix. One schema typed
as string or object lets each keyword apply to its own type, so the
message names the missing or unexpected key instead.

🤖 Generated with Claude Code
The location reaches the model only through the Gemini client, yet any
id was wrapped in Gemini. A model ADK serves with another class, such as
Claude on Vertex AI, passed validate and then called a publisher that
does not have it. The id is now resolved through ADK's own registry, the
one a plain name goes through: validate reports anything that is not a
Gemini, and the runtime refuses it too, for a deployment validate never
saw.

The runtime test no longer compares the client arguments, which fixed
how the location travels rather than where the call goes; the test that
checks the client's location covers that. README says such a model is
always called on Vertex AI, local runs included.

🤖 Generated with Claude Code
@haruotsu
haruotsu merged commit b2638b0 into main Oct 20, 2026
3 checks passed
@haruotsu
haruotsu deleted the model-location branch October 20, 2026 05:55
@github-actions github-actions Bot mentioned this pull request Oct 20, 2026
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