SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
31.8 KB · 303 lines markdown
Rendered Raw Blame History
1# Google Gemini (Gemini Developer API) — provider research for PolyLLM23Last documentation audit: **2026-09-08**. SDK probed: `@google/genai` **2.21.0** (Node 25.9, tsx 4.23). Probe scripts and raw outputs: `research/gemini/*.ts`, `research/gemini/out/*.json` (keys redacted). Key used: free-tier `AQ.`-style ("auth") key.45> Headline for the adapter: `generateContent` / `streamGenerateContent` remain **fully supported** but Google now labels them legacy and documents everything on the new **Interactions API** (`ai.interactions.create`, GA June 2026, "recommended for all new projects", all new features land there first). This audit targets `generateContent` (what the SDK's `ai.models.*` uses) and notes Interactions where relevant.67---89## 1. Endpoint, auth, headers1011| Item | Value (verified by probe unless marked docs) |12|---|---|13| Base URL | `https://generativelanguage.googleapis.com` |14| API version | `v1beta` (SDK default; needed for computer use / MCP / preview features). `v1` also works: probed `gemini-3.8-flash` + `thinkingLevel` on `v1` -> OK. Select via `new GoogleGenAI({ apiKey, httpOptions: { apiVersion: "v1" } })`. |15| Auth header | `x-goog-api-key: <key>` (works with `AQ.` keys). `?key=` query param also works (200). **`Authorization: Bearer <api key>` does NOT work**: 401 `UNAUTHENTICATED`, reason `ACCESS_TOKEN_TYPE_UNSUPPORTED` (Bearer is only for the OpenAI-compat endpoint). |16| Other headers | `Content-Type: application/json`. No rate-limit headers are returned (only `server-timing`, `vary`, `alt-svc`). |17| Key formats | New keys created in AI Studio are "auth keys" bound to a service account (`AQ.` prefix). Docs: **standard (legacy) keys will be rejected after September 2026** — expect BYOK users with old keys to break. Leaked keys: `"Your API key was reported as leaked. Please use another API key."` |18| SDK env vars | `GEMINI_API_KEY` or `GOOGLE_API_KEY` (`GOOGLE_API_KEY` wins if both). Pass `apiKey` explicitly in a BYOK app. |19| OpenAI-compat | `https://generativelanguage.googleapis.com/v1beta/openai/` with `Authorization: Bearer <key>`; `reasoning_effort` maps to thinking levels (3.x) or budgets 1024/8192/24576 (2.5); Gemini extras via `extra_body.google.*`. Beta. Not probed. |2021Docs: https://ai.google.dev/gemini-api/docs/api-key , https://ai.google.dev/gemini-api/docs/api-versions , https://ai.google.dev/gemini-api/docs/openai2223## 2. SDK2425```bash26pnpm add @google/genai@latest   # 2.21.0 on 2026-09-08; Node >= 20 (3.x will require Node 22+)27```28```ts29import { GoogleGenAI } from "@google/genai";30const ai = new GoogleGenAI({ apiKey, httpOptions: { timeout: 120_000 /* ms */, apiVersion: "v1beta" } });31```32- `ai.models.generateContent / generateContentStream / countTokens / list / get`, `ai.chats.create` (client-side history helper), `ai.caches`, `ai.files`, `ai.batches`, `ai.live`, `ai.interactions` (new), `mcpToTool()`.33- Errors: `ApiError { name: "ApiError", status: <http>, message: <JSON string of the body> }` — message is the raw `{"error":{code,message,status,details}}` JSON; parse it. Retries: `httpOptions.retryOptions` exists (defaults not documented for JS).34- `config.abortSignal` supported on generate calls.35- Legacy `@google/generative-ai` is deprecated (since 2025-11-30) — do not use.36- SDK-side guard: `toolConfig.functionCallingConfig.streamFunctionCallArguments` throws `"streamFunctionCallArguments parameter is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."` (no partial-args streaming on the Developer API).3738Docs: https://github.com/googleapis/js-genai , https://ai.google.dev/gemini-api/docs/libraries3940## 3. generateContent / streamGenerateContent4142REST: `POST /v1beta/models/{model}:generateContent` and `POST /v1beta/models/{model}:streamGenerateContent?alt=sse`.4344Request body: `contents[]` (`{role:"user"|"model", parts:[...]}`), `systemInstruction` (`{parts:[{text}]}`), `tools[]`, `toolConfig`, `safetySettings[]`, `generationConfig`, `cachedContent`, `serviceTier`, `store`.45Response: `candidates[0].content.parts[]`, `candidates[0].finishReason`, `usageMetadata`, `modelVersion`, `responseId`, `promptFeedback`, `modelStatus`.4647Part fields seen in probes: `text`, `thought: true` (thought summary), `thoughtSignature` (base64 string, 300–700 chars), `functionCall {name, args, id}`, `functionResponse`, `inlineData {mimeType, data}`, `fileData {fileUri, mimeType}`, `executableCode {language:"PYTHON", code, id}`, `codeExecutionResult {outcome:"OUTCOME_OK", output, id}`.4849FinishReason enum (SDK 2.21): `STOP, MAX_TOKENS, SAFETY, RECITATION, LANGUAGE, OTHER, BLOCKLIST, PROHIBITED_CONTENT, SPII, MALFORMED_FUNCTION_CALL, IMAGE_SAFETY, UNEXPECTED_TOOL_CALL, TOO_MANY_TOOL_CALLS, IMAGE_PROHIBITED_CONTENT, NO_IMAGE, IMAGE_RECITATION, IMAGE_OTHER`.5051Docs: https://ai.google.dev/api/generate-content , https://ai.google.dev/gemini-api/docs/text-generation5253### 3.1 Streaming protocol (verified)5455- REST with `alt=sse`: `Content-Type: text/event-stream`; each event is `data: {GenerateContentResponse JSON}` terminated by **CRLF CRLF** (`\r\n\r\n`); no `event:` lines, no `[DONE]` sentinel — the stream simply ends. Errors before the first token come back as a normal HTTP error status (400 etc.) but still with `text/event-stream` content type and a JSON `{error}` body — check `res.ok` before parsing SSE.56- REST without `alt=sse`: `application/json` **JSON array** of responses (`[{...},{...}]`) streamed incrementally — avoid.57- SDK: `for await (const chunk of await ai.models.generateContentStream({...}))`, each chunk is a full `GenerateContentResponse` (`chunk.text` getter concatenates non-thought text).58- Chunk shape observed (Gemini 3.x Flash): 2–4 chunks for short answers. With `includeThoughts: true` the **first chunk** carries `parts:[{text:"…", thought:true}]`; then text chunks; the **last chunk** has `finishReason` and typically an **empty text part carrying `thoughtSignature`** (`{text:"", thoughtSignature:"…"}`) — do not render it as text, but keep it if you replay history.59- `usageMetadata` is present on **every** chunk. On 3.5/3.6 Flash the first chunk has only `promptTokenCount/totalTokenCount/promptTokensDetails/serviceTier`; subsequent chunks have the full set (`candidatesTokenCount`, `thoughtsTokenCount`). On 3.7 Flash all chunks were full. Rule: **take usage from the last chunk**.60- Function calls stream as a single chunk: `parts:[{functionCall:{name,args,id:"call_…"}, thoughtSignature:"…"}]`, followed by a final chunk with an empty `text` part and `finishReason:"STOP"` (not `FUNCTION_CALL`). Args are complete (never partial).6162### 3.2 usageMetadata fields63`promptTokenCount`, `candidatesTokenCount`, `thoughtsTokenCount` (absent when 0), `cachedContentTokenCount` (absent when 0), `toolUsePromptTokenCount`, `totalTokenCount` (= prompt + candidates + thoughts), `promptTokensDetails[{modality:"TEXT"|"IMAGE"|…, tokenCount}]`, `cacheTokensDetails[]`, `candidatesTokensDetails[]`, `serviceTier:"standard"`. Thinking tokens are billed as output.6465### 3.3 Exact SDK code that worked6667```ts68const stream = await ai.models.generateContentStream({69  model: "gemini-3.5-flash-lite",70  contents: [{ role: "user", parts: [{ text: "Count from 1 to 12, then say DONE." }] }],71  config: {72    systemInstruction: "You are terse.",73    maxOutputTokens: 2000,                       // includes thinking tokens!74    thinkingConfig: { includeThoughts: true, thinkingLevel: "LOW" }, // never combine with thinkingBudget75  },76});77let usage;78for await (const chunk of stream) {79  for (const part of chunk.candidates?.[0]?.content?.parts ?? []) {80    if (part.thought) emitReasoning(part.text);81    else if (part.text) emitText(part.text);             // may be "" on the final signature-only part82    if (part.functionCall) emitToolCall(part.functionCall.id, part.functionCall.name, part.functionCall.args, part.thoughtSignature);83  }84  if (chunk.candidates?.[0]?.finishReason) finish = chunk.candidates[0].finishReason;85  usage = chunk.usageMetadata ?? usage;                   // last chunk wins86}87```8889## 4. generationConfig parameter support (probed matrix)9091Probed with `maxOutputTokens: 1500` on the models the free-tier key can call. Legend: OK / 400 "…" = exact server message / — = not probed (quota).9293| param | 3.8-flash | 3.5-flash | 3.5-flash-lite | 3.1-flash-lite | 3-flash-preview | gemma-4-31b-it |94|---|---|---|---|---|---|---|95| temperature 0 / 1.5 / 2 | OK | OK | OK | OK | OK | 2 OK; 0,1.5 → 500 INTERNAL (flaky) |96| temperature 2.5 | 400 `* GenerateContentRequest.generation_config.temperature: temperature must be in the range [0.0, 2.0].` | same | same | same | same | same |97| topP 0.9 | OK | OK | OK | OK | OK | 500 INTERNAL (flaky) |98| topK 40 | — | OK | OK | OK | OK | OK |99| seed 42 | — | OK | OK | OK | OK | OK |100| stopSequences ["DONE"] | — | OK (stopped before DONE) | OK | OK | OK | 500 INTERNAL (flaky) |101| frequencyPenalty 0.5 | — | 400 `Penalty is not enabled for this model` | same | same | same | same |102| presencePenalty 0.5 | — | 400 `Penalty is not enabled for this model` | same | same | same | same |103| candidateCount 2 | 400 `Multiple candidates is not enabled for this model` | same | same | same | same | same |104| responseMimeType application/json | — | OK | OK | OK | OK | 500 (flaky) |105| responseSchema (OpenAPI, `OBJECT/STRING`) | — | OK | OK | OK | OK | OK |106| responseJsonSchema (JSON Schema) | — | OK | OK | OK | OK | OK |107| responseMimeType text/x.enum + enum schema | — | OK (`blue`) | OK | OK | OK | OK |108| thinkingConfig.thinkingBudget 0 | — | OK (0 thoughts) | 400 `Request contains an invalid argument.` | OK | OK | 400 `Thinking budget is not supported for this model.` |109| thinkingBudget 1024 / -1 | — | — | OK / OK | OK / OK | OK / OK (-1 → 1436 thought tokens) | 400 same |110| includeThoughts true | — | OK (thought parts) | OK | OK | OK | 500 (flaky) |111| thinkingLevel MINIMAL | docs: **error** | — | OK (0 thoughts) | OK (0) | OK (0) | 500 (flaky) |112| thinkingLevel LOW / MEDIUM / HIGH | — | — | OK | OK | HIGH, MEDIUM OK; LOW — | 400 `Thinking level is not supported for this model.` (HIGH → 500) |113| thinkingLevel + thinkingBudget | — | — | 400 `You can only set only one of thinking budget and thinking level.` | same | same | same |114| responseLogprobs/logprobs | 400 `Logprobs is not enabled for this model` | — | same | same | — | same |115116Notes:117- **Sampling deprecation**: changelog 2026-07-21 — "The sampling parameters `temperature`, `top_p` and `top_k` are now deprecated" (for 3.6 Flash / 3.5 Flash-Lite onward; the 3.8 guide says "Strip `temperature`, `top_p`, and `top_k` from generation configs"). They are still **accepted** by `generateContent` (probed) but Google recommends not sending them; Gemini 3 guide: "strongly recommend keeping the temperature parameter at its default value of 1.0" (lower values can cause looping). Adapter: default to not sending temperature/topP/topK for Gemini 3.x unless the user overrides.118- Default temperature/topP/topK reported by the models endpoint: 1 / 0.95 / 64 (maxTemperature 2) for all Gemini text models.119- `maxOutputTokens` **includes thinking tokens**: `gemini-3.8-flash` with `maxOutputTokens: 100` returned empty text, `finishReason: MAX_TOKENS`, `thoughtsTokenCount: 97`. Give thinking models a generous budget (>= 1024 + expected answer) or disable thinking.120- `stopSequences` are honoured but the stop string itself is stripped.121122## 5. Reasoning controls per family123124| Family | Default | Controls | Probe evidence |125|---|---|---|---|126| Gemini 3.8 / 3.7 Flash | `thinkingLevel` **medium** | `low, medium, high`; `minimal` → error (docs). `thinkingBudget` still accepted "for backward compatibility" per Gemini 3 guide but 3.8 guide says replace it. Cannot be disabled. | 3.8: thoughts 199–330 on trivial prompts. |127| Gemini 3.6 Flash | medium | `minimal, low, medium, high` | stream has thought part first. |128| Gemini 3.5 Flash | medium (docs) | all four levels (docs); `thinkingBudget: 0` **does** disable (probed). | thoughts 136–515 by default. |129| Gemini 3.5 Flash-Lite / 3.1 Flash-Lite | **minimal (off)** | all four levels; `thinkingBudget` 1024 / -1 OK; `thinkingBudget: 0` rejected on 3.5-lite (use MINIMAL) but OK on 3.1-lite. | thoughts 0 unless requested. |130| Gemini 3 Flash Preview / 3.1 Pro Preview | high (dynamic) | `minimal, low, medium, high`; budgets 0/1024/-1 OK on 3-flash-preview. | |131| Gemini 2.5 Pro / Flash / Flash-Lite | on / on / off | `thinkingBudget` (Pro 128–32768 cannot disable; Flash 0–24576; Lite 512–24576; -1 dynamic) + `thinkingLevel` low/medium/high (docs). | **Not callable with new keys (404)** — docs only. |132| Gemma 4 | thinks by default | **no control**: both `thinkingBudget` and `thinkingLevel` → 400. | thoughts 47–430. |133134- `includeThoughts: true` → thought **summaries** as parts with `thought: true` (streamed first). Raw reasoning is never returned.135- Thinking tokens: `usageMetadata.thoughtsTokenCount`, billed at the output rate.136- SDK enum: `ThinkingLevel.MINIMAL|LOW|MEDIUM|HIGH` (strings `"MINIMAL"`… accepted; Interactions API uses lowercase).137138Docs: https://ai.google.dev/gemini-api/docs/thinking , https://ai.google.dev/gemini-api/docs/gemini-3 , https://ai.google.dev/gemini-api/docs/latest-model139140### 5.1 Thought signatures (critical for tool calling)141- Gemini 3.x attaches `thoughtSignature` to the **functionCall part** (and to the last text/empty part of a text answer, and to `executableCode` parts).142- **Function calling is strict**: replaying the model turn without the signature → 400 `INVALID_ARGUMENT`: `Function call is missing a thought_signature in functionCall parts. This is required for tools to work correctly, and missing thought_signature may lead to degraded model performance. Additional data, function call `default_api:get_weather` , position 2. Please refer to https://ai.google.dev/gemini-api/docs/thought-signatures for more details.` (probed on gemini-3.7-flash).143- Echoing the real signature → OK. The documented escape hatch `thoughtSignature: "skip_thought_signature_validator"` on the functionCall part → accepted (probed) — useful when importing history from another provider.144- Text-only multi-turn **without** signatures works (probed): signatures on text parts are optional.145- Adapter rule: persist `thoughtSignature` alongside each tool call (and ideally each assistant part) in the conversation store and replay it verbatim in `contents`.146147## 6. Tool calling (function declarations)148149```ts150const tools = [{ functionDeclarations: [{151  name: "get_weather", description: "Get weather for a city.",152  parametersJsonSchema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] }, // JSON Schema (lowercase types)153  // or legacy: parameters: { type: "OBJECT", properties: { city: { type: "STRING" } }, required: ["city"] }154}]}];155// model turn → parts: [{ functionCall: { name, args, id: "call_88210" }, thoughtSignature }]156// reply:157contents.push({ role: "model", parts: modelPartsVerbatim });            // keep thoughtSignature!158contents.push({ role: "user", parts: [{ functionResponse: { name, id, response: { /* any JSON object */ } } }] });159```160- `toolConfig.functionCallingConfig`: `mode: AUTO | ANY | NONE | VALIDATED`, `allowedFunctionNames[]`. `ANY` + `allowedFunctionNames` probed OK on 3.6 Flash (forced call).161- Parallel calls arrive as multiple `functionCall` parts in one turn; send all `functionResponse` parts in one user turn (same order).162- `functionCall.id` is present on 3.x (`call_NNNNN`); echo it in `functionResponse.id`.163- Built-in tools **can be combined with function declarations on Gemini 3** (not on 2.5). Structured output + tools also allowed on Gemini 3.164- SDK automatic function calling exists (`config.automaticFunctionCalling`) — disable it in a UI adapter (`{ disable: true }`) to keep control of the loop.165- MCP: `mcpToTool(client)` in the SDK (experimental) and server-side `tools:[{ mcpServers: [...] }]` (v1beta, docs: HTTP transport only).166167Docs: https://ai.google.dev/gemini-api/docs/function-calling (now Interactions-only; legacy shapes verified by probe and SDK types)168169## 7. Structured output / JSON schema170- `responseMimeType: "application/json"` alone → valid JSON of free shape (one model returned an array).171- `responseJsonSchema` (standard JSON Schema: lowercase types, `additionalProperties`, `anyOf`, `$ref:"#"`, `enum`, `format` date/time, `minimum/maximum`, `items/prefixItems/minItems/maxItems`) → **preferred**; probed OK on all callable models incl. Gemma 4.172- `responseSchema` (legacy OpenAPI `Schema` with uppercase `TYPE`s, `propertyOrdering`) → still OK. Don't send both.173- `responseMimeType: "text/x.enum"` + `{type:"STRING", enum:[…]}` → bare enum value (probed).174- Works together with thinking (JSON is in non-thought parts) and, on Gemini 3, with tools.175176Docs: https://ai.google.dev/gemini-api/docs/structured-output177178## 8. Modalities179| Modality | How | Limits / notes |180|---|---|---|181| Image in | `inlineData {mimeType, data(base64)}` or `fileData {fileUri}`; PNG/JPEG/WebP/HEIC/HEIF; inline request total <= 20 MB; up to 3,600 images | Probed 2x2 PNG → `promptTokensDetails IMAGE 1089` tokens on 3.8/3.5 Flash (Gemini 3 default media resolution is high; docs' "258 tokens" applies to older models / low res). Use `mediaResolution` to cut cost. |182| PDF | `inlineData application/pdf` or Files API; <= 50 MB, <= 1000 pages, ~258 tokens/page | Not probed. |183| Audio in | wav/mp3/aiff/aac/ogg/flac/m4a/opus/webm…; 32 tokens/s; <= 9.5 h/prompt; >20 MB via Files API | Not probed. |184| Video in | inline <100 MB, Files API up to 2 GB (free) / 20 GB (paid), YouTube URLs via `fileData.fileUri`; ~100 tok/s low / 300 tok/s high res; 3.5+ have "agentic" video mode | Not probed. |185| Audio out | **Not via chat models.** TTS models (`gemini-3.1-flash-tts-preview`, `gemini-2.5-*-preview-tts`) with `responseModalities:["AUDIO"]` + `speechConfig` → `inlineData audio/L16 24 kHz PCM`; 32k context; Live API (`bidiGenerateContent`, WebSocket) for realtime voice. | Not probed. |186| Image out | Image models only (`gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image`, `gemini-3-pro-image`, `gemini-2.5-flash-image`), `responseModalities:["TEXT","IMAGE"]`, `imageConfig {aspectRatio, imageSize "512px"/"1K"/"2K"/"4K"}` → `inlineData image/png`; SynthID watermark; priced per image (~$0.045–0.151). | Not probed. |187| Video out | `gemini-omni-1.1-flash` / `gemini-omni-flash-preview` (paid tier only), Veo 3.1 via `predictLongRunning`. | Not chat models. |188189Docs: image-understanding, document-processing, audio, video-understanding, speech-generation, image-generation, files pages under https://ai.google.dev/gemini-api/docs/190191## 9. Files API192`ai.files.upload({file, config:{mimeType}})` → `{uri, mimeType, state}`; poll until `ACTIVE`; reference with `fileData:{fileUri, mimeType}`. 48 h retention, 2 GB/file, 20 GB/project. Use when the request exceeds 20 MB (100 MB for video per newer docs). Not probed.193194## 10. System instruction195`config.systemInstruction` (string or `Content`) — probed OK on 3.5/3.6/3.7/3.8 Flash ("BLUE" uppercase obeyed). Not probed on Gemma 4 (historically Gemma rejected developer instructions on this API; Gemma 4 docs say the system role is now supported — verify before enabling).196197## 11. Conversation state198- `generateContent` is **stateless**: send the full `contents` history each turn (`ai.chats` is a client-side helper only).199- Server-side state exists only in the **Interactions API** (`previous_interaction_id`, `store` default true; paid tier retains 55 days, free 1 day). Probed `ai.interactions.create({model, input, generation_config:{thinking_level:"low"}, store:false})` → works with this key; response has `steps[]`, `output_text`, `usage{total_input_tokens,total_output_tokens,total_thought_tokens,total_cached_tokens,total_tool_use_tokens}`. Consider it for a later "Gemini v2" adapter; it lacks Batch, explicit caching and custom safety settings today.200201## 12. Context caching202- **Implicit** caching is on by default for 2.5+ (min 4,096 tokens on 3.x Flash / 3.1 Pro, 2,048 on 2.5); savings show as `usageMetadata.cachedContentTokenCount`; cached input billed at ~10 % (e.g. 3.8 Flash $0.075/M). Put static content first.203- **Explicit**: `ai.caches.create({model, config:{contents, systemInstruction, tools, ttl:"300s"|expireTime, displayName}})` → pass `config.cachedContent = cache.name`. Models with `createCachedContent` in `supportedGenerationMethods` (all Gemini text models; not Gemma/omni). Storage $0.50–4.50 /M tokens/hour. Probe on 2.5-flash hit the 404 (model gone); not re-probed.204205Docs: https://ai.google.dev/gemini-api/docs/caching , https://ai.google.dev/api/caching206207## 13. Built-in tools208- **Google Search grounding**: `tools:[{googleSearch:{}}]`. Response `candidates[0].groundingMetadata { webSearchQueries[], searchEntryPoint{renderedContent: HTML chip — must be displayed per ToS}, groundingChunks[{web:{uri,title}}], groundingSupports[{segment{startIndex,endIndex,text}, groundingChunkIndices[], confidenceScores[]}] }` (docs + SDK types; probe hit 429 on both attempts — **unverified**). Pricing: 3.x — 5,000 free requests/month shared, then $14/1,000; 2.5 — 1,500 free RPD then $35/1,000. Legacy `googleSearchRetrieval` only for 1.5.209- **URL context**: `tools:[{urlContext:{}}]`, <= 20 URLs, 34 MB each, `candidates[0].urlContextMetadata`; billed as input tokens.210- **Code execution**: `tools:[{codeExecution:{}}]` probed OK on 3.6 Flash → parts `executableCode{language:"PYTHON", code, id}` + `codeExecutionResult{outcome:"OUTCOME_OK", output, id}` (both also carry `thoughtSignature`), then text. 30 s runtime, matplotlib only for plots, billed as tokens.211- **File Search** (RAG): stores + `tools:[{fileSearch:{fileSearchStoreNames:[…]}}]`, citations with `media_id`/page numbers; indexing $0.15/M embedding tokens, storage free. Not probed.212- **Computer use**: `tools:[{computerUse:{environment:"browser"|"mobile"|"desktop"}}]` on 3.8/3.7/3.5 Flash(-Lite) (preview, documented for Interactions). Not probed.213- **Google Maps grounding**, **MCP servers** tool: v1beta. Not probed.214215## 14. Safety settings216`safetySettings:[{category, threshold}]`; categories `HARM_CATEGORY_HARASSMENT | HATE_SPEECH | SEXUALLY_EXPLICIT | DANGEROUS_CONTENT` (+ `CIVIC_INTEGRITY` in SDK enum); thresholds `OFF | BLOCK_NONE | BLOCK_ONLY_HIGH | BLOCK_MEDIUM_AND_ABOVE | BLOCK_LOW_AND_ABOVE`. Default for 2.5/3 models is **OFF**. Blocked prompt → `promptFeedback.blockReason` (`SAFETY|OTHER|BLOCKLIST|PROHIBITED_CONTENT|IMAGE_SAFETY`) with no candidates; blocked answer → `finishReason: SAFETY` + `safetyRatings`. Probe was routed to 2.5-flash-lite (404) — not re-probed. Not supported on the Interactions API.217218## 15. Rate limits and tiers219- Tiers: Free, Tier 1 (billing linked), Tier 2 ($100 spent + 3 days), Tier 3 ($1,000 + 30 days). Dimensions: RPM, TPM (input), RPD (resets midnight Pacific) + rolling 10-minute spend caps ($10/$50/$200). Per-model tables are only shown in AI Studio (`https://aistudio.google.com/rate-limit`), not in docs.220- Observed free-tier quotas (from 429 `google.rpc.QuotaFailure` details): `GenerateRequestsPerDayPerProjectPerModel-FreeTier` **quotaValue 20** for `gemini-3.8-flash`; per-minute quota trips after ~5–10 requests; `gemini-3.1-pro*`, `gemini-pro-latest`, `gemini-omni-*` → **limit 0** (paid only). Quotas are **per model**, so parallelising across models is fine.221- 429 body includes `details[]`: `google.rpc.Help`, `google.rpc.QuotaFailure{violations[{quotaMetric, quotaId, quotaDimensions{model,location}, quotaValue}]}`, `google.rpc.RetryInfo{retryDelay:"24s"}` → honour `retryDelay`.222- 503 `UNAVAILABLE` "This model is currently experiencing high demand…" was frequent on 3.8-flash and gemini-flash-latest (transient; retry with backoff). Gemma 4 returned 500 `INTERNAL` on ~50 % of calls (retry).223224Docs: https://ai.google.dev/gemini-api/docs/rate-limits225226## 16. Errors227Body: `{"error":{"code":<http>,"message":"…","status":"<grpc status>","details":[…]}}`. Seen:228229| HTTP | status | Example |230|---|---|---|231| 400 | INVALID_ARGUMENT | temperature range, `Penalty is not enabled for this model`, `Multiple candidates is not enabled for this model`, `Logprobs is not enabled for this model`, `You can only set only one of thinking budget and thinking level.`, missing thought_signature, `Request contains an invalid argument.` |232| 401 | UNAUTHENTICATED | invalid key: `Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential…`, `details[0].reason: "ACCESS_TOKEN_TYPE_UNSUPPORTED"`. (Docs list 400 `API_KEY_INVALID` for malformed keys — with `AQ.`-style bogus key we got 401.) |233| 403 | PERMISSION_DENIED | key lacks permission / wrong project (docs) |234| 404 | NOT_FOUND | unknown model: `models/gemini-9-ultra is not found for API version v1beta, or is not supported for generateContent. Call ModelService.ListModels…`; **retired-for-new-users**: `This model models/gemini-2.5-flash is no longer available to new users. Please update your code to use models/gemini-3.6-flash for the latest features and improvements. We recommend you to use the Interactions API.` |235| 429 | RESOURCE_EXHAUSTED | quota (see §15) |236| 500 | INTERNAL | `Internal error encountered.` (Gemma 4, transient) |237| 503 | UNAVAILABLE | high demand |238| 504 | DEADLINE_EXCEEDED | docs |239240Retry: exponential backoff with jitter on 429/500/503/504 (and network), honour `RetryInfo.retryDelay`; never retry 400/401/403/404. Streaming errors mid-stream: SDK throws from the iterator. Timeouts: set `httpOptions.timeout` (ms); long outputs with thinking can take >60 s — use 120 s+ for streaming.241242Docs: https://ai.google.dev/gemini-api/docs/troubleshooting , https://ai.google.dev/gemini-api/docs/api-errors (now describes Interactions snake_case codes)243244## 17. Token counting245`ai.models.countTokens({model, contents})` → `{totalTokens}` (free, no quota cost documented). REST `:countTokens` with `generateContentRequest{contents, systemInstruction, tools}` counts everything (probed: 11 tokens text-only → 51 with system + tool) and returns `promptTokensDetails`. Rules of thumb: ~4 chars/token; image 258 tokens (<=384 px) or per 768 px tile — but Gemini 3 high res gave 1,089 for a tiny PNG; audio 32 tok/s; video ~100–300 tok/s.246247## 18. Model listing248`GET /v1beta/models?pageSize=1000` (paginated via `nextPageToken`; SDK `ai.models.list()` / `ai.models.get({model})`). 54 models on 2026-09-08. Fields: `name`, `version`, `displayName`, `description`, `inputTokenLimit`, `outputTokenLimit`, `supportedGenerationMethods[]` (SDK renames to `supportedActions`), `temperature`, `maxTemperature`, `topP`, `topK`, `thinking` (boolean). Filter chat models with `supportedGenerationMethods.includes("generateContent")` and exclude by name pattern (`-image`, `-tts`, `lyria`, `veo`, `transcribe`, `live`, `native-audio`, `robotics`, `computer-use`, `deep-research`, `antigravity`, `embedding`, `aqa`). **The list includes models the key cannot call** (2.5 family → 404 for new users; paid-only → 429 limit 0), so the picker must tolerate per-model failures.249250## 19. Pricing (paid tier, USD per 1M tokens; pricing page 2026-09-08)251| Model | Input | Cached | Output (incl. thinking) |252|---|---|---|---|253| 3.8 / 3.7 / 3.6 Flash | 0.75 (1.50 from 2027-01-01) | 0.075 (0.15) | 3.75 (7.50) |254| 3.5 Flash | 1.50 | 0.15 | 9.00 |255| 3.5 Flash-Lite | 0.30 | 0.03 | 2.50 |256| 3.1 Flash-Lite | 0.25 (audio 0.50) | n/a | 1.50 |257| 3 Flash Preview | 0.50 (audio 1.00) | n/a | 3.00 |258| 3.1 Pro Preview | 2.00 / 4.00 (>200k) | 0.20 / 0.40 | 12.00 / 18.00 |259| 2.5 Pro | 1.25 / 2.50 | 0.125 / 0.25 | 10.00 / 15.00 |260| 2.5 Flash | 0.30 (audio 1.00) | 0.03 | 2.50 |261| 2.5 Flash-Lite | 0.10 (audio 0.30) | 0.01 | 0.40 |262| Omni Flash | 1.50 | n/a | 9.00 text / 17.50 video |263| Gemma 4 | free (free tier only; "Not available" on paid) | | |264Batch = 50 % off; priority tier = 1.8x; cache storage $0.50–4.50 /M/h; Search grounding see §13. Free tier: prompts may be used for product improvement.265266Docs: https://ai.google.dev/gemini-api/docs/pricing267268## 20. Lifecycle, aliases, deprecations269- Stable ids don't change (`gemini-3.6-flash`); previews get >= 2 weeks' notice; `-latest` aliases are hot-swapped with a 2-week email notice: `gemini-flash-latest` → 3.5 Flash (changelog), `gemini-flash-lite-latest` → 3.5 Flash-Lite (probed), `gemini-pro-latest` → 3.1 Pro (quota dimension).270- Shut down: all `gemini-2.0-*` (2026-06-01), `gemini-3.1-flash-lite-preview` (2026-05-25; the id still answers as 3.1-flash-lite), 2.5 previews. **Gemini 2.5 Pro/Flash/Flash-Lite are closed to new users** (404) though still listed and priced.271- Preview shutdown windows are 3–9 months. Legacy JS SDK deprecated 2025-11-30. Standard API keys rejected after Sept 2026.272273Docs: https://ai.google.dev/gemini-api/docs/deprecations , https://ai.google.dev/gemini-api/docs/changelog , https://ai.google.dev/gemini-api/docs/models274275## 21. Probe results summary276277| Probe | 3.8-flash | 3.7-flash | 3.6-flash | 3.5-flash | 3.5-flash-lite | 3.1-flash-lite | 3-flash-preview | gemma-4-31b-it | 3.1-pro-preview | 2.5-flash / 2.5-pro | omni-1.1-flash |278|---|---|---|---|---|---|---|---|---|---|---|---|279| generateContent + systemInstruction | OK | OK | OK | OK | OK (no sysinstr) | OK (no sysinstr) | OK (no sysinstr) | OK (no sysinstr) | 429 limit 0 | 404 gone | 429 limit 0 |280| stream (chunk shape, usage) | 503 then quota | OK 3 chunks, usage full each | OK 4 chunks | OK 3 chunks | — | — | — | — | — | 404 | — |281| params matrix | partial (RPD 20) | — | — | most | full | full | most | full (flaky 500s) | — | 404 | — |282| function call stream + round trip | 503/429 | OK (+signature tests) | OK | OK | OK | OK | — | — | — | 404 | — |283| responseJsonSchema | — | — | OK | OK | OK | OK | OK | OK | — | 404 | — |284| vision (inline PNG) | OK 1089 img tokens | — | — | OK | — | — | — | — | 429 | 404 | 429 |285| googleSearch grounding | 429 | — | 429 | — | — | — | — | — | — | 404 | — |286| codeExecution | 429 | — | OK | — | — | — | — | — | — | — | — |287| invalid key | 401 UNAUTHENTICATED (REST + SDK) | | | | | | | | | | |288| countTokens / models.get / v1 apiVersion / interactions.create | OK / OK / OK / OK | | | | | | | | | | |289290Not probed (docs only): audio/video/PDF input, Files API, explicit caching, safety settings behaviour, TTS/image output, Live API, File Search, computer use, MCP, Batch, 3.1 Pro & omni anything (paid tier), 2.5 anything (closed to new users).291292## 22. Gotchas for the adapter (checklist)2931. Use `x-goog-api-key`, never Bearer. Expect 401 (not 400/403) for bad keys.2942. Filter the model list by `generateContent` + name patterns, and **probe-or-handle 404 "no longer available to new users"** (2.5 family) and 429 "limit: 0" (paid-only) gracefully — hide or badge those models.2953. Gemini 3.x: prefer `thinkingLevel` (uppercase enum in SDK), never send both level and budget, `MINIMAL` errors on 3.7/3.8, Gemma rejects both. Map PolyLLM "reasoning effort" → level; "off" → `MINIMAL` on lite/3.5-flash-lite, `thinkingBudget: 0` on 3.5 Flash / 3.1-lite / 3-flash-preview, and "not disableable" on 3.6+/Pro.2964. `maxOutputTokens` includes thoughts — floor it (e.g. >= 2048) when thinking is on, or you get empty text with `MAX_TOKENS`.2975. Store and replay `thoughtSignature` on tool-call parts (400 otherwise); `skip_thought_signature_validator` is the documented escape hatch. Strip empty signature-only text parts from the UI but keep them in history.2986. Usage: read `usageMetadata` from the last chunk; thoughts = `thoughtsTokenCount`, cache hits = `cachedContentTokenCount`.2997. Penalties, `candidateCount > 1`, logprobs → 400 on every model: don't expose them for Gemini. Temperature/topP/topK are accepted but deprecated for 3.6+; don't send by default.3008. SSE events are CRLF-delimited `data:` lines with no terminator; HTTP error status can arrive with `text/event-stream` content type.3019. Quotas are per model; free tier is tiny (20 RPD on 3.8-flash) — surface `RetryInfo.retryDelay` to the user.30210. Consider a future switch to the Interactions API (server-side state, unified steps); today `generateContent` is stable and complete for chat.303