Ragmux

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 functionDeclarations list, and tool_choice maps to Gemini’s functionCallingConfig — "auto", "required" and a named function all have direct equivalents. Unlike the Anthropic path, "none" still sends the declarations, because Gemini has an exact NONE mode rather than needing the tools hidden entirely.
  • Results are correlated by function name, not by id. A tool message’s tool_call_id is looked up against the assistant tool_calls that came before it, and the recovered name — not the id — is what’s actually sent upstream as a functionResponse. 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 functionCall inside one chunk rather than fragmenting arguments across several, so each call comes back as one tool_calls delta 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 the thoughtSignature on a tool-calling turn to come back with the next request, and a request that dropped it could be answered with a 400. Ragmux hands it to the client as an opaque tool_calls[].ragmux_signature field, streamed and non-streamed alike, and writes it back onto the functionCall part 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

  1. 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.

  2. 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.

  3. 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.

  4. 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

Product and provider names are trademarks of their respective owners.

Arrow keys to move, Enter to open.