Gemini Enterprise Agent Platform
Gemini Enterprise Agent Platform is the current name for Google Cloud's managed platform for Gemini and partner models, previously documented as Vertex AI. AISIX gives applications one OpenAI-compatible API for models hosted on the platform.
This configuration is for Agent Platform models that should use AISIX authentication, model allowlists, rate limits, and usage accounting. AISIX retains google-vertex as the provider identifier and vertex as the adapter identifier for compatibility. It authenticates to the Agent Platform API with a GCP OAuth2 bearer token and can mint that token from a service-account key.
Prerequisites
Before starting, prepare the following:
- One AISIX setup:
- For AISIX Enterprise or AISIX Cloud, an environment with an attached gateway and a write-scoped admin token. For AISIX Enterprise, follow the AISIX Enterprise getting-started guide. To request AISIX Cloud access, contact API7.
- For the open-source AISIX gateway, prepare either a local AISIX installation or the Docker setup from the AISIX OSS getting-started guide. Configure the gateway to load a declarative resources file.
- The Agent Platform API (
aiplatform.googleapis.com) enabled in the target GCP project. - An Agent Platform location supported by the target model and a service account with the Agent Platform User (
roles/aiplatform.user) role. The current Gemini example usesglobal. - The GCP project ID and Agent Platform model ID.
curlandjq.
Configure with AISIX Enterprise or AISIX Cloud
Export the control-plane connection details:
# AISIX_CP is the Admin API base URL; include /api and omit a trailing slash
# The local AISIX Enterprise quickstart uses http://localhost:8080/api
export AISIX_CP="YOUR_AISIX_CONTROL_PLANE_API_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
Create an Agent Platform provider key, model alias, and caller API key. The provider key stores the GCP project, location, and credential mode; the model selects the publisher model ID.
Create an Agent Platform Provider Key
Create the provider key with the GCP credential settings:
PROVIDER_KEY_ID=$(curl -sS -X POST "$AISIX_CP/provider_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "vertex-prod",
"provider": "google-vertex",
"api_base": "https://aiplatform.googleapis.com",
"config": {
"project": "my-gcp-project",
"region": "global",
"service_account_json": {
"type": "service_account",
"private_key": "-----BEGIN PRIVATE KEY-----\nYOUR_SERVICE_ACCOUNT_PRIVATE_KEY\n-----END PRIVATE KEY-----\n",
"client_email": "vertex-sa@my-gcp-project.iam.gserviceaccount.com",
"token_uri": "https://oauth2.googleapis.com/token"
}
},
"allowed_environments": ["'"$ENV_ID"'"]
}' | jq -r '.provider_key.id')
❶ provider selects the compatibility catalog entry for Agent Platform, which routes traffic through the vertex protocol adapter.
❷ The AISIX Cloud Admin API requires api_base for this platform provider. For the global location, use https://aiplatform.googleapis.com; https://global-aiplatform.googleapis.com is not the global endpoint. For a regional location, use https://<region>-aiplatform.googleapis.com, matching config.region. A proxy or private endpoint can replace either origin.
❸ config is the structured credential with project, region, and exactly one credential mode. The example uses service_account_json as a nested object. You can instead send it as a string holding the key file's JSON text; the control plane parses the string and stores the object. Omit api_key when the credential is supplied through config; a non-empty api_key is rejected for this provider.
The control plane checks service_account_json when you create the provider key and when you rotate its credential. The object must have type set to service_account, a PEM-encoded private_key that starts with -----BEGIN, and non-empty client_email and token_uri strings. A value that fails these checks, including a string that does not parse as a JSON object, is refused with 400, and the message names the field. A provider key saved with service_account_json as a string before this check existed was stored as the string, which the gateway cannot load, so every request on it failed; save its credential again to fix it.
Use service_account_json unless you already manage short-lived GCP access tokens yourself. AISIX signs a JWT, mints an OAuth token, caches it, and refreshes it before expiry. If you use access_token, you are responsible for refreshing it. The current adapter does not discover Application Default Credentials, metadata-server credentials, or Workload Identity Federation credentials.
Provider key secrets follow the credential-handling behavior described in Provider Keys.
The command captures the returned provider key ID for the model resource. allowed_environments lets the environment reference this key.
Create a Model
Map a caller-facing alias to the Agent Platform model ID:
MODEL_ID=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/models" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "gemini-prod",
"model_name": "gemini-3.6-flash",
"provider_key_id": "'"$PROVIDER_KEY_ID"'"
}' | jq -r '.model.id')
❶ model_name is the Agent Platform publisher model ID. The current Gemini 3.6 Flash example is generally available in global. Google documents custom temperature, top-P, and top-K values as ignored for this model, so omit temperature and top_p from calls through AISIX. Requests to this model must also end with a user message. Google rejects a GenerateContent request whose final turn has the model role, and AISIX maps a final OpenAI assistant message to that role.
❷ provider_key_id attaches the model to the Agent Platform credential captured in the previous step.
Other supported examples include Claude on Agent Platform, OpenAI-compatible MaaS models such as Llama, and partner publisher models from Mistral and AI21.
AISIX chooses the Agent Platform route from model_name:
| Model ID Family | How AISIX Sends It to Agent Platform |
|---|---|
Gemini models, such as gemini-* | Uses the Google Gemini publisher route. |
Claude models, such as claude-* | Uses the Anthropic publisher route with an Anthropic Messages body. |
| OpenAI-compatible MaaS models, such as Llama, DeepSeek, Qwen, GPT-OSS, MiniMax, Moonshot, or Z.ai | Uses Agent Platform's OpenAI-compatible chat-completions route. |
| Mistral and AI21 models | Uses the partner publisher route with an OpenAI-compatible body. |
Create a Caller API Key
Create the API key resource with access to the Agent Platform model alias. The server generates the key and returns the plaintext once:
AISIX_API_KEY=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/api_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "vertex-caller",
"allowed_models": ["'"$MODEL_ID"'"]
}' | jq -r '.plaintext')
allowed_models references the model by ID, so the caller can only use the Agent Platform alias. Store the plaintext key securely; read endpoints do not return it again.
Configure with AISIX OSS
For the open-source AISIX gateway, set adapter: vertex and place the project, region, and credential mode in the provider key's api_key value. The example below reads the structured credential from an environment variable:
export VERTEX_CREDENTIAL='{"project":"my-gcp-project","region":"global","service_account_json":{"type":"service_account","private_key":"-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----\n","client_email":"vertex-sa@my-gcp-project.iam.gserviceaccount.com","token_uri":"https://oauth2.googleapis.com/token"}}'
For a new gateway, use this complete resources file. For an existing gateway, merge the entries into its current file, preserving its other resources:
_format_version: "1"
provider_keys:
- display_name: vertex-prod
provider: google-vertex
adapter: vertex
api_key: ${VERTEX_CREDENTIAL}
api_base: https://aiplatform.googleapis.com
models:
- display_name: gemini-prod
provider: google-vertex
model_name: gemini-3.6-flash
provider_key: vertex-prod
api_keys:
- display_name: vertex-caller
key_env: VERTEX_CALLER_KEY
allowed_models:
- gemini-prod
The credential must contain project, region, and exactly one of access_token or service_account_json. In the resources file, service_account_json must be the key file's JSON object, not a string holding it. Use the same model_name values described above for other Agent Platform publishers.
For AISIX OSS, api_base can be omitted for a standard Agent Platform location. AISIX derives the origin from the credential's region: https://aiplatform.googleapis.com for global, https://aiplatform.us.rep.googleapis.com or https://aiplatform.eu.rep.googleapis.com for the us and eu multi-regions, and https://<region>-aiplatform.googleapis.com for a regional location such as us-central1. Set api_base explicitly for a proxy, private endpoint, or another endpoint override.
Export the caller key:
export VERTEX_CALLER_KEY="YOUR_CALLER_API_KEY"
If AISIX is installed locally, validate the file before loading it:
aisix validate --resources resources.yaml
After validation, start the gateway with the referenced environment variables in its process environment. Reload an existing gateway only if those variables are already available to the process; otherwise, restart it with the updated environment.
If you use Docker, adapt the validation and startup commands in the AISIX OSS getting-started guide. Mount this resources.yaml file and pass every environment variable it references with -e in both commands. After the resources load, set AISIX_API_KEY for the shared verification request:
export AISIX_API_KEY="$VERTEX_CALLER_KEY"
Verify the Provider Connection
Export the AISIX gateway origin:
# AISIX_PROXY has no trailing slash or endpoint path
# The local quickstarts use http://127.0.0.1:3000
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"
Send a chat-completions request through the AISIX proxy. The example uses Gemini:
curl -sS -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-prod",
"messages": [
{
"role": "user",
"content": "Say hello from Agent Platform."
}
]
}'
The gateway returns an OpenAI-compatible response with the caller-facing alias:
{
"object": "chat.completion",
"model": "gemini-prod",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello from Agent Platform!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 4,
"completion_tokens": 4,
"total_tokens": 8
}
}
Check Agent Platform logs, metrics, quota usage, or provider-side request records for the test request. If AISIX returns a token-minting or upstream authentication error, check the service-account key, location, Agent Platform API enablement, IAM role, and model access.
Endpoint and Content Support
Agent Platform exposes more capabilities than the current AISIX vertex adapter translates. The gateway behavior depends on both the caller route and the publisher family selected by model_name.
| Route | Behavior with a google-vertex provider key |
|---|---|
/v1/chat/completions | Supported, including streaming. The Gemini publisher translation accepts text, image, audio, video, and file parts as input, returns reasoning and generated images, and carries function calling. The Claude translation accepts images and PDF documents as input. Other partner publisher behavior depends on its rail. |
/v1/embeddings | Supported for Google-publisher text embedding models, such as gemini-embedding-001. AISIX sends text inputs to publishers/google/models/<model>:predict; partner and multimodal embedding formats are not supported. Token-ID input is refused with 400, because these models take text only. With gemini-embedding-001, send one input string per request. AISIX forwards an input array as one upstream request, but this model accepts only one input and can reject an array with multiple items. |
/v1/responses | Supported through the chat-based Responses bridge, not a native Agent Platform Responses endpoint. text.format becomes a chat response_format and reaches Gemini as described in Structured Output on Gemini. Other fields without a chat equivalent are ignored. |
/v1/messages | Claude models use Agent Platform's own Anthropic Messages API, and /v1/messages/count_tokens counts with its token-counting API; see Claude on Agent Platform. Other publishers are supported through translation to chat, and /v1/messages/count_tokens is unavailable for them. |
/v1/completions | Not supported by the vertex adapter. |
/v1/images/generations, /v1/audio/*, /v1/videos, and /v1/rerank | Not supported by the vertex adapter or these routes' provider rules, even though Agent Platform offers separate native media services. Gemini image models generate images through /v1/chat/completions instead; see Images on Gemini. |
/v1/files, /v1/batches, and /v1/fine_tuning/jobs | Not supported because the AISIX jobs surface does not accept the vertex adapter. |
/passthrough/google-vertex/* | Not a workaround for native Agent Platform APIs, even through a configured passthrough route. Passthrough routes bypass the adapter, so credential injection neither mints OAuth tokens from the service-account JSON nor extracts an access token from the structured credential. |
Gemini rejects a request with no user or assistant turn. When a request contains only system or developer messages, AISIX preserves them as system instructions and adds one blank user turn so Gemini can answer them.
For Gemini publisher models, AISIX serializes text parts, media parts, system instructions, the generation settings described in Generation Settings on Gemini, and function calling, as described in Function Calling on Gemini. Inline media is sent as inlineData. A gs:// or https:// URL is sent as fileData for Gemini to read when the caller's format or the extension in the URL names its media type; otherwise, and for an http:// URL, the gateway downloads it and sends it inline. See Media Inputs and URL Fetching. Function calls in a response are returned as tool calls, thought text as reasoning, and generated images as described in Images on Gemini. Other non-text response parts, such as audio, are not returned.
An error Agent Platform returns reaches the caller with its own message text; an error with no message is named by its status.
Generation Settings on Gemini
AISIX writes each OpenAI chat field that Gemini defines into its generationConfig field, with the value as sent, and Agent Platform answers for a value it refuses. See Request Parameter Handling for the general rule.
| Request field | Gemini field |
|---|---|
temperature, top_p | temperature, topP |
max_completion_tokens, or max_tokens when it is absent | maxOutputTokens |
stop | stopSequences; a single string is sent as a one-element array. A /v1/messages request's stop_sequences arrives here as stop. |
seed, presence_penalty, frequency_penalty | seed, presencePenalty, frequencyPenalty |
n | candidateCount |
logprobs, top_logprobs | responseLogprobs, logprobs |
top_k from a /v1/messages request | topK |
reasoning_effort | thinkingConfig, as described below. |
modalities | responseModalities, upper-cased, and only when the request sends it. See Images on Gemini. |
response_format | See Structured Output on Gemini. |
reasoning_effort becomes thinkingConfig.thinkingLevel on Gemini 3 and later, and on model names AISIX does not recognize as an older generation: minimal, low, medium, and high are sent as MINIMAL, LOW, MEDIUM, and HIGH. On Gemini 2.5, it becomes thinkingConfig.thinkingBudget, using the budgets Google publishes for its OpenAI-compatible endpoint: 1024 for minimal and low, 8192 for medium, and 24576 for high. none is a budget of 0 on Gemini 2.5 Flash and Flash-Lite. A /v1/messages request that enables thinking with budget_tokens sends that exact budget to Gemini 2.5 instead, so a budget above the model's ceiling receives Agent Platform's own 400, such as thinking_budget is out of range; supported values are integers from 1 to 24576 on gemini-2.5-flash. A tier the model has no setting for, such as max or xhigh, none on Gemini 2.5 Pro or on Gemini 3, and every tier on an older model, sets no budget or level. That alone does not make the request succeed: a caller that asked for reasoning still sends includeThoughts, which some models refuse, as described in Reasoning on Gemini.
service_tier flex or priority is sent as the X-Vertex-AI-LLM-Shared-Request-Type header, which selects Agent Platform's Flex PayGo or Priority PayGo. auto, default, and scale have no Agent Platform setting and are dropped. A value for that header from the provider key's request.default_headers or a forwarded caller header wins.
Every candidate Gemini returns becomes a choice, indexed by its candidate index, on streaming and non-streaming requests, and a candidate's logprobsResult is returned as that choice's logprobs. A prompt Gemini blocks, which returns promptFeedback.blockReason and no candidate, finishes as content_filter with content: null.
Other chat fields have no place in the Gemini request and are dropped. An assistant turn that carries only replayed reasoning, as reasoning_content or as reasoning content parts such as a Mistral thinking or Databricks reasoning part, is left out of contents. AISIX refuses these values with 400 invalid_request_error naming the field: a stop, reasoning_effort, or service_tier that is not a string, and a response_format that is not an object or whose type is not text, json_object, or json_schema. A gateway at 1.7.0 or earlier sends Gemini only temperature, top_p, the output limit, and the structured-output fields.
Reasoning on Gemini
AISIX asks Gemini for its thoughts only when the caller asks for reasoning. On Gemini 2.5 and later, and on model names AISIX does not recognize as an older generation, such a request carries thinkingConfig.includeThoughts: true next to the budget or level described in Generation Settings on Gemini.
| Route | The caller asks for reasoning when |
|---|---|
/v1/chat/completions | reasoning_effort is non-empty and not none. |
/v1/messages | thinking.type is enabled or adaptive, and thinking.display is not omitted. output_config.effort on its own does not ask. |
/v1/responses | reasoning.effort is non-empty and not none. |
AISIX leaves includeThoughts out when the effective setting turns thinking off: a /v1/messages thinking.budget_tokens of 0, or an effort that effort mapping turns into none. An effort that only a mapping wrote is not the caller asking. It still sets the budget or level, and Gemini returns no thoughts for it.
A thought part never reaches the answer text, whatever the request asked. Its text is returned unmodified in the reasoning field of the caller's route:
reasoning_contenton/v1/chat/completions, inmessageordelta.- A
thinkingblock with an emptysignatureon/v1/messages. - A
reasoningitem on/v1/responses.
In a stream, a frame holding only thoughts is sent as it arrives, so the first event can be reasoning rather than text. Gemini can also send thoughts nobody asked for: gemini-nano-banana-2.1 does, so a client that did not ask for reasoning can still see reasoning_content from that model. Reasoning in the conversation history is not sent back to Gemini, and generated reasoning is outside the output guardrail's scope.
An effort with no Gemini setting, such as max or xhigh, sends includeThoughts on its own when the caller asked for reasoning. A model that does not think by default refuses that with its own 400, which AISIX passes through: Thinking_config.include_thoughts is only enabled when thinking is enabled. This was measured on gemini-2.5-flash-lite, gemini-2.5-flash-image, and gemini-3.5-transcribe. Send a tier the model supports to those models, or omit the effort.
A gateway at 1.8.0 or earlier never asks Gemini for thoughts, so its callers receive no reasoning, and a max or xhigh request to such a model returns 200.
Images on Gemini
Gemini image models on Agent Platform, such as gemini-nano-banana-2.1 and gemini-2.5-flash-image, return generated images on /v1/chat/completions, with and without streaming. Each image is an entry in message.images, or delta.images on a stream:
{"type":"image_url","image_url":{"url":"data:image/png;base64,...","detail":"auto"},"index":0}
Images never go into content, and text parts behave as usual. A gateway at 1.8.0 or earlier does not return generated images.
Images are returned on chat only. /v1/messages and /v1/responses discard them and keep any text, so an image-only answer on those routes can be a 200 with empty content. /v1/images/generations does not accept this adapter.
Read the last entry for the final image. Thought images are returned too, in response order, and index is the position within the response, counted across stream frames. Nothing marks which image is final. In testing, gemini-nano-banana-2.1 commonly returned two nearly identical images with the final one last, and gemini-2.5-flash-image returned one.
If a non-streaming image request fails while decoding the response, disable upstream compression. When AISIX returns 502 upstream_decode_error with upstream returned an unparseable body: error decoding response body, set accept-encoding: identity on the provider key and retry:
{
"request": {
"default_headers": {
"accept-encoding": "identity"
}
}
}
In a resources file, write the same value under the provider key:
request:
default_headers:
accept-encoding: identity
See Upstream Request Headers for how request.default_headers applies. Separately, a stream fails after its 200 when one image's base64 exceeds 16 MiB, the limit on a single stream frame.
Choose the output with modalities. AISIX writes modalities to generationConfig.responseModalities, upper-cased, only when the client sends it, and checks it against no list of models. In testing, ["text", "image"] and ["image"] work on the two models above. ["text"] receives a 400 from the upstream on both, which AISIX passes through.
Edit an image by sending it in a user message. Assistant images in the history are sent back to Gemini without a thoughtSignature, so on gemini-nano-banana-2.1 they do not work for editing: in testing, the model did not see the original image, reporting prompt_tokens of 12 with no image tokens, and the result moved to a different scene. Put the image in a user message's image_url part instead. The same test kept the original scene, reporting prompt_tokens of 1125, of which 1120 were image tokens. On gemini-2.5-flash-image, the two forms made no difference.
{
"model": "gemini-image",
"modalities": ["text", "image"],
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Make the sky overcast."},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
]
}
]
}
A replayed data: URL is sent as inlineData, and a gs:// or https:// URL whose media type is known is sent as fileData. AISIX refuses an entry it cannot write with 400 invalid_request_error naming messages[i].images[j], before contacting the upstream. That includes an http:// URL, because AISIX downloads media for user turns only. images that is not an array is refused the same way.
Refused and failed images finish as content_filter. Gemini's IMAGE_PROHIBITED_CONTENT, NO_IMAGE, IMAGE_OTHER, and IMAGE_RECITATION map to content_filter as IMAGE_SAFETY does, even when the response also carries text. The response is still an HTTP 200:
| Route | Result |
|---|---|
/v1/chat/completions | finish_reason: content_filter |
/v1/messages | stop_reason: refusal |
/v1/responses | status: incomplete, with incomplete_details.reason: content_filter |
Guardrails read text, not images. Output guardrails do not scan generated images and do not block because of them, the same rule as Image Generation. Text in the same response is scanned, masked, and blocked as usual, and a text-matching guardrail lets an image-only response through. A custom guardrail still decides on a response with no text, so it can block an image-only response; see What the Hook Receives.
Images stay out of records and the cache. Generated image data does not reach usage events, access logs, or exporters, even with content_mode: full. Under content_mode: full, an input image the client sent is exported as a part with a placeholder in place of its base64, and an http or https image URL stays as sent; see Base64 Media in Captured Content. A response with images is not stored in the response cache.
Response IDs on Gemini
The id a client receives depends on the route:
| Route | id |
|---|---|
/v1/chat/completions, non-streaming | Gemini's responseId. When Gemini reports none, a generated chatcmpl-<uuid>. |
/v1/chat/completions, streaming | From the first frame that reports a responseId, that value on every later frame. Frames ahead of it share one generated chatcmpl-<uuid>, and a stream with no responseId uses one generated ID throughout. |
/v1/messages, non-streaming | Gemini's responseId. When Gemini reports none, a generated msg_<uuid>. |
/v1/messages, streaming | A generated msg_<uuid>, never responseId. |
/v1/responses | A generated ID that starts with resp_, never responseId. |
A client can rely on id being non-empty on these routes. It cannot rely on id being Google's own, or on a chat stream's first frame having the same id as its last.
To find a call in Google's records, read provider_request_id. It records responseId on all three routes, so it has a value in the control plane's usage records and in exports, where OTLP and Datadog name it gen_ai.response.id. A generated ID is never recorded there.
Other upstreams whose response AISIX translates get the same generated ID when they report none. A gateway at 1.8.0 or earlier returns an empty id from Gemini on /v1/chat/completions and non-streaming /v1/messages, and records no responseId in provider_request_id.
Token Usage on Gemini
AISIX maps Agent Platform's token counters into the normalized response rather than only their total. Thinking tokens (thoughtsTokenCount) count toward completion_tokens, as Google bills them, and are named separately under completion_tokens_details.reasoning_tokens. Context-cache hits (cachedContentTokenCount) stay a subset of prompt_tokens and are reported under prompt_tokens_details.cached_tokens.
Those are the Chat Completions field names. The same values reach a Responses caller as output_tokens_details.reasoning_tokens and input_tokens_details.cached_tokens, and an Anthropic Messages caller sees the cache hit as cache_read_input_tokens and no separate reasoning counter, because that protocol has none.
The usage record keeps Gemini's own counters instead of the caller's view. When Gemini reports the thinking tokens beside candidatesTokenCount, the record's completion_tokens is candidatesTokenCount, reasoning_tokens is thoughtsTokenCount, and total_tokens is totalTokenCount. This applies only when prompt + completion + reasoning tokens equal that total. When Gemini reports no total, or one those counts do not add up to, the record keeps the thinking tokens inside completion_tokens, as a gateway at 1.4.0 or earlier does for every Gemini response. The control plane prices both forms the same way; see How Reasoning Tokens Are Billed.
Function Calling on Gemini
Gemini publisher models support function calling on /v1/chat/completions, /v1/messages, and /v1/responses, with and without streaming. Messages and Responses requests reach Gemini through the chat translation, so the same rules apply to all three routes:
- Function tools in
toolsare sent to Gemini as function declarations. On Gemini 2.5 and later, including the-latestaliases, a function'sparametersare sent unchanged asparametersJsonSchema. On older families, AISIX converts them to Gemini's typedparametersschema when that subset can express them, and otherwise sends them unchanged asparametersJsonSchema, for example withoneOf,allOf,const, or a non-stringenum. A gateway at 1.7.0 or earlier converts the parameters of Gemini 2.5 models to the typed schema too, which removesadditionalProperties. tool_choicemaps to Gemini's function-calling mode:none,auto, andrequiredbecomeNONE,AUTO, andANY, and a named function becomesANYrestricted to that function. Anallowed_toolschoice inrequiredmode becomesANYrestricted to the listed functions; inautomode it sets no mode.- The legacy
functionsandfunction_callfields are sent as function declarations and a function-calling mode, as described in Legacy Function Calling. - An OpenAI
customtool takes free-form text, which Gemini has no declaration for, so it is dropped. - Each function call Gemini returns becomes its own tool call, including parallel calls. In a stream, each call arrives complete with its own
index. A turn that ends with function calls finishes astool_calls, or the equivalent stop reason on Messages and Responses. - Assistant tool calls and tool results in the conversation history are sent back to Gemini as function calls and function responses. A tool result that is a JSON object is sent as the function response itself; any other result is sent as
{"content": "<text>"}.
Send tool call IDs back unchanged. Gemini 3 requires the thought signature it returns with a function call to come back with that call in the next request, and no client protocol has a field for it. AISIX therefore appends the signature to the tool call ID it returns, as <id>__thought__<signature>. These IDs are longer than usual but contain only letters, digits, _, and -. Return each ID exactly as received, in both the replayed assistant turn and the matching tool result, so that Gemini gets its signature back. When a Gemini 3 history carries unsigned function calls, for example because another model wrote the turn, AISIX adds Google's documented placeholder signature to the first function call only. In a parallel-call turn, later calls remain unsigned, matching Gemini's signing behavior.
The signature is meant for Gemini only. When a conversation that started on an Agent Platform Gemini model continues on any other upstream, AISIX removes the __thought__ suffix and sends only the base ID, so the conversation can move between providers. Guardrails never read a tool call ID, so they scan neither the base ID nor the signature. They still scan the function name and arguments. See Guardrail Behavior.
AISIX rejects the following with 400 invalid_request_error rather than dropping them:
- A tool that is neither a function tool nor a
customtool, such as a hosted web search tool. Gemini built-in tools are not supported. toolsthat is not an array, or a function tool without a name.- A
tool_choicestring other thannone,auto, orrequired, atool_choiceobject of another type or without a function name, or atool_choicethat is a number or boolean. This check applies even when the request sends no tools. - A tool result that matches no tool call in an earlier assistant turn.
- A replayed assistant tool call whose arguments are not valid JSON.
Structured Output on Gemini
A response_format carrying a JSON schema — sent to /v1/chat/completions, or translated from a Responses text.format or an Anthropic Messages output_config.format — becomes generationConfig.responseMimeType: "application/json" plus the schema itself, in whichever field the model's generation reads it out of:
- Gemini 2 and later:
generationConfig.responseJsonSchema, ordinary JSON Schema, forwarded as the caller wrote it. - Gemini 1.x:
generationConfig.responseSchema, the older OpenAPI-derived dialect. Type names are upper-cased (OBJECT,STRING); members the dialect has no equivalent for are removed,additionalPropertiesamong them, because Gemini rejects them by name; and an explicitpropertyOrderingis added so the member order the model emits is fixed rather than unspecified.exclusiveMinimum,exclusiveMaximum,multipleOf, anduniqueItemsare restated in the property'sdescriptionrather than dropped silently,oneOfbecomesanyOf, and internal$refs are expanded in place because the dialect has no$ref. Numeric and string bounds such asminimum,maxLength, andpatternare part of the dialect and survive.
AISIX reads the generation off the model name, tolerating the models/<id> spelling. A name with no readable generation, such as gemini-exp-1206, falls back to responseSchema, the field every generation accepts.
{"type": "json_object"} sets the mime type only; {"type": "text"} sends nothing. There is no tool fallback and no prompt injection on this path — AISIX does not turn the schema into a forced tool call, and a model that cannot constrain its decoding answers as it would have. required is the caller's own statement in both dialects and is never rewritten.
Claude on Agent Platform
/v1/messages and /v1/messages/count_tokens addressed to a claude-* model go to Agent Platform's own Anthropic Messages routes with the caller's body, instead of being translated through the chat format. /v1/chat/completions and /v1/responses are not affected.
| Caller route | Agent Platform route |
|---|---|
/v1/messages | :rawPredict, or :streamRawPredict for a streaming request, on publishers/anthropic/models/<model> |
/v1/messages/count_tokens | publishers/anthropic/models/count-tokens:rawPredict, with any @ version suffix removed from model |
The caller gets the response as Claude produced it, including signed thinking blocks, redacted_thinking blocks, images inside a tool_result, the upstream message ID, and the native usage counters. On the way, AISIX applies only these changes:
modelis removed, because the URL names the model, andanthropic_versionis set tovertex-2023-10-16.- Top-level fields are limited to the ones Agent Platform accepts:
anthropic_version,max_tokens,messages,metadata,stop_sequences,stream,system,temperature,thinking,tool_choice,tools,top_k,top_p,inference_geo,mcp_servers,context_management,compaction,container,output_config,speed,cache_control, andsafeguards. A legacy top-leveloutput_formatmoves intooutput_config.format, andmetadatakeeps onlyuser_id. - The
anthropic-betaheader carries the caller's betas, even whenforward_client_headersdoes not name the header, merged with the provider key's, filtered to the betas Agent Platform accepts.advanced-tool-use-2025-11-20is sent astool-search-tool-2025-10-19, and AISIX adds the context-management, web-search, tool-search, safeguards, and per-turn-control betas when the body uses those features. See Claude Beta Values. - A
cache_controlmarker loses itsscopefield and keeps the rest. - Media given by URL, including inside a
tool_result, is downloaded and sent inline. - The Anthropic attribution line and
thinkingblocks with an empty signature are removed; see Anthropic Attribution Line.
A structured-output request, output_format or output_config.format, to a Claude family before 4.5, which has no native structured output, stays on these routes too. AISIX moves the schema onto the synthetic json_tool_call tool in the Anthropic body, sealed and narrowed as described in How AISIX Adapts the Schema, and forces that tool unless the request sets its own tool_choice or enables thinking. The model's call to the tool is returned as the JSON text the caller asked for. Everything else in the body reaches the model as written. The upstream call does not stream, so a streaming caller receives the answer as an SSE stream once it is complete.
Prompt-cache markers reach Claude on every route: the chat translation keeps the caller's cache_control markers, and a model can add them automatically with Anthropic Prompt Caching.
Claude Beta Values
Agent Platform answers an anthropic-beta value it does not know with a 400, so AISIX filters every beta it sends to a Claude model, renames it where Agent Platform's name differs, and drops a value Agent Platform refuses. The betas travel in the anthropic-beta request header.
The provider key's anthropic-beta is the value its request headers resolve to: a request.default_headers entry, or the caller's own header when request.forward_client_headers admits it. When both supply it, the default_headers entry wins, as for any other header. Which betas are sent then depends on the route:
| Caller route | Betas in the anthropic-beta header |
|---|---|
/v1/messages, streaming or not | The caller's anthropic-beta header, even when forward_client_headers does not name it, merged with the provider key's anthropic-beta and with the betas the body's features need. |
/v1/messages/count_tokens | None. Token counting takes no beta. |
/v1/chat/completions and /v1/responses | The provider key's anthropic-beta only. No betas are added for the body's features, and the header is left out when no value survives the filter. |
Agent Platform Publisher Routing
Publisher selection is prefix-based. If model_name does not match a supported prefix, the gateway rejects the request before the provider request with an unsupported-publisher configuration error.
The example uses Gemini because it is the main Google publisher path. For partner models, validate the exact model ID, quota, and regional availability in your Agent Platform project before exposing the alias to callers.
A provider key has one project and location. Create another provider key when a partner model is available only in a different location; changing model_name does not change the location used in the Agent Platform resource path.
Provider-key request and response overrides can apply on Agent Platform routes, but they are most directly useful on OpenAI-compatible routes. Gemini's native contents format does not match every OpenAI-style override target.
Next Steps
You have now connected AISIX to Gemini Enterprise Agent Platform and verified the model alias. Continue with these guides:
- Gemini (Google AI Studio): configure Gemini with an AI Studio API key instead of a Google Cloud project.
- Model Aliases: configure routing, retry behavior, or cost metadata for the alias.
- Provider Compatibility: review supported proxy endpoints and provider-specific boundaries.