Google Gemini Provider

Two providers for Google's Gemini models:

  • GoogleProvider — Google AI Studio (Generative AI API)
  • GoogleVertexProvider — Google Cloud Vertex AI

Google AI Studio

#![allow(unused)]
fn main() {
use yoagent::provider::ModelConfig;

// The preset carries the model's real limits (1,048,576 context, 65,536 output)
// and its price; `ModelConfig::google(id, name)` works for any other Gemini id.
let agent = Agent::from_config(ModelConfig::gemini_3_8_flash());
}

API Details

  • Endpoint: {base_url}/v1beta/models/{model}:streamGenerateContent?alt=sse
  • Auth: API key in the x-goog-api-key header (since 0.24.2; earlier versions put it in the URL, where transport errors could echo it)
  • API key env: GEMINI_API_KEY, then GOOGLE_API_KEY (see API keys)
  • Default base URL: https://generativelanguage.googleapis.com
  • Default context window: 1,000,000 tokens

Message Format

Google uses a different message format than OpenAI/Anthropic:

yoagentGoogle API
user roleuser role
assistant rolemodel role
Content::Text{"text": "..."}
Content::Image{"inlineData": {...}}
Content::ToolCall{"functionCall": {...}}
Message::ToolResult{"functionResponse": {...}}
System promptsystemInstruction field
Toolstools[].functionDeclarations[]

Thinking

Both Google AI Studio and Vertex AI map ThinkingLevel onto generationConfig.thinkingConfig, using exactly one of two fields:

  • Gemini 3 and later: thinkingLevel. Google documents thinkingLevel as "Recommended for Gemini 3 or later models. Use with earlier models results in an error." Off → (omitted), Minimal → MINIMAL, Low → LOW, Medium → MEDIUM, High / XHigh / Max → HIGH.
  • Gemini 2.x: thinkingBudget, unchanged: 1,024 / 8,192 / 24,576 tokens (see the ThinkingLevel table).

The two are never sent together — Vertex: "If you specify both thinking_level and thinking_budget in the same request for a Gemini 3 model, the model returns an error."

How the generation is detected. Google's rule is version-based, so the crate reads the version from the model id: the last path segment is taken (so models/gemini-3.8-flash and projects/…/publishers/google/models/gemini-3.1-pro-preview both work), an @version suffix is dropped, and a gemini-<major>[.<minor>] id with major ≥ 3 gets thinkingLevel. gemini-flash-latest and gemini-pro-latest also get it (the changelog points them at 3.5 Flash and 3 Pro preview). gemini-flash-lite-latest, and any id the rule cannot read (gemma-*, tuned-model names, proxy aliases), keeps thinkingBudget, which Gemini 3 still accepts for backward compatibility.

MINIMAL is not accepted everywhere. Per Google's level table, 3.8 and 3.7 Flash reject it and 3.1 Pro does not support it. The crate sends MINIMAL only where it is documented as accepted — 3.6 / 3.5 Flash, every 3.x Flash-Lite, 3 Flash — and clamps Minimal to LOW everywhere else, including the -latest aliases, which Google swaps to new models without notice.

Off sends nothing — and does not disable thinking on Gemini 3. On every Gemini model Off omits thinkingConfig. On 2.x that is the crate's long-standing behaviour. On Gemini 3 thinking cannot be switched off this way: with no thinkingConfig the model thinks at its own default level — HIGH on 3.1 Pro, MEDIUM on 3.5–3.8 Flash, MINIMAL on 3.x Flash-Lite (HIGH on 3 Flash) — per Google's level tables. Google: "If you don't specify a thinking level, Gemini will use the Gemini 3 models' default thinking level (e.g., "high" for Gemini 3.1 Pro, and "medium" for Gemini 3.5 Flash)", and "You cannot disable thinking for Gemini 3.1 Pro. Gemini 3 Flash and Flash-Lite also do not support full thinking-off."

To ask for the least thinking, use ThinkingLevel::Minimal. It sends MINIMAL where the model accepts it — which Google says "Matches the "no thinking" setting for most queries" but "does not guarantee that thinking is off" — and LOW elsewhere.

Image and TTS models. Google's Vertex AI thinking table lists narrower level sets for the image models: Gemini 3.1 Flash Image and 3.1 Flash-Lite Image accept "MINIMAL , HIGH", and Gemini 3 Pro Image accepts "HIGH" only. The crate reads any Gemini 3+ -image id that way: on the MINIMAL/HIGH models Minimal and Low send MINIMAL and Medium and above send HIGH; on -pro…-image ids every level sends HIGH. Gemini 3 TTS models (gemini-3.8-flash-tts, gemini-3.8-flash-lite-tts, gemini-3.1-flash-tts-preview) are not in either guide's list of thinking models, so they get no thinkingConfig at any level. 2.x image and TTS ids keep the thinkingBudget payload unchanged.

Override. When the id does not say what the model is, set GoogleCompat:

#![allow(unused)]
fn main() {
use yoagent::provider::{GoogleCompat, ModelConfig};

let mut config = ModelConfig::google("my-gemini-proxy-alias", "Gemini via proxy");
config.google = Some(GoogleCompat::force_thinking_level()); // or force_thinking_budget()
}

A forced thinkingLevel on an unrecognised id clamps Minimal to LOW, since the crate cannot know whether that model accepts MINIMAL; Off still sends nothing.

Streaming

Uses SSE format (alt=sse). Each chunk contains candidates with content.parts and optional usageMetadata. The API key goes in the x-goog-api-key header.

A turn is never an empty or truncated success:

  • A stream that ends (or drops) before any finishReason is a retryable network error. A drop after it keeps the response, so it is not billed twice.
  • An in-stream {"error": …} is an error. A 503 / UNAVAILABLE overload is retried, honouring Retry-After on an HTTP error; a 429 RESOURCE_EXHAUSTED (often an exhausted quota) is not.
  • SAFETY, PROHIBITED_CONTENT, BLOCKLIST, SPII, IMAGE_SAFETY and a prompt blocked via promptFeedback.blockReason end as StopReason::Refusal with the reason in error_message.
  • MALFORMED_FUNCTION_CALL, UNEXPECTED_TOOL_CALL and TOO_MANY_TOOL_CALLS end as StopReason::Error, with Gemini's finishMessage when it sends one.

Google Vertex AI

GoogleVertexProvider uses the same message format but with Vertex AI authentication and endpoints. It shares Gemini's stream parser, so everything under Streaming applies to it too.

  • Protocol: ApiProtocol::GoogleVertex. There is no preset; build the config with ModelConfig::custom.
  • Auth: an OAuth2 access token passed with with_api_key, sent as Authorization: Bearer {token}. yoagent reads no service-account file, refreshes no token, and resolves no environment variable for vertex: mint the token yourself (e.g. gcloud auth print-access-token) and rebuild the agent before it expires.
  • base_url: https://{region}-aiplatform.googleapis.com/v1/projects/{project}/locations/{region}/publishers/google/models. Requests go to {base_url}/{model}:streamGenerateContent?alt=sse.
#![allow(unused)]
fn main() {
use yoagent::provider::{ApiProtocol, ModelConfig};

let agent = Agent::from_config(ModelConfig::custom(
    ApiProtocol::GoogleVertex,
    "vertex",
    "https://us-central1-aiplatform.googleapis.com/v1/projects/my-proj/locations/us-central1/publishers/google/models",
    "gemini-3.8-flash",
    "Gemini 3.8 Flash",
))
.with_api_key(access_token);
}