Integrations
Using Gemini through an OpenAI-compatible gateway
Call Gemini with the OpenAI SDK through Ragmux — how tool schemas get sanitised, how tool results are correlated, and the one token count Google's own docs don't agree on.
Updated: Last reviewed: Verified against Ragmux 0.5.0
Gemini is reached through Ragmux the same way every other provider is: your code keeps
calling the OpenAI-shaped /v1/chat/completions, and the gateway is what actually speaks
Google’s generateContent API underneath. The mapping is mostly what you’d expect —
system becomes systemInstruction, assistant turns are sent as role model,
temperature/top_p/max_tokens/stop land in generationConfig — but Gemini’s take
on function calling and token accounting has real gaps a caller should know about before
depending on either.
Adding the connection
POST /admin/api/models with provider_type: gemini, an api_key and a model_name
creates the connection. Unlike Anthropic, Gemini also supports embeddings — the same
provider type can back a chat connection or an embedding connection, so a private
document Q&A setup can run entirely on Gemini without a
second provider for the embedding half. POST /admin/api/models/{id}/test with
{"mode": "chat"} or {"mode": "embedding"} confirms the key and model name actually
work — for embeddings, the response reports the vector’s dimension count rather than a
sample reply.
Images and finish reasons
Gemini accepts images in two different ways, and the gateway picks between them rather
than exposing the choice to a caller. A data: URL in an OpenAI-shaped image content
part is sent as inline base64 data, the same way it would be for any other provider. A
plain https:// URL, though, is something Gemini can’t fetch itself the way Anthropic
can — so Ragmux downloads it and resends it as inline data, unless it already points at
something Gemini can resolve on its own, such as a Files API URI or a gs:// path. That
download step depends on outbound fetches being allowed at all: with IMAGE_FETCH=false
set on the gateway, a request carrying a plain image URL against a Gemini connection is
refused outright with a 400, rather than silently sent without the image attached.
Finish reasons get the same normalization every other provider does. An ordinary
completion’s STOP becomes OpenAI’s stop; hitting the token ceiling’s MAX_TOKENS
becomes length. Gemini’s various safety-related reasons — SAFETY, RECITATION,
BLOCKLIST, PROHIBITED_CONTENT, SPII — all collapse into OpenAI’s single
content_filter value, since OpenAI-SDK code generally only branches on whether a
response was filtered, not on which specific safety category triggered it. A turn that
ends in a tool call always reports tool_calls, because Gemini has no separate finish
reason for that case the way OpenAI does.
Tool calling: more translation than most providers need
Gemini has function calling, but neither call ids nor full JSON Schema, so Ragmux does more work here than for OpenAI-shaped providers:
- Tool definitions become one
functionDeclarationslist, andtool_choicemaps to Gemini’sfunctionCallingConfig—"auto","required"and a named function all have direct equivalents. Unlike the Anthropic path,"none"still sends the declarations, because Gemini has an exactNONEmode rather than needing the tools hidden entirely. - Results are correlated by function name, not by id. A
toolmessage’stool_call_idis looked up against the assistanttool_callsthat came before it, and the recovered name — not the id — is what’s actually sent upstream as afunctionResponse. A result whose call name isn’t in the preceding history is dropped rather than forwarded as something Gemini would reject. - Streamed tool calls arrive whole. Gemini emits a complete
functionCallinside one chunk rather than fragmenting arguments across several, so each call comes back as onetool_callsdelta with its index, name and arguments already assembled — no partial JSON to reconstruct on the client side. - The thought signature round-trips as
ragmux_signature. Gemini 3 thinking models expect thethoughtSignatureon a tool-calling turn to come back with the next request, and a request that dropped it could be answered with a400. Ragmux hands it to the client as an opaquetool_calls[].ragmux_signaturefield, streamed and non-streamed alike, and writes it back onto thefunctionCallpart when the client sends that turn again. A client that rebuilds tool calls by hand has to copy the field along with the id, name and arguments. It is stripped for every non-Gemini upstream, so it never reaches OpenAI, Anthropic or Ollama.
The bigger gap is schema shape, not call plumbing. Gemini’s parameters accept only an
OpenAPI 3.0 subset and reject an unknown keyword outright rather than ignoring it —
and an OpenAI strict-mode tool routinely carries additionalProperties: false and
$ref/$defs, neither of which Gemini’s API accepts as written. Every schema is
rewritten before it leaves the gateway: $ref is inlined from $defs, oneOf becomes
anyOf, the object branches of an allOf are merged into the node itself (only
non-object branches fall back to anyOf), const becomes a one-value enum or a bare
type, and keywords Gemini has no equivalent for — additionalProperties, default,
patternProperties, not — are dropped entirely. This is deliberately lossy: a tool whose argument shape depends on
oneOf discriminating between two payloads, or on additionalProperties: false
forbidding extra keys, behaves differently against Gemini than it does against OpenAI or
Claude. A tool that leans on either belongs on a connection with wider schema support.
Structured output: json_schema reaches Gemini
response_format: {"type": "json_schema", ...} is forwarded as
generationConfig.responseSchema, after the schema goes through the same reduction to
the subset Gemini accepts that tool parameters get. Dropped keywords are logged at debug
level, and a schema left with no properties falls back to JSON mode alone
(responseMimeType: application/json) instead of sending a constraint Gemini would
reject.
Upstream errors keep their Retry-After
When Gemini answers an error with a Retry-After, in seconds or as an HTTP date, it
reaches the client on non-streaming errors and on errors before a stream starts. A
malformed value is dropped rather than relayed.
The one token count to double-check yourself
On an ordinary request, Gemini’s usage numbers map cleanly onto the OpenAI shape
everything else uses. On a tool-calling round, they don’t necessarily add up: Gemini
separately reports a toolUsePromptTokenCount that Ragmux neither reads nor folds into
prompt_tokens, while total_tokens is relayed exactly as Gemini sent it — so
prompt_tokens + completion_tokens can come out less than total_tokens on those
requests, and the difference isn’t priced into the per-request cost estimate. This isn’t
a bug Ragmux is working around quietly: Google’s own REST reference and its published
protobuf disagree on what totalTokenCount actually sums, and neither states whether
tool-use tokens are inside it, so the gateway doesn’t guess at a number it can’t verify.
For a Gemini tool-calling workload where the exact bill matters, read it from Google’s own
console rather than from Ragmux’s cost estimate on those specific requests.
After it’s connected
Past the translation layer, a Gemini-backed project is unremarkable in exactly the way it
should be: rate limits, token budgets, the audit log and the request log all key off the
project, not the provider behind it, so tracking what a Gemini-backed project actually
spent works the same way it would for GPT or
Claude. Moving off Gemini later — to a different provider, or to a self-hosted model
through Ollama or vLLM — means updating the connection or
the project’s model_connection_id, not rewriting the application code that calls
/v1/chat/completions.
Full reference: /docs/providers/#gemini.
Connect Gemini to Ragmux
Add a Gemini model connection
POST /admin/api/models with provider_type: gemini, an api_key, and a model_name. Gemini also supports embeddings, so the same provider_type can back a RAG store's embedding model too.
Test it before wiring it into a project
POST /admin/api/models/{id}/test with {"mode": "chat"} or {"mode": "embedding"} confirms the key and model name work before a real request depends on them.
Attach the connection to a project
POST /admin/api/projects with model_connection_id set. If the same project also needs retrieval, its rag_store_id can point at a store using a Gemini embedding connection of its own.
Call /v1/chat/completions with tools as you normally would
Send OpenAI-shaped tools and tool_choice unchanged. Ragmux rewrites the JSON Schema into what Gemini's API accepts before it leaves the gateway.
Frequently asked questions
- Can one Gemini connection back both chat and embeddings?
- Yes — gemini supports embeddings as well as chat, so the same provider type can be used for a project's model connection and, separately, for a RAG store's embedding connection.
- Why did my OpenAI-style tool schema stop working against Gemini?
- Gemini's function-calling schema rejects unknown JSON Schema keywords instead of ignoring them, so keywords like additionalProperties or $ref are rewritten or dropped before the request reaches Gemini; a tool that depends on oneOf or a recursive $ref behaves differently there than on OpenAI.
- Are Gemini tool call ids preserved end to end?
- No — Gemini has no concept of a tool call id, so Ragmux correlates a tool result back to the call that produced it by function name instead; a result whose call name isn't in the preceding message history is dropped rather than sent upstream.
- Can Ragmux's cost estimate be trusted for a Gemini tool-calling request?
- Only for the prompt and completion counts — a tool round's total_tokens can include a toolUsePromptTokenCount Ragmux doesn't read or map, so total_tokens can exceed prompt_tokens plus completion_tokens on those requests; check Google's own console for the exact bill.
- How do I keep Gemini 3 tool calling working across turns?
- Copy the tool_calls[].ragmux_signature field back unchanged when you send the assistant turn again. Ragmux returns Gemini's thoughtSignature there as an opaque value and writes it back onto the functionCall part; a client that rebuilds tool calls by hand without it can be answered with a 400.
- Does response_format json_schema work with a Gemini connection?
- Yes — the schema is reduced to the subset Gemini accepts and sent as generationConfig.responseSchema; if nothing constraining is left after that, the request falls back to JSON mode alone.
- Does Gemini support prompt caching through Ragmux?
- Only the response side — Gemini's implicit caching is reported automatically in cachedContentTokenCount, but its explicit, stateful cachedContents caching has no request-side equivalent Ragmux can pass through.
Sources
- Gemini API — GenerateContentResponse.UsageMetadata reference
- generativelanguage protobuf — UsageMetadata.total_token_count
Product and provider names are trademarks of their respective owners.