From 0b3d98d91b82156fe05ac1a6b8ee9f088cf94176 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sat, 29 Aug 2026 12:16:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(stats):=20token=20=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BC=93=E5=AD=98=E5=91=BD=E4=B8=AD=E8=BF=BD?= =?UTF-8?q?=E8=B8=AA=E4=B8=8E=E5=91=BD=E4=B8=AD=E7=8E=87=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - utils/token_usage.py:usage 归一化补全缓存命中字段提取,覆盖 prompt_tokens_details/input_tokens_details.cached_tokens(OpenAI 系)、 顶层 prompt_cache_hit_tokens(DeepSeek)、顶层 cached_tokens(Kimi/Step)、 cache_read_input_tokens(Anthropic 系)、cachedContentTokenCount(Gemini); Anthropic 语义下把缓存读/写加回总输入以统一口径,normalize 保持幂等 - 对话级统计新增 total_cached_input_tokens 与 cache_exempt_input_tokens (首轮/深度压缩后首轮未命中缓存的输入视为冷启动成本,豁免出命中率分母; 压缩通过 cache_cold_start_pending 标记在下一次真实调用时判定) - token_update 广播与 token-statistics 接口同步携带新字段 - TokenDrawer 面板新增「累积缓存输入」「缓存命中率」(前端按 缓存/(总输入-豁免) 换算) - 深色模式下「当前上下文」数字由灰色 --accent 改为 --text-primary(白) - 附 cache_research/ 各厂商缓存字段调研文档(代码注释引用) --- cache_research/SUMMARY.md | 84 + cache_research/aggregators/report.md | 297 + cache_research/official_china/README.md | 304 + .../official_china/usage_fields_reference.md | 91 + .../_src/gemini-generate-content-api.txt | 6810 +++++++++++++++++ cache_research/official_overseas_v2/report.md | 422 + server/deep_compression.py | 3 + static/src/app/methods/taskPolling/sync.ts | 2 + static/src/components/token/TokenDrawer.vue | 25 + static/src/composables/useLegacySocket.ts | 2 + static/src/locales/en-US/sidebar.ts | 2 + static/src/locales/zh-CN/sidebar.ts | 2 + static/src/stores/resource.ts | 14 +- .../components/panels/_resource-panel.scss | 8 + utils/context_manager/token_mixin.py | 11 +- utils/conversation_manager/metadata_mixin.py | 10 +- utils/conversation_manager/token_mixin.py | 25 + utils/token_usage.py | 79 +- 18 files changed, 8178 insertions(+), 13 deletions(-) create mode 100644 cache_research/SUMMARY.md create mode 100644 cache_research/aggregators/report.md create mode 100644 cache_research/official_china/README.md create mode 100644 cache_research/official_china/usage_fields_reference.md create mode 100644 cache_research/official_overseas/_src/gemini-generate-content-api.txt create mode 100644 cache_research/official_overseas_v2/report.md diff --git a/cache_research/SUMMARY.md b/cache_research/SUMMARY.md new file mode 100644 index 00000000..878cb3d4 --- /dev/null +++ b/cache_research/SUMMARY.md @@ -0,0 +1,84 @@ +# LLM API 缓存命中 Token 字段调研 · 总汇总 + +> 调研时间:2026-08-29 · 目的:为「验证各家 API 是否返回缓存命中 token」实验提供字段对照 +> 详细报告:`official_overseas_v2/report.md`(海外官方)、`official_china/README.md`(国内官方)、`aggregators/report.md`(聚合层) +> ⚠️ 全部为官方文档 + 社区证据调研结论,**未经实请求验证**;实验时以实际返回为准。 + +--- + +## 一、缓存命中字段总对照表 + +### 官方 API + +| 提供商 | 命中字段完整路径 | 写入字段 | 自动/显式 | 最低门槛 | 命中价折扣 | +|---|---|---|---|---|---| +| OpenAI Chat Completions | `usage.prompt_tokens_details.cached_tokens` | `...cache_write_tokens`(GPT-5.6+) | 自动(5.6+ 可显式断点) | 1024(5.5 及更早 2048) | 读 0.1×(5.6+)/ 0.5×(老模型) | +| OpenAI Responses API | `usage.input_tokens_details.cached_tokens` | `...cache_write_tokens` | 同上 | 同上 | 同上 | +| Anthropic Claude | `usage.cache_read_input_tokens`(顶层) | `usage.cache_creation_input_tokens`(另有 `cache_creation.ephemeral_5m/1h_input_tokens` 细分) | **显式** `cache_control` | 按模型 512/1024/2048/4096 | 读 0.1×、写 1.25×(5m)/ 2×(1h) | +| Google Gemini | `usageMetadata.cachedContentTokenCount`(SDK:`cached_content_token_count`) | 无 usage 内写入字段(显式缓存按资源 TTL 计费) | 隐式自动 + 显式 cachedContents | 隐式 2048(2.5)/ 4096(3.x) | 命中 ~0.1×(2.5+) | +| xAI Grok | `usage.prompt_tokens_details.cached_tokens`(Responses:`input_tokens_details.cached_tokens`) | 无 | 自动(建议 `x-grok-conv-id`/`prompt_cache_key`) | 未公布 | 有缓存价 | +| Mistral | `usage.prompt_tokens_details.cached_tokens` | 无 | 半显式(建议 `prompt_cache_key`) | 64 tokens 起,恒为 64 的倍数 | 读 0.1× | +| **DeepSeek** | **`usage.prompt_cache_hit_tokens`(顶层!)** + `prompt_cache_miss_tokens` | 无(自动) | 自动 | 未公布 | **读 ≈0.03×($0.014 vs $0.44,折扣最大)** | +| **Kimi / Moonshot** | **`usage.cached_tokens`(顶层)**;部分官方示例为 `prompt_tokens_details.cached_tokens`——**两处都要读** | 无 | 自动(可用请求参数 `prompt_cache_key` 提命中率) | 未公布 | 读 0.1×~0.2×(k3 为 0.1×) | +| Qwen / DashScope | `usage.prompt_tokens_details.cached_tokens`;显式另有 `cache_creation_input_tokens`;Anthropic 兼容模式为 `cache_read_input_tokens` | 显式时上报创建量 | 隐式自动 + 显式 `cache_control` | 隐式 256(部分模型 2000)/ 显式块 1024 | 隐式读 0.2×;显式读 0.1×、写 1.25× | +| 智谱 GLM | `usage.prompt_tokens_details.cached_tokens` | 无 | 自动 | 512 | 读 0.5× | +| 豆包 / 火山方舟 | `usage.prompt_tokens_details.cached_tokens` | 创建接口响应同路径 | **仅显式**(Context API / Responses API `caching` 参数) | — | 缓存输入折扣价 + 存储费 | +| MiniMax | OpenAI 模式:`prompt_tokens_details.cached_tokens`;Anthropic 模式:`cache_read_input_tokens` | Anthropic 模式:`cache_creation_input_tokens` | 自动 + 显式(Anthropic 模式) | 512 | 读 0.1×~0.2× | +| 阶跃 Step | **`usage.cached_tokens`(顶层)** | 无 | 自动 | 256 | 读 0.2× | +| 百度千帆 | `usage.prompt_tokens_details.cached_tokens` | 无 | 自动 | 未公布 | 读 0.4× | + +### 聚合层 / 中转(实验时最容易踩坑的一层) + +| 服务 | 缓存字段行为 | 关键坑 | +|---|---|---| +| **OpenRouter** | 规范化为 `usage.prompt_tokens_details.cached_tokens` + 扩展 `cache_write_tokens` / `cache_discount` / `cost` | ⚠️ 它另有「响应缓存」`X-OpenRouter-Cache-Status: HIT`——命中时 **usage 全为 0**,与 prompt 缓存是两回事;个别上游(如 DeepSeek)缓存不过网关 | +| **opencode Zen / Go** | Zen 价格表单列 Cached Read/Write(必然解析了上游缓存字段);「opencode go」= **$10/月订阅服务**,非 Go 语言版 | ⚠️ opencode 客户端流式解析有 bug(#33997):`tokens_cache_read` 恒 0——别看客户端展示值,抓原始 SSE | +| **one-api / new-api / one-hub** | 意图透传 `cached_tokens`,但流式渠道多个已证实 bug(字段清零/计费错误/负 token) | ⚠️ 客户端收到的 usage ≠ 网关账单;非流式作基线对照 | +| **国内中转站(packycode、灵眸AI 等)** | 口碑「官转」站透传 Anthropic 原生 `cache_creation/read_input_tokens` 并按 5m cache write 计费;逆向接口站无缓存 | 社区验收标准=响应 usage 里有没有这两个字段 | +| **LiteLLM / Portkey / CF AI Gateway** | LiteLLM 双格式并存但 Anthropic 透传路径有 bug;Portkey 明确规范化;CF 未文档化(推测透传) | LiteLLM `/v1/messages` 路径不映射 `cached_tokens`(#27763) | +| **订阅制(Copilot/Cursor/Windsurf/Augment)** | 无公开 per-request usage API;Cursor/Augment 面板展示 cache read/write(数据来自上游响应) | 无法从响应侧做本实验,跳过 | + +--- + +## 二、实验用统一读取器(Python 伪代码) + +```python +def extract_cache_hit(usage: dict, body: dict | None = None) -> dict: + """按优先级从各家 usage 中提取缓存命中 token 数。""" + u = usage or {} + details = u.get("prompt_tokens_details") or {} + in_details = u.get("input_tokens_details") or {} + candidates = [ + ("prompt_cache_hit_tokens", u.get("prompt_cache_hit_tokens")), # DeepSeek(顶层) + ("cached_tokens@top", u.get("cached_tokens")), # Kimi / Step / 部分 DashScope(顶层) + ("prompt_tokens_details", details.get("cached_tokens")), # OpenAI Chat / Qwen / GLM / MiniMax / 千帆 / xAI / Mistral / OpenRouter + ("input_tokens_details", in_details.get("cached_tokens")), # OpenAI/xAI Responses API + ("cache_read_input_tokens", u.get("cache_read_input_tokens")), # Anthropic / Bedrock / MiniMax-Anthropic / 中转站 + ] + hit = next(((k, v) for k, v in candidates if v), (None, 0)) + # Gemini 走完全独立的 usageMetadata(camelCase),从响应体而非 usage 取 + gemini = ((body or {}).get("usageMetadata") or {}).get("cachedContentTokenCount") + return {"hit_tokens": hit[1] or gemini or 0, "field": hit[0] or ("usageMetadata" if gemini else None)} +``` + +--- + +## 三、实验设计要点(三份报告的共同结论) + +1. **两轮法**:第 1 轮建缓存(命中=0 或走写入字段),第 2 轮同前缀不同后缀(命中>0)。两轮间隔必须在缓存 TTL 内(Anthropic/Qwen 显式 = 5 分钟)。 +2. **前缀 ≥2048 tokens**,避开各家阈值差异(256~4096 不等)。 +3. **流式必须 `stream_options: {"include_usage": true}`**,否则 OpenAI 系协议流式响应没有 usage chunk;Kimi 流式末 chunk 带 usage;Anthropic 看 `message_start` 事件。 +4. **语义差异**:OpenAI 系 `prompt_tokens` **包含**缓存部分;Anthropic `input_tokens` **不含**缓存部分(cache_read 另算)。对账时别混。 +5. **区分两种「缓存」**:网关级响应缓存(result cache,命中时 usage 可能归零)≠ prompt 前缀缓存(KV cache,本实验目标)。 +6. **聚合层要抓三个视图**:客户端响应 usage、网关账单/消费日志、可直连时的上游原生 usage——三者可能互不一致(new-api #6144 教训)。 +7. **首轮 `cache_read=0` 是预期行为**,不是字段丢失;写入字段(`cache_creation_input_tokens` / `cache_write_tokens`)>0 反而证明缓存机制在运作。 + +--- + +## 四、详细报告索引 + +| 报告 | 路径 | 覆盖 | +|---|---|---| +| 海外官方 | `official_overseas_v2/report.md` | OpenAI / Anthropic / Gemini / xAI / Mistral / Bedrock / Azure | +| 国内官方 | `official_china/README.md` + `usage_fields_reference.md` | DeepSeek / Kimi / Qwen / GLM / 豆包 / MiniMax / Step / 千帆 | +| 聚合层 | `aggregators/report.md` | OpenRouter / opencode Zen·Go / one-api·new-api·one-hub / 中转站 / Copilot·Cursor·Windsurf·Augment / LiteLLM·Portkey·CF | diff --git a/cache_research/aggregators/report.md b/cache_research/aggregators/report.md new file mode 100644 index 00000000..0be60f75 --- /dev/null +++ b/cache_research/aggregators/report.md @@ -0,0 +1,297 @@ +# 聚合层调研报告:聚合 API / 中转服务 / coding plan 的「缓存命中 token」字段透传情况 + +- 撰写时间:2026-08-29 +- 调研人:子智能体 #3(聚焦聚合层) +- 配套调研(其他子智能体负责):官方海外 API(OpenAI/Anthropic/DeepSeek 等)、官方国内 API +- **重要说明**:本领域大量结论来自 GitHub issue、论坛/社区讨论而非官方文档。每条结论都标注了证据等级: + - **官方文档**:服务方官方文档/博客 + - **官方源码**:服务方开源仓库源码(本文直接读取了 new-api 的 `relay/channel/openai/helper.go`) + - **Issue 讨论**:GitHub issue / 论坛讨论(含用户实测) + - **第三方调研**:独立第三方测评/文档(如 cuihuan/awesome-ai-gateway 的逐 commit 源码审查) + - **社区讨论**:LINUX DO、Cursor 论坛、Reddit 等社区帖子 + - **推测**:无直接证据,基于已有事实的合理推断;此类结论已明确标注「推测」 + - 未找到明确证据的,一律写「未找到证据」。 + +--- + +## 1. 总览对照表 + +| 服务 | 是否透传/保留缓存字段 | 字段格式 / 重命名情况 | 流式中的表现 | 计费显示 | 证据等级 | 来源 | +|---|---|---|---|---|---|---| +| **OpenRouter** | ✅ 保留并**统一规范化**为 OpenAI 风格 | `usage.prompt_tokens_details.cached_tokens` + 自有扩展 `cache_write_tokens`、`cache_discount`、`cost`、`cost_details` | 需 `stream_options.include_usage=true`;末 chunk 带回 usage(官方格式);**OpenRouter 自身的响应缓存 HIT 时 usage 全为 0** | ✅ `usage.cost` 会按缓存读取折扣计价;`cache_discount` 表示本 generation 的缓存折扣;Activity 页与 `/api/v1/generation` 可查 | 官方文档 | [OpenRouter chat completion 文档](https://openrouter.ai/docs/api/api-reference/chat/create-a-chat-completion)、[Prompt Caching 教程博客](https://openrouter.ai/blog/tutorials/prompt-caching-sticky-routing)、[Response caching 文档](https://openrouter.ai/docs/guides/features/response-caching) | +| **opencode(开源 agent)** | 客户端**解析**用法字段(含缓存),但 TUI 默认不显示 | `session.tokens_cache_read` / `info.tokens.cache.read` | 已知 bug:OpenAI-compatible 流式路径下 `tokens_cache_read` 恒为 0(上游明明返回了 `cached_tokens`),#33997 | opencode 内部按模型计费;TUI 不展示缓存明细(有多个第三方插件补足) | 官方源码(基于 issue 定位) + Issue 讨论 | [anomalyco/opencode#33997](https://github.com/anomalyco/opencode/issues/33997)、[#34296](https://github.com/anomalyco/opencode/issues/34296)、[#13003](https://github.com/anomalyco/opencode/issues/13003) | +| **opencode Zen(PAUG 网关)** | 见下;同时提供 OpenAI 兼容 / Anthropic 兼容 / Gemini 兼容端点;**官方价格表单独列出 Cached Read / Cached Write 两列**(按模型计费,说明其必然解析上游缓存字段) | 端点协议原生格式(`v1/chat/completions` 走 OpenAI 格式,`v1/messages` 走 Anthropic 格式) | 未找到官方对流式 usage 的专门描述 | ✅ 官方按 Cached Read/Write 定价 | 官方文档(价格表)+ 第三方(Bifrost 文档,见下) | [opencode.ai/docs/zen](https://opencode.ai/docs/zen)、[docs.getbifrost.ai OpenCode 页](https://docs.getbifrost.ai/providers/supported-providers/opencode) | +| **opencode Go(订阅)** | 见下;「opencode go」= OpenCode Go 订阅服务($5 首月/$10 每月),**不是**「Go 语言版本」 | 同上 | 同上 | 订阅制,固定月费 + 用量限额,**不按缓存计费** | 官方文档 | [opencode.ai/docs/go](https://opencode.ai/docs/go)、[opencode.ai/zh/go](https://opencode.ai/zh/go) | +| **one-api(songquanpeng)** | 大体透传上游 OpenAI 格式 usage;**计费模型不含缓存折扣**(`额度 = 分组倍率 × 模型倍率 × (提示 token + 补全 token × 补全倍率)`) | OpenAI 风格(其主干只做 OpenAI 兼容转发) | 依赖 `stream_options.include_usage`(README 中有可选 env `ENFORCE_INCLUDE_USAGE`) | ❌ 计费不区分缓存命中;缓存 token 按全价输入计 | 第三方调研(逐 commit 源码审查)+ 官方 README | [awesome-ai-gateway virtual-keys-metering](https://github.com/cuihuan/awesome-ai-gateway/blob/main/docs/virtual-keys-metering.zh-CN.md)、[one-api README](https://github.com/songquanpeng/one-api) | +| **new-api(QuantumNous)** | ✅ 转发路径基本保留缓存字段(OpenAI 渠道流式 `*usage = lastStreamResponse.Usage` 整体拷贝);**但存在多个已证实的 bug**:自定义渠道/火山方舟流式把 `cached_tokens` 打成 0(#5672);xAI 渠道流式转发对但内部计费 usage 损坏(#6144);缓存命中导致输入 token 变负数(#5003/#5005);缓存写入 token 未计费(#6353) | OpenAI 风格 `prompt_tokens_details.cached_tokens`;清理/重建 usage 时会注入大量默认字段(`text_tokens/audio_tokens/claude_cache_creation_*` 等) | 多个渠道的流式 usage 处理有 bug(见上);「透传模式」直连上游→字段原样 | ⚠️ 内部计费有 `CacheRatio` + `CacheCreationRatio`(5m/1h 拆分),但多个 bug 导致缓存计费错误甚至倒扣 | 官方源码 + Issue 讨论 + 第三方调研 | new-api#6144、#5672、#5003、#6353;源码 `relay/channel/openai/helper.go`;awesome-ai-gateway 文档 | +| **one-hub(MartialBE)** | ✅ 基本透传;**曾被证实 Responses API 的 `cached_tokens` 因 `omitempty` 标签被省略**,导致 Codex CLI 报 `missing field 'cached_tokens'`,已修复(PR #910) | OpenAI 风格 | Responses SSE 的 `input_tokens_details.cached_tokens` 曾缺失(已修复) | v0.14.26 起为 Bedrock 渠道的 Claude 增加 prompt caching 支持;计费沿用 one-api/new-api 体系 | Issue/PR 讨论 + Release 说明 | [one-hub PR #910](https://github.com/MartialBE/one-hub/pull/910)、[Release v0.14.26](https://github.com/MartialBE/one-hub/releases) | +| **国内中转站(packycode、灵眸AI 等)** | 参差不齐:宣称「官转」的站会解析并透传 usage(packycode 明说「透传用户的请求…解析 claude 传来的 usage tokens」);部分站(逆向接口)不缓存 | Anthropic 原生格式(Claude Code 场景)或 OpenAI 风格 | 实测有的站「完整透传 `cache_creation_input_tokens` / `cache_read_input_tokens`」(灵眸AI) | ⚠️ 中转站按 usage 计费,且**默认按 5m Cache Write 计缓存**(packycode);缓存命中占比极高(用户实测 82.9% cache read) | 社区讨论 | LINUX DO 帖、fulitimes 博客,见 §5 | +| **GitHub Copilot** | 终端用户**拿不到 per-request usage**(订阅制)。订阅用量属 token 配额制(2026-06 起转 token 计费);企业版 REST metrics API 只给每日聚合 `prompt_tokens_sum/output_tokens_sum`,**无缓存拆分**;VS Code 的 OTLP 指标不暴露 cached input | 其内部 OpenAI 兼容后端 SSE **会**把 `prompt_tokens_details.cached_tokens` 与 DeepSeek 原生 `prompt_cache_hit_tokens` 透给客户端(社区实测,free 计划 DeepSeek) | 同上(社区实测见原始 SSE) | 订阅/token 配额内,无 per-request 缓存折扣展示 | 官方文档 + Issue/社区实测 | GitHub REST Copilot metrics 文档、microsoft/vscode#317837、obsidian-copilot discussion #2380 | +| **Cursor** | 订阅与 BYOK 的用量面板都**展示 Cache Read / Cache Write**(官方客服口径:usage 报告里显示的是「AI provider 随响应返回的精确 token」);BYOK 直连时缓存字段来自 Anthropic/OpenAI | Anthropic/OpenAI 原生 | 多个论坛帖证实 Auto 模式曾路由到不支持缓存的模型导致 cache=0(版本问题) | ✅ 面板单列 Cache Read/Write 并计费(cache read 价约输入价 10%) | 社区讨论(官方客服回复)+ 官方文档未直接确认 | Cursor 论坛帖,见 §6 | +| **Windsurf** | 订阅/credits 制;**计量按 token 且明确区分 cache-read 单价**(如 Sonnet:input 90 credits/M、cache read 9 credits/M、output 450 credits/M),说明网关侧跟踪缓存 token | 不暴露原始 usage 给用户,走 credits 换算 | 未找到 per-request usage 暴露证据 | ✅ cache-read 以低价 credit 计费 | 第三方文档 + 官方价格说明 | flexprice.io、Windsurf 官方文档(见 §6) | +| **Augment Code** | token 计费制;官方文档明说「自动缓存稳定上下文,cached input 按供应商缓存价(约 10%)计费」,Usage 面板展示 input/output/cache read/cache write 单价 | 不暴露原始 usage 字段 | 未找到 | ✅ 缓存读取按折扣计费 | 官方文档 | [docs.augmentcode.com/models/token-based-pricing](https://docs.augmentcode.com/models/token-based-pricing) | +| **Cloudflare AI Gateway** | 作为透明代理转发(推测透传 usage);**官方文档未明确描述缓存 usage 字段的保留/规范化**;其自带「响应缓存」是网关级缓存(`cf-aig-cache-status: HIT/MISS`),与 prompt cache 是两回事;社区实测 `cache_control` 请求体能透传 | 上游协议原样 | 未找到官方文档 | 网关自己的日志/analytics 记录 token usage 供计费统计,不向调用方展示 | 官方文档(缓存功能)+ Issue 讨论(cache_control 透传) | Cloudflare AI Gateway docs、openclaw#46709 | +| **Portkey** | ✅ **明确规范化到 OpenAI 格式并保留缓存字段**:`prompt_tokens = input + cache_read + cache_creation`,`cached_tokens` 出现在 `prompt_tokens_details`|(Bedrock 场景有明确文档) | Portkey 透传模式下响应按供应商原样;其观测端展示 `cached_tokens` | ✅ 定价公式单独处理 base input / cache read / cache write | 官方文档 | [Portkey Bedrock Prompt Caching](https://docs.portkey.ai/docs/integrations/llms/bedrock/prompt-caching)、[Portkey docs](https://docs.portkey.ai/docs/integrations/llms/openai/prompt-caching-openai) | +| **LiteLLM** | ✅ OpenAI 兼容端点规范化到 OpenAI 风格 `prompt_tokens_details.cached_tokens`,同时在同一 usage 对象中保留 Anthropic 原生 `cache_creation_input_tokens` / `cache_read_input_tokens`;**但 Anthropic `/v1/messages` 透传路径不把原生字段映射到 `cached_tokens`,导致指标/计费不识别缓存(bug #27763)** | 双格式并存(OpenAI 风格 + Anthropic 原生) | 流式 usage 合成有历史 bug(如 synth chunk 的 `choices` 非空);默认不强制 include_usage | ⚠️ 有独立 cache read/write 单价,但多个计费 bug:缓存 token 按全价算(#26807,多收 1.67×)、cache write 未计入(#33772)等 | 官方文档 + Issue 讨论 + 第三方调研 | litellm docs Prompt Caching、#27763、#26807、#33772、awesome-ai-gateway | +| **Vercel AI Gateway(顺带)** | 面板正确展示 cache read,但**缓存 token 按全价输入计费**(Kimi 案例 6× 成本) | 上游协议原样 | 未细查 | ⚠️ 计费不应用缓存折扣(issue 讨论) | Issue 讨论 | [vercel/ai#13907](https://github.com/vercel/ai/issues/13907) | + +--- + +## 2. OpenRouter(重点) + +**结论先行**:OpenRouter 是少数把「缓存命中 token」做成**一等公民**的聚合层——它把各上游(Anthropic/OpenAI/Gemini/DeepSeek…)的缓存字段**统一规范化**成 OpenAI 风格的 `prompt_tokens_details.cached_tokens`,并增加自有扩展字段 `cache_write_tokens`(缓存写入)与 `cache_discount`(本次缓存折扣金额)。 + +### 2.1 usage 字段是否原样透传 / 规范化成什么 +- 官方 API 参考(`ResponseUsage` 类型): + - `usage.prompt_tokens` / `completion_tokens` / `total_tokens` + - `usage.prompt_tokens_details.cached_tokens`("Tokens cached by the endpoint")+ 可选 `cache_write_tokens`("Tokens written to cache (models with explicit caching)") + - 另有 `completion_tokens_details.reasoning_tokens`、`cost`、`cost_details`(含 `upstream_inference_prompt_cost` 等)、`is_byok`、`server_tool_use_details` 等 OpenRouter 扩展。 +- 官方示例:`"usage": { "prompt_tokens": 10339, "completion_tokens": 60, "total_tokens": 10399, "prompt_tokens_details": { "cached_tokens": 10318, "cache_write_tokens": 0 } }`。 +- 也就是说:**Anthropic 的 `cache_read_input_tokens` 会被折算进 `cached_tokens`**(并参与折扣计费)。OpenRouter 官方博客明确说明:缓存读取价格约为正常输入价的 0.1×–0.5×(Anthropic/DeepSeek/Qwen 0.1×,OpenAI 0.25×–0.5×……)。 +- 没有找到 OpenRouter 会把 Anthropic 原生 `cache_creation_input_tokens` 原样透传的证据——它统一到 OpenAI 风格。OpenRouter 自己的扩展字段就叫 `cache_write_tokens`。 + +### 2.2 流式响应 +- 与 OpenAI 相同:需 `stream_options: { include_usage: true }`,最后一个 SSE chunk 带 `usage`(官方博客称**每个响应都包含** `usage.prompt_tokens_details` 的 `cached_tokens`/`cache_write_tokens`)。 +- 注意:OpenRouter 官方「Response caching(响应缓存)」是**另一回事**——它缓存的是整条响应(`X-OpenRouter-Cache-Status: HIT/MISS` 头);**HIT 时返回的 usage 是 `prompt_tokens: 0, completion_tokens: 0, total_tokens: 0`**(官方文档示例)。实验时不要把「OpenRouter 响应缓存」当成「prompt cache」。 + +### 2.3 计费显示 +- `usage.cost` 体现缓存折扣后的实际金额;`usage.cost_details` 细分上游各项成本;`cache_discount` 表示本 generation 因缓存省下/付出的金额(写入缓存的那一轮可能为负折扣,因为写缓存更贵)。 +- Activity 页面与 `GET /api/v1/generation` 可逐条查看 `cached_tokens` / `cache_write_tokens` / `cache_discount`。 +- 社区实测(2026-07,china-llm.com):GLM-5 经 OpenRouter 重复调用返回 3200 cached tokens、价格降 75%;同时**同一前缀 DeepSeek 经 OpenRouter 报 0 cached tokens**(原生端点几分钟内有 98% 命中)——**说明 OpenRouter 某些模型/上游不保留缓存,不能一概而论**。Paul's Programming Notes 也实测 Kimi K3 的缓存折扣「过不了 OpenRouter」。 +- 第三方安全测评(Tarun Chitra 文章)指出:存在供应商「把缓存 token 按全额重新计价」的多收费现象,OpenRouter 本身对上游的缓存识别并不总是生效——意味着 **`cached_tokens` 字段是否存在、是否 >0,可作为判断上游是否真正给了缓存折扣的观测点**。 + +**证据等级**:缓存字段设计=官方文档;折扣细节=官方博客;个别模型缓存不过网关=第三方实测;上游「repricing」问题=第三方文章。 + +### 2.4 来源 +- https://openrouter.ai/docs/api/api-reference/chat/create-a-chat-completion (官方,ResponseUsage 定义/示例) +- https://openrouter.ai/blog/tutorials/prompt-caching-sticky-routing (官方,缓存字段与折扣) +- https://openrouter.ai/docs/guides/features/response-caching (官方,响应缓存 HIT 时 usage 归零) +- https://china-llm.com/blog/openrouter-prompt-caching (第三方实测,2026-07-28) +- https://www.paulsprogrammingnotes.com/2026/08/kimi-k3-cache-discount-openrouter.html (第三方实测) + +--- + +## 3. opencode / opencode Zen / opencode Go + +### 3.1 先说清楚「opencode go」是什么(任务要求查清) +- `opencode`(sst/opencode,现仓库 `anomalyco/opencode`,作者 Anomaly,前 SST 团队)是**用 Go 写的开源 terminal coding agent**(MIT)。 +- **「opencode go」= OpenCode Go**,是 Anomaly 推出的**低价订阅服务**(首月 $5,之后 $10/月),提供一批开源/开源权重 coding 模型(Kimi、GLM、MiniMax、DeepSeek、Qwen、Grok、GPT-5.6 Luna 等)。**它不是「Go 语言版本的 opencode」,而是「一个叫 Go 的订阅套餐」**。它诞生背景是 Anthropic 2026-01 禁止第三方工具使用 Claude 订阅凭据后,Anomaly 顺势推出的三个订阅产品之一:**Go($10/月开源模型)**、**Zen(按量付费网关)**、Black(企业网关)。 +- 官方描述:Go 是面向国际用户的低成本订阅,通过 OpenAI 兼容 / Anthropic 兼容端点提供(Docker 文档确认:`openai_chatcompletions`,base URL 为 opencode.ai 的 Go 端点;MiniMax/Qwen 等走 Anthropic 客户端)。**订阅制=固定月费+用量限额,不按 token/缓存计费**,因此对「缓存命中计费」不敏感——用户看不到用量明细。 +- Zen 才是按量付费:`https://opencode.ai/zen/v1/chat/completions`(OpenAI 兼容)、`/v1/messages`(Anthropic 兼容)、`/v1/responses`(OpenAI Responses)、Gemini 风格端点。 + +### 3.2 Zen 是否保留/计费缓存字段 +- **官方价格表(opencode.ai/docs/zen)对每个模型单独列出 `Cached Read` 和 `Cached Write` 两列单价**(如 MiniMax M3:Input $0.30/M、Output $1.20/M、Cached Read $0.06/M;Claude Sonnet:Cached Read $0.20/M、Cached Write $2.50/M;Qwen 3.7 Plus:Cached Read $0.04、Cached Write $0.50)。**既然按缓存读取/写入单独定价,Zen 网关必然解析上游响应里的缓存 usage 字段**——这是「Zen 保留缓存字段」的最强官方证据(间接)。 +- 第三方佐证——Bifrost 的 OpenCode provider 文档(docs.getbifrost.ai,Bifrost 用同一套 OpenCode Zen/Go provider 实现): + - OpenCode 返回 `usage.prompt_tokens` / `usage.completion_tokens` / `usage.total_tokens` / **`usage.prompt_tokens_details.cached_tokens`** / `usage.completion_tokens_details.reasoning_tokens`。 + - 「有些模型上报 `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`,Bifrost 会把这些映射成标准 `cached_tokens` 参与定价计算」。 + - 「缓存行为取决于底层供应商;有的模型(如 Go 上的 DeepSeek V4 Flash)可能根本不缓存」。 +- opencode 客户端侧:`packages/llm/src/protocols/openai-chat.ts` 的 `mapUsage` 会映射 `prompt_tokens_details.cached_tokens`(issue #33997 里确认);会话级字段 `session.tokens_cache_read`、`info.tokens.cache.read`。**但存在一个已知 bug:OpenAI-compatible(自定义 baseURL,如 LiteLLM 代理)流式路径下 `tokens_cache_read` 恒为 0,即使上游 SSE usage chunk 里明明有 `cached_tokens`(实测 5888/6004 ≈98% 命中)**(#33997,2026-06)。即:**opencode 客户端本身对流式缓存的解析有坑,实验者别只看 opencode 的展示值**。 +- opencode TUI 默认不展示缓存明细——有多个第三方插件补足(opencode-visual-cache、opencode-cache-hit、oc-plugin-caching),说明多模型(含 Zen)返回的缓存 usage 是**可达**的(插件从 opencode session API 读 `tokens`/`cost`)。 + +**回答五个问题**: +1. 透传?— Zen/Go 后端按协议原生透传(表单即 OpenAI/Anthropic 兼容格式);opencode 客户端解析 `cached_tokens`(OpenAI 兼容路径有流式 bug)。 +2. 规范化?— 未见 Zen 官方文档说明是否统一改名;Bifrost 实现会把 `prompt_cache_hit_tokens` 映射成 `cached_tokens`。未找到 Zen 对 Anthropic 端点的缓存字段改名证据(推测为 Anthropic 原生格式透传)。 +3. 流式?— opencode #33997 证实上游流式 chunk 带 `cached_tokens`,但 opencode 展示为 0(客户端 bug)。 +4. 计费?— Zen 按 Cached Read/Write 单价收费(官方价格表);Go 订阅制不按缓存计费。 +5. 来源 — 见下。 + +来源:https://opencode.ai/docs/zen、https://opencode.ai/docs/go、https://opencode.ai/zh/go、https://docs.getbifrost.ai/providers/supported-providers/opencode、https://github.com/anomalyco/opencode/issues/33997、#13003、#23109、#34296、https://ai.miraheze.org/wiki/OpenCode_Go(第三方介绍)、https://thomas-wiegold.com/blog/opencode-go-review(第三方评测) + +--- + +## 4. 开源自建中转网关:one-api / new-api / one-hub + +### 4.1 one-api(songquanpeng) +- 主干是「OpenAI 兼容格式 `chat/completions` 转发」,上游 OpenAI 协议响应的 usage 基本原样转发;但**计费模型完全没有缓存折扣**:官方 FAQ 的额度公式 = 分组倍率 × 模型倍率 ×(提示 token 数 + 补全 token 数 × 补全倍率)。 +- 第三方逐 commit 源码审查(cuihuan/awesome-ai-gateway, 2026-07-29)结论: + - one-api 的 `quota = ceil((promptTokens + completionTokens*completionRatio) * ratio)`,**全仓没有 cache read/write 单价**(六网关对比中 one-api/Kong/Higress 是仅有的三家无独立缓存价格的)。 + - one-api 最新 commit 停留在 2025-02-21(v0.6.10),计量代码比 new-api 旧约 17 个月;流式 usage 缺失时用 tiktoken 兜底重算(但仅对 gpt-3.5/4 前缀建了真编码器,Claude/Gemini 流会退回 gpt-3.5-turbo 编码器)。 + - 用户视角:**客户端拿到的 usage 里缓存字段大概率保留(OpenAI 渠道),但账单不会给缓存折扣**。 +- 流式:README 有可选环境变量 `ENFORCE_INCLUDE_USAGE`(是否强制在 stream 下返回 usage)。 +- 未找到 one-api 专门讨论 cached_tokens 透传的 issue(搜索「one-api cached_tokens」无直接命中;其 issue #204 是登录 token 缓存导致额度超额,与 prompt cache 无关,不采用)。 + +**结论**:one-api = usage 大体透传、缓存字段保留与否取决于客户端是否请求 include_usage;**计费无缓存折扣**。证据等级:第三方源码审查 + 官方 README。 + +### 4.2 new-api(QuantumNous,one-api 的主要活跃 fork,包装「官转」最多的底座) +- **转发路径**:OpenAI 渠道流式 `handleLastResponse` 里 `*usage = lastStreamResponse.Usage`(**整体拷贝**,`cached_tokens` 保留)——本文直接读取源码 `relay/channel/openai/helper.go` 确认。非流式 `xAIHandler` 直接 `return xaiResponse.Usage, nil`。 +- **但有一批已证实的 bug(全部为 issue 讨论 + 部分有源码定位)**: + 1. **#6144(xAI 渠道)**:流式 handler 双路径分叉——转发给客户端的 usage 是完整的(`cached_tokens=1792` 正确返回),但内部计费用的是手动重建的残缺 usage(只拷 3 个标量),`cache_tokens` 记成 0,缓存 token 全按全价计费。非流式正常。已提交修复 PR #6145(`*usage = *xAIResp.Usage` 整体拷贝)。**「客户端看到的 usage 是对的,网关自己计费是错的」的典型例子**。 + 2. **#5672(自定义渠道/火山方舟)**:流式模式下 `usage.prompt_tokens_details.cached_tokens` 恒为 0,`prompt_tokens` 从 3513 膨胀到 4540(+29%),`reasoning_tokens` 被清零,并被注入大量默认字段(`text_tokens:0, audio_tokens:0, claude_cache_creation_*:0` 等)。非流式正常。已关闭(not planned)。 + 3. **#5003 / #5005(缓存命中→输入 token 为负数)**:上游按 Anthropic 排除语义返回(cache read 已从输入中排除),new-api 又减了一次,输入算出 −16,638,账单反而「倒贴」给用户(第三方文档给出可复算算术)。重视用户实测「站长亏损」。 + 4. **#6353(Claude 缓存写入 token 未计费)**:5m/1h TTL 拆分缺席时级联 bug 把 cache creation 值清零,最贵的写入 token 打了 100% 折。开放中。 + 5. **#1103(Gemini reasoning 未计费,开放 14 个月)**:`completion_tokens`(124)不含 `reasoning_tokens`(1097),90% 输出 token 未计费(属推理字段,非缓存,顺带记录)。 +- **透传模式**:new-api 的 issue 模板明确写「透传模式会直接转发请求,请自行确认上游行为;开启透传后的转发相关反馈不接受 issue」→ **存在「透传(直连上游)」开关,开启后缓存字段随上游原样返回**;反之普通中继模式会走上面的 usage 规范化逻辑(可能补默认字段、改计数)。 +- **计费**:`service/text_quota.go`(OpenAI 语义)`promptQuota = (PromptTokens - CacheTokens) + CacheTokens * CacheRatio`,并有 `CacheCreationRatio`(5m/1h 拆分)——**new-api 是少数原生支持缓存折扣计费的开源网关**,但 bug 多。 + +**结论**:new-api「会」保留缓存字段(多个渠道/修复后),但**流式+自定义渠道/部分内置渠道历史上会丢/损坏缓存字段或计费错误**;实验透过 new-api 必须同时看「客户端收到的 usage」与「网关消费日志/账单」两处。证据等级:官方源码(helper.go + issue 中源码定位)+ issue 讨论 + 第三方调研(awesome-ai-gateway)。 + +### 4.3 one-hub(MartialBE,one-api 的另一活跃 fork) +- 与 new-api 同源(都 fork 自 one-api);能力上对齐 new-api 的缓存计费方向(README 称「支持更多模型」)。 +- **直接证据:PR #910(2026-01,由 done-hub 转来)——「修复 Responses API cached_tokens 字段缺失问题」**:原代码对 `ResponsesUsageInputTokensDetails.CachedTokens` 用了 `omitempty` 标签,**值为 0 时字段被省略**,导致 Codex CLI 解析 `response.completed` 事件时报 `missing field 'cached_tokens'` 并无限重试。修复=移除 omitempty 保证零值也输出。→ **说明网关在 Responses 路径会把 `cached_tokens` 弄丢(至少历史版本)**。 +- Release v0.14.26:「为通过 AWS Bedrock 渠道访问的 Claude 模型添加 prompt caching 支持」(PR #850)→ one-hub 主动做缓存透传/支持。 +- 计费沿用 one-api/new-api 体系(new-api 特性 `CacheRatio` 等是否完全同步需逐个版本核对,未找到独立证据)。 + +**结论**:one-hub 基本透传,但历史上有 Responses API 丢 `cached_tokens` 的 bug 并已修复;实验者用 Codex Responses 端点时建议对照上游原始响应。证据等级:PR 讨论 + Release 说明。 + +### 4.4 来源汇总 +- https://github.com/songquanpeng/one-api (README:额度公式、ENFORCE_INCLUDE_USAGE) +- https://github.com/QuantumNous/new-api/issues/6144 、#5672 、#5003 、#5005 、#6353 、#1103 +- https://raw.githubusercontent.com/QuantumNous/new-api/main/relay/channel/openai/helper.go (源码) +- https://github.com/MartialBE/one-hub/pull/910 、https://github.com/MartialBE/one-hub/releases (v0.14.26) +- https://github.com/cuihuan/awesome-ai-gateway/blob/main/docs/virtual-keys-metering.zh-CN.md (第三方逐 commit 审查,2026-07-29;含上述 issue 的状态核实与可复算算术) + +--- + +## 5. 国内常见中转/拼车 API 站(packycode、灵眸AI 等)与缓存计费讨论 + +### 5.1 packycode(PackyAPI,自称「官转」) +- LINUX DO 官方商家帖(2025-07):「Packycode 的计费保持和官网的 api 计费方式一样」「**我们会透传用户的请求(保护隐私),最后解析 claude 传过来的 usage tokens,我们默认使用 5m Cache Writes 做 cache 的计费**」——**明说基于上游 usage 计费、缓存按 5m cache write 计费**。同时有用户问「Claude code 拼车的时候,背后是 Claude code 的池子,不会没有办法命中 cache 吗」——官方回复大意:全局用 Claude Code 的话缓存命中由 Claude Code 自管,实际消耗不大。 +- GitHub 宣传页(2026):PackyAPI 主站按量付费、计费对标 Claude/OpenAI 官网价格;Codex 有独立包月站。 +- 用户实测(什么值得买/其他帖):Claude Code 场景 cache read 占输入大头(另一帖统计 82.9% cache read / 15.6% cache write / 1.5% fresh input);**cache 命中基本决定中转实际价格**。 + +### 5.2 灵眸AI 等(社区实测透传) +- fulitimes 博客(2026,Claude Code 缓存指南):「实测灵眸AI **完整透传** `cache_creation_input_tokens` 和 `cache_read_input_tokens` 这两个字段,可在后台账单中查看每次请求的 cache 命中情况」;并警告「**很多便宜平台用逆向接口,不支持 Prompt Caching**——表面价低但无缓存差距」;验证方法=在响应 usage 里查这两个字段是否存在。 +- 知乎/博客普遍教程:判断中转是否支持缓存的唯一方法是看响应 usage 里有没有 `cache_creation_input_tokens` / `cache_read_input_tokens`(Anthropic 风格)。说明**社区已把「usage 缓存字段是否透传」当作中转站质量的验收标准**。 + +### 5.3 结论(针对四个问题) +1. 是否透传缓存字段:**参差不齐**。口碑「官转」站大多解析上游 usage 并据此计费(packycode 明说,灵眸AI 实测透传);逆向/低价接口通常无缓存。**没有统一规范**。 +2. 规范化/改名:一般保持上游协议原生(Claude Code 场景=Anthropic 原生字段;OpenAI 兼容场景=OpenAI 风格)。 +3. 流式:Claude Code 流式 usage 走 Anthropic `message_start`/`message_delta`;有 issue 表明 Claude Code 类客户端对 messageDelta 里的缓存计数有兼容问题(cline#4346 讨论 Anthropic API 在 messageDelta 增加累计缓存计数的兼容问题)。 +4. 计费显示:中转站按解析后的 usage 计费并**普遍把缓存写入按 5m 档定价**(1.25×输入价),缓存读取按 0.1×;用户可看到余额消耗,部分站(如灵眸AI)后台可查 cache 命中明细。 +- 证据等级:除 GitHub 宣传页外几乎全部为社区讨论/用户实测(无官方文档)。**未找到「中转站统一丢弃缓存字段」的系统性证据**;相反,多个实测表明主流中转会透传。 + +来源: +- https://linux.do/t/topic/771392 (Packycode 计费说明帖) +- https://linux.do/t/topic/1620430 (cache read 占比 82.9% 实测) +- https://linux.do/t/topic/2591545 (Sub2API 中转 Claude Code 消耗统计) +- https://blog.fulitimes.com/claude-code-cost-optimization (灵眸AI 透传实测、逆向接口无缓存) +- https://github.com/CherryHQ/cherry-studio/discussions/15278 (Feiyuan API「原生透传 cache_control」的站长自述,Claude 中转缓存讨论) +- https://github.com/cline/cline/issues/4346 (Anthropic messageDelta 缓存计数的客户端兼容问题) + +--- + +## 6. 订阅制 coding plan:GitHub Copilot / Cursor / Windsurf / Augment Code + +统一先回答「是否向终端用户暴露 token usage/缓存信息」:**多数不暴露原始 per-request usage,但 Cursor/Augment 等会在用量面板里展示缓存拆分明细;Copilot/Windsurf 只给聚合/credit 换算后的信息**。 + +### 6.1 GitHub Copilot +- 经典订阅制(token 配额):用户拿不到 per-request usage。2026-06 起逐步转 token 计费(Medium/官方博客)。 +- 企业版提供 REST Copilot usage metrics API(enterprise/org 级):返回**每日聚合**的 `prompt_tokens_sum`、`output_tokens_sum`、`avg_tokens_per_request` 等,**没有缓存 token 拆分字段**(官方文档示例可见)。→ 官方聚合指标里**看不到 cached tokens**。 +- VS Code 内 OTLP 指标:microsoft/vscode#317837 确认 **Copilot Chat 的 OTLP metrics 不暴露 cached input token usage**;但 GitHub 定价区分 normal input 与 cached input(说明**平台侧在按缓存计费**,只是不暴露给用户)。 +- 有趣的实证:obsidian-copilot 的讨论(#2380)贴出免费 Copilot 计划(DeepSeek v4)的**原始 SSE**——`usage.prompt_tokens_details.cached_tokens` 和顶层 `prompt_cache_hit_tokens`/`prompt_cache_miss_tokens` **都原样出现在流里**(总计 128 cached)。→ **Copilot 的 OpenAI 兼容后端(至少 DeepSeek 路径)会把缓存字段透传给流式客户端**,尽管官方不提供 per-request 文档。该讨论同时指出 DeepSeek 的缓存折扣对 Copilot 免费用户「用不上」(因为系统提示没被缓存)。 +- copilot-cli issue #3808:请求 Copilot CLI 对 Claude Sonnet 启用 Anthropic 缓存断点(当前「无可见优化」)——说明 Copilot CLI 订阅路径**目前不刻意利用/暴露 Anthropic prompt cache**。 +- **结论**:Copilot=订阅+token 配额;缓存字段**不面向终端用户文档化**;企业聚合 API 无缓存拆分;底层 SSE 有透传迹象(社区实测)。证据等级:官方文档(metrics API 字段)+ issue 讨论。 + +来源:https://docs.github.com/rest/copilot/copilot-usage-metrics 、https://github.com/microsoft/vscode/issues/317837 、https://github.com/logancyang/obsidian-copilot/discussions/2380 、https://github.com/github/copilot-cli/issues/3808 、https://code.visualstudio.com/blogs/2026/06/17/improving-token-efficiency-in-github-copilot + +### 6.2 Cursor +- 论坛官方账号(客服口径,thread「Why are cache read and write chargeable?」):「In all cases we show the precise token consumed in Usage report **as provided by AI provider sent back with AI response**」——**用量面板展示的缓存拆分明细来自上游 API 响应原样**;「有些供应商把 cache write 算进 Input 只单列 cache read,有些(Anthropic)单独分开,我们按供应商返回的展示」。 +- 订阅(Pro)与 BYOK 的用量面板都单列 **Cache Read / Cache Write**,且按缓存价计费(cache read ≈ 输入价 10%)。多篇论坛帖用「0 cache read / 0 cache write → usage 暴涨」排查 Auto 模式路由到不支持缓存的模型(版本 2.6.12 → 2.6.18 修复)。 +- **注意**:这说明 Cursor 订阅计划**会展示**缓存 token 明细(这是少数订阅制里对用户可见的);但这只是「面板展示」,非公开 API —— Cursor 不提供获取原始 usage 的 API(未找到)。 +- 另一个相关实证(microsoft/vscode#312939,OpenRouter BYOK in Copilot):**经 OpenRouter 的 Claude 在 agent 模式里 `cached_tokens` 恒 0**,与原生 Anthropic BYOK 对比 10 倍成本差异——聚合层缓存是否生效对 agent 成本影响极大。 + +**结论**:Cursor=订阅制但用量面板单列 cache read/write(透传自上游响应);无公开 usage API。证据等级:社区讨论(官方客服回复)+ 论坛实测;官方文档未直接确认面板字段。 + +来源:https://forum.cursor.com/t/someone-please-explain-why-are-cache-read-and-write-chargeable/153538/8 、https://forum.cursor.com/t/auto-mode-not-using-prompt-caching-0-cache-read-write-sudden-usage-spike/154278 、https://forum.cursor.com/t/cache-read-token/153794 、https://github.com/microsoft/vscode/issues/312939 + +### 6.3 Windsurf +- credits + token 混合计费:外部模型按「模型供应商 API 价 + 20% 加成」换算 credit,**明确区分 input / cache-read / output 三种单价**(flexprice.io 整理:Claude Sonnet 4:input 90 credits/M、**cache read 9 credits/M**、output 450 credits/M;1 credit=$0.04)。→ Windsurf 计量层**按 cache-read 打折计费**,说明其网关解析并保留了缓存字段。 +- 用户侧**看不到原始 usage 字段**,只能看到 credit 消耗与用量面板;Tokenminning 的 Windsurf 页提到「Quota & billing(daily/weekly quota, cache reads, enterprise ACUs)」→ 官方文档存在 cache reads 相关条目(推测在用量说明中,未逐字核验)。 +- **结论**:订阅/credits 制;缓存 token 参与折扣计费(第三方资料);未找到向用户暴露 per-request usage 的证据。证据等级:第三方价格分析 + 官方文档存在性(未逐字核验)。 + +来源:https://flexprice.io/blog/windsurf-ai-pricing-breakdown 、https://tokenminning.ai/ides/windsurf 、Windsurf 官方文档(quota & billing,未逐字核验) + +### 6.4 Augment Code +- 官方文档(Token-Based Pricing):「Augment **自动缓存稳定上下文**(repo index、AGENTS.md、最近文件),**cached input tokens 按供应商缓存价计费(约输入价 10%)**,服务费随缩水」;「Usage → Models 面板展示每个模型的 input/output/**cache read/cache write** 单价」。 +- 定价体系:2025-10 起从 message 制改 credit 制(token 制文档较新,网页 2026 版本同时提到 token-based pricing 与 credit)。 +- **结论**:订阅/credit 制,官方明确缓存读取按折扣计费并在面板展示缓存单价——但没有公开 API 暴露原始 usage 字段。证据等级:官方文档。 + +来源:https://docs.augmentcode.com/models/token-based-pricing 、https://www.augmentcode.com/blog/augment-codes-pricing-is-changing + +--- + +## 7. Cloudflare AI Gateway / Portkey / LiteLLM(及顺带 Vercel AI Gateway) + +### 7.1 Cloudflare AI Gateway +- 官方「Caching」文档指的是**网关级响应缓存**:按 provider+endpoint+model+auth+body 构造 SHA-256 cache key,用 `cf-aig-cache-status: HIT/MISS` 头标识;**命中时直接返回缓存响应,不再调用上游**——这是「cache 掉整条响应」,不是 prompt cache。命中响应的 usage 含义取决于缓存内容(官方未在此文档中说明 usage 归零;**与 OpenRouter 响应缓存把 usage 清零不同,Cloudflare 文档未写明**,实验时注意区分)。 +- Anthropic provider 文档:给出把 base URL 指向 AI Gateway 的示例(`/ai/v1/messages`),**未提到会规范化/丢弃 Anthropic 的 `cache_read_input_tokens`**。社区(openclaw#46709)实测请求体的 `cache_control` 能透传到 gateway(bug 是在 openclaw 侧 TTL 设置,不是网关丢弃)。 +- **未找到**官方文档明确说明 Cloudflare AI Gateway 对上游 usage 缓存字段的保留/改名策略——按「透明代理」设计推测为原样透传(推测,证据不足)。 +- Workers AI(非网关)文档确认其在 `usage` 对象里返回 cached token 计数——但那是 Cloudflare 自营推理,不是聚合层。 + +**结论**:Cloudflare AI Gateway 未文档化缓存 usage 字段处理;其自带缓存是响应级缓存(有 HIT/MISS 头);请求侧 cache_control 可达。证据等级:官方文档(缓存功能)+ issue 讨论(cache_control 透传)+ 推测(usage 透传)。 + +来源:https://developers.cloudflare.com/ai-gateway/features/caching 、https://developers.cloudflare.com/ai-gateway/usage/providers/anthropic 、https://github.com/openclaw/openclaw/issues/46709 、https://developers.cloudflare.com/workers-ai/features/prompt-caching + +### 7.2 Portkey +- **有明确的规范化文档**(Bedrock Prompt Caching 页): + - 「Portkey normalizes responses to the OpenAI format」;`prompt_tokens` **包含**缓存 token:`prompt_tokens = inputTokens + cache_read_input_tokens + cache_creation_input_tokens`。 + - `cached_tokens` 出现在 usage 里(OpenAI 风格);定价时先从 prompt_tokens 减去缓存部分,再分别按 base input / cache read(折扣价)/ cache write 计价。 +- 其观测端/Inference API Responses 返回 `usage.input_tokens_details.cached_tokens`(官方 API 参考示例)。 +- 自带「响应缓存(simple/semantic)」与 prompt cache 是两回事(Portkey blog 明说两者可叠加)。 +- **结论**:Portkey 会保留并**主动规范化**缓存字段到 OpenAI 风格(`prompt_tokens_details.cached_tokens`),且计费按缓存分项。证据等级:官方文档。 + +来源:https://docs.portkey.ai/docs/integrations/llms/bedrock/prompt-caching 、https://docs.portkey.ai/docs/integrations/llms/openai/prompt-caching-openai 、https://docs.portkey.ai/docs/api-reference/inference-api/responses/retrieve-response 、https://portkey.ai/blog/openais-prompt-caching-a-deep-dive + +### 7.3 LiteLLM +- 官方 Prompt Caching 文档:「For the supported providers, **LiteLLM follows the OpenAI prompt caching usage object format**」→ OpenAI 兼容 `completion()` 返回 `usage.prompt_tokens_details.cached_tokens`;同时返回对象里也带 Anthropic 原生 `cache_creation_input_tokens` / `cache_read_input_tokens`(官方示例的 Usage 对象同时含两者)。即**双格式并存**(规范化 + 保留原生)。 +- `/v1/messages`(Anthropic 兼容端点):按 Anthropic 原生返回 `cache_creation_input_tokens` / `cache_read_input_tokens`(官方 anthropic_unified 文档)。 +- **已知 bug #27763**:Anthropic `/v1/messages`(含 Vertex/Bedrock 透传路径)**不会把原生 `cache_read_input_tokens` 映射成 `prompt_tokens_details.cached_tokens`**,导致 Prometheus 的 `litellm_cached_tokens_metric_total` 恒为 0、缓存命中看起来像没发生,且 `litellm_spend_metric` 可能把缓存读取按全价算。 +- 计费:有 `cache_read_input_token_cost` / `cache_creation_input_token_cost` 单价,但**计费 bug 多**:litellm#26807(自定义定价路径缓存 token 按全价算,用户多付 1.67×)、#33772(OpenAI `cache_write_tokens` 未计入成本,消费远低于厂商账单)、#11364(Anthropic 缓存成本算错)、#34875(生产流式 80.7% 行成本 $0,并发竞态)。 +- 流式:默认不强制 include_usage(`always_include_stream_usage` 默认关);合成末端 usage chunk 曾有 `choices` 非空的历史 bug(#28735 等)。 +- **结论**:LiteLLM 意图是「OpenAI 风格规范化 + 保留原生」,但 Anthropic 透传路径的功能与计费都有多个已知坑,实验中应同时对比原生字段与 `cached_tokens`。证据等级:官方文档 + issue 讨论 + 第三方调研。 + +来源:https://docs.litellm.ai/docs/completion/prompt_caching 、https://docs.litellm.ai/docs/anthropic_unified 、https://github.com/BerriAI/litellm/issues/27763 、#26807 、#33772 、#11364 、https://github.com/cuihuan/awesome-ai-gateway/blob/main/docs/virtual-keys-metering.zh-CN.md + +### 7.4 Vercel AI Gateway(顺带) +- vercel/ai#13907(2026-03):经 Vercel AI Gateway 调 `moonshotai/kimi-k2.5`,面板正确显示 Cache Read 5.8M(93.5% 命中),但**账单按全价输入计费**——真实成本 $4.00 vs 直连 $1.28(6×)。→ 网关侧「显示缓存但不应用缓存折扣」的实例。证据等级:issue 讨论。 +- 来源:https://github.com/vercel/ai/issues/13907 + +--- + +## 8. 实验建议(通过聚合层验证缓存字段时的检查清单与坑) + +### 8.1 该检查哪些字段(按入口格式) +- **OpenAI 兼容入口(大多数聚合层采用)**: + - `usage.prompt_tokens_details.cached_tokens`(聚合层规范化后应在此) + - 扩展字段:OpenRouter `cache_write_tokens`、`cache_discount`、`cost_details`;LiteLLM 同对象里还可能带 `cache_creation_input_tokens` / `cache_read_input_tokens` + - Responses API 入口(Codex 类客户端):`usage.input_tokens_details.cached_tokens`(one-hub 曾因 omitempty 漏掉此字段) +- **Anthropic 兼容入口(`/v1/messages`)**:`usage.input_tokens`、`cache_creation_input_tokens`、`cache_read_input_tokens`(+新格式 `cache_creation.ephemeral_5m/1h_input_tokens`) +- **DeepSeek/部分上游**:顶层 `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`(注意有些聚合层会原样透传、有些会映射进 `cached_tokens`——Bifrost 的做法是映射) +- **同一个请求的三种视图都要抓,不能只看一种**: + 1. 客户端收到的响应 usage; + 2. 网关消费日志/账单里的 token 拆分(new-api#6144 的教训:这两者可能不一致——响应是对的、账单是坏的); + 3. 上游(如果可直连对照)原生 usage——用于判断聚合层是「透传」「改名」还是「丢弃」。 + +### 8.2 已知的坑(汇总自本节调研) +1. **流式 usage 必须有 `stream_options.include_usage=true`**,否则 OpenAI Chat Completions 风格的流根本没有 usage chunk(OpenRouter 同规则;LiteLLM 有合成兜底但历史上有格式 bug)。实验脚本务必显式带该参数并对齐最后一个 chunk。 +2. **网关自身另有「响应缓存」(result cache)**:OpenRouter 的 `X-OpenRouter-Cache-Status: HIT` 时 usage 全为 0;Cloudflare AI Gateway 有 `cf-aig-cache-status`;此类命中不是 prompt cache,别误读为「缓存命中 token=0」。 +3. **前缀漂移/路由漂移**:聚合层多供应商路由会让同一 session 落到不同上游导致缓存失效;OpenRouter 用 `session_id` 做 sticky routing 以保缓存。实验中固定供应商(`provider` 参数)或固定 session_id 再测。 +4. **客户端解析 bug 会掩盖真相**:opencode 对 OpenAI-compatible 流式 provider 的 `tokens_cache_read` 恒 0(#33997)——不要用 opencode 的展示值当结论,要看原始 SSE。 +5. **显示 vs 计费分离**:new-api xAI 渠道(#6144)响应正确但账单按全价;Vercel AI Gateway(#13907)面板显示缓存但账单全价。**验证「缓存字段是否透传」和「缓存是否影响账单」是两件事**,后者在中转站/订阅网关里只能靠站方后台,无法从响应验证。 +6. **供应商/模型差异**:同一聚合层下,DeepSeek 缓存可能不过网关(china-llm 实测 OR 上 0 cached)而 GLM 正常;Kimi K3 缓存折扣不经过 OpenRouter。实验要按模型逐个测,不能拿一个模型代表全部。 +7. **语义差异**:Anthropic 的 `input_tokens` 是「最后一个缓存断点之后的 token」(缓存读取已排除);OpenAI 的 `prompt_tokens` **包含**缓存读取。字段 `cached_tokens > input 总量` 只有在排除语义下才可能出现(new-api#5003 曾因此把输入算成负数)。取值与对账时务必按供应商语义。 +8. **订阅制服务(Copilot/Windsurf/Augment/Cursor 订阅)没有公开 per-request usage API**:无法从响应侧做该实验;Cursor 面板展示的 cache read/write 数据点据客服称来自上游响应。若实验目标是「验证缓存命中 token」,应优先选按量 API(OpenRouter、Zen、中转站)。 +9. **国内中转站验证**:Claude Code 场景看 `cache_creation_input_tokens` / `cache_read_input_tokens` 是否存在且随轮次递增(命中);缺失=该站(逆向/无缓存)不保留缓存字段。社区普遍以「响应 usage 是否带缓存字段」作为中转是否『支持缓存计费』的验收标准。 +10. **缓存写入也有计费折扣的镜像**:OpenRouter 用 `cache_write_tokens`、Anthropic 用 `cache_creation_input_tokens`(5m=1.25×、1h=2× 输入价)。实验前两轮必然出现 cache write>0、cache read=0,符合预期;别把首轮 cache read=0 当成「网关丢字段」。 + +### 8.3 建议的最小实验矩阵 +| 层 | 建议入口 | 必查字段 | 对照 | +|---|---|---|---| +| 直连官方(对照组) | Anthropic/OpenAI/DeepSeek 原生 | `cache_read_input_tokens` / `cached_tokens` / `prompt_cache_hit_tokens` | — | +| OpenRouter | `chat/completions` + include_usage | `cached_tokens`+`cache_write_tokens`+`cost`/`cache_discount` | 与直连对照;固定 provider+session_id | +| opencode Zen/Go | `v1/chat/completions`/`v1/messages` | 协议原生缓存字段 | 与官方价格表 Cached Read 列对照 | +| new-api/one-hub | chat/completions(流式+非流式各一遍) | `cached_tokens`;同时看网关消费日志 | 非流式作为基线(历史上流式丢字段 bug 多) | +| LiteLLM | completion + /v1/messages | `cached_tokens` 与 `cache_read_input_tokens` 是否同时出现 | 抓 `litellm_cached_tokens_metric` 是否>0 | +| 国内中转站 | Anthropic 兼容 | `cache_creation/read_input_tokens` | 两轮同前缀请求,命中应>0 | + +--- + +## 9. 一句话总结 + +- **透传且规范化得最好**:OpenRouter(统一 OpenAI 风格 `cached_tokens`+扩展)、Portkey(明确规范化并分项计价)、Bifrost(将 `prompt_cache_hit_tokens` 映射为 `cached_tokens`)。 +- **意图透传但坑多**:new-api / one-hub(多个流式/Responses bug)、LiteLLM(Anthropic 透传路径不映射、计费 bug 多)、Cloudflare AI Gateway(未文档化,推测透传)。 +- **计费不含缓存或订阅不暴露**:one-api(无缓存单价)、Copilot(聚合 API 无缓存拆分)、Windsurf/Augment(按缓存折扣计费但不暴露原始字段)、Cursor(面板展示缓存明细但没有公开 API)。 +- **核心陷阱**:「客户端收到的 usage」≠「网关账单」≠「上游计费」,三者要分开验证;流式必须 `include_usage`;注意区分网关的 prompt cache(KV cache 命中)与网关的响应缓存(result cache,可能返回 usage 全 0)。 + +--- +*报告完。所有引用为调研时(2026-08-29)可访问的 URL;证据等级逐条标注;凡「未找到证据」处均已如实说明。* \ No newline at end of file diff --git a/cache_research/official_china/README.md b/cache_research/official_china/README.md new file mode 100644 index 00000000..492b4482 --- /dev/null +++ b/cache_research/official_china/README.md @@ -0,0 +1,304 @@ +# 国内官方 LLM API「缓存命中 Token」字段对照报告 + +> 调研子智能体 #2 · 调研时间:2026-08-29 +> 调研范围:**国内官方 API**(DeepSeek / Moonshot Kimi / 阿里通义千问 / 智谱 GLM / 字节豆包 / MiniMax / 阶跃星辰 Step / 百度文心千帆) +> 数据来源:以**各厂商官方文档**为准(文末附全部 URL);个别引用了第三方报道处已单独标注。 +> 结论确定性说明:本文所有"字段名 / 官方示例 JSON / 官方计费规则"均直接取自官方文档,可据此设计实验;**本文只做了文档调研,未实际跑请求验证**,实测时字段是否如实返回以实验为准。 + +--- + +## 一、总览表(速查) + +| # | 厂商 | 官方平台 | 缓存机制 | 命中字段(usage 内位置) | 最小触发阈值 | 命中计费折扣(官方口径) | +|---|---|---|---|---|---|---| +| 1 | DeepSeek | api.deepseek.com | **自动**(无需配置) | 顶层 `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`(**注意:不在 details 里**) | 文档未给出固定值 | deepseek-v4-flash 峰值:缓存命中 $0.014/M vs 未命中 $0.44/M(≈3.2%,约 1/31) | +| 2 | Moonshot Kimi | platform.moonshot.cn / platform.kimi.com | 自动(当前推荐);历史上曾有显式 cache API | `usage.cached_tokens`(顶层,官方示例);部分资料为 `usage.prompt_tokens_details.cached_tokens` | 文档未给出固定值(按前缀匹配) | kimi-k3 为 $0.30/M vs $3.00/M = **10%**;k2.7-code ≈20%;k2.6 ≈16.8%;k2.5 ≈16.7% | +| 3 | 通义千问 Qwen | DashScope / 阿里云百炼 | OpenAI 兼容:**隐式自动** + 显式 `cache_control`;原生 DashScope:同一套显式机制 | OpenAI 兼容:`usage.prompt_tokens_details.cached_tokens`(命中)、`...cache_creation_input_tokens`(创建);DashScope:`usage.prompt_tokens_details.cached_tokens`(部分海外地域 `usage.cached_tokens` 顶层) | 隐式 ≥256 tokens(Qwen3.7 系列约 2000);显式块 ≥1024 tokens | 隐式命中 20%(阿里部署常规);显式命中 10%、创建 125%;qwen3.8-max 例外(以控制台为准) | +| 4 | 智谱 GLM | bigmodel.cn | **自动**(默认开启) | `usage.prompt_tokens_details.cached_tokens` | 智谱部署 GLM 为 512 tokens(阿里云文档口径) | 命中按标准价格 **50%**(智谱官方);阿里云转售口径 25% | +| 5 | 字节豆包 Doubao | 火山方舟 volces.com | **仅显式**(Context API 待下线;Responses API 推荐) | `usage.prompt_tokens_details.cached_tokens` | 无自动缓存;最大缓存长度≈上下文窗口-最大输出 | 缓存输入折扣价(低于新输入)+ 存储费(元/千 token/小时) | +| 6 | MiniMax | platform.minimaxi.com(国内)/ platform.minimax.io(海外) | 自动(被动)+ 显式 Anthropic 兼容 | OpenAI 格式:`usage.prompt_tokens_details.cached_tokens`;Anthropic 格式:`usage.cache_read_input_tokens` / `cache_creation_input_tokens` | **≥512 tokens**(自动) | M3:命中 $0.12/M vs 输入 $0.60/M = **20%**;M2.7:$0.06 vs $0.30 = 20%;M2.5/M2.1:$0.03 vs $0.30 = 10%(显式写入 $0.375/M) | +| 7 | 阶跃星辰 Step | platform.stepfun.com | **自动** | 顶层 `usage.cached_tokens`(**注意:在顶层,不在 details 里**) | **≥256 tokens** | 缓存部分按该模型费用 **20%** 计费 | +| 8 | 百度文心 ERNIE | 千帆 ModelBuilder(qianfan.baidubce.com) | **自动**(默认开启,无需改代码) | `usage.prompt_tokens_details.cached_tokens` | 文档未给出固定值 | 命中按 prompt 单价 **40%** | + +> ⚠️ 最容易踩坑的两点: +> 1. **DeepSeek 和 Step 的字段在 `usage` 顶层**(`prompt_cache_hit_tokens` / `cached_tokens`),其余六家在 `usage.prompt_tokens_details` 下——实验代码要同时兼容这两种形态。 +> 2. **只有豆包是纯显式缓存**(需要创建缓存或传 `caching` 参数),其余六家自动缓存 + 千问可显式。不传任何参数就指望豆包返回 cached_tokens 是无效的。 + +--- + +## 二、逐家明细 + +### 2.1 DeepSeek(深度求索,api.deepseek.com) + +- **官方文档** + - API 参考(usage 结构):https://api-docs.deepseek.com/api/create-chat-completion + - 定价页(缓存命中价):https://api-docs.deepseek.com/quick_start/pricing +- **缓存机制**:自动 context cache,无需任何配置;有"高峰期/非高峰期"分时定价(峰值=非峰值的 2 倍)。 +- **usage 字段(官方 API 参考 schema 原文)**:DeepSeek 是**独立字段模型**—— + ```json + "usage": { + "completion_tokens": 10, + "prompt_tokens": 16, // = prompt_cache_hit_tokens + prompt_cache_miss_tokens + "prompt_cache_hit_tokens": 0, // ← 命中缓存的 token 数(顶层!) + "prompt_cache_miss_tokens": 16, // ← 未命中缓存的 token 数(顶层!) + "total_tokens": 26, + "completion_tokens_details": { "reasoning_tokens": 0 } + } + ``` + - 官方定义原文:"Number of tokens in the prompt that hits the context cache." +- **流式行为**:官方流式示例中,最后一个 chunk(`finish_reason=stop`)携带 `usage`;若设置 `stream_options.include_usage=true`,会在 `data: [DONE]` 前再补一个 `choices` 为空的 usage chunk;其余 chunk 的 `usage` 为 `null`。 +- **计费折扣(官方定价页,单位 $/1M tokens)**: + + | 模型 | 输入(缓存命中) 峰值/非峰值 | 输入(缓存未命中) 峰值/非峰值 | + |---|---|---| + | deepseek-v4-flash | $0.014 / $0.007 | $0.44 / $0.22 | + | deepseek-v4-pro | $0.044 / $0.022 | $1.32 / $0.66 | + + 即命中≈未命中的 **1/31(≈3.2%)**,折扣力度为国内最大。峰值时段:UTC 周一至五 01:00–04:00 与 06:00–10:00。 + > 第三方报道(知乎,非官方):DeepSeek V4-Pro 人民币口径"缓存命中 0.1 元/百万 vs 未命中 3 元/百万(差 30 倍),促销窗口 0.025 元"。此条为第三方转述,仅作参考。 +- **实验要点**:读取顶层 `usage.prompt_cache_hit_tokens`;未命中时该字段为 `0`(官方示例即返回 0),不要把它当缺失。 + +--- + +### 2.2 Moonshot Kimi(platform.moonshot.cn / platform.kimi.com) + +- **官方文档** + - API 参考(Chat):https://platform.kimi.com/docs/api/chat + - 上下文缓存指南:https://platform.kimi.com/docs/guides/context-caching + - 定价页:https://platform.kimi.com/docs/pricing/chat +- **缓存机制**:默认会自动缓存(官方指南:"当请求包含相同前缀时自动缓存,无需手动调用;命中后自动续期")。历史上曾公测显式缓存 API(`POST /v1/caching`,2024 年月之暗面文档),当前 platform.moonshot.cn API 列表已不含该端点,以自动缓存为准。 +- **usage 字段(官方 API 参考示例原文)**: + ```json + "usage": { + "prompt_tokens": 19, + "completion_tokens": 21, + "total_tokens": 40, + "cached_tokens": 10 // ← 命中缓存 token(顶层!官方示例原文) + } + ``` + 官方《上下文缓存指南》(PDF)中的 usage 示例则为 `usage.prompt_tokens_details.cached_tokens`。**两处官方文档形态不一致**,实验时两个位置都要读。 +- **请求参数**:官方请求体字段 `prompt_cache_key`(官方原文)——“用于缓存相似请求的响应以优化缓存命中率。对于 Coding Agent,通常是代表单个会话的 session id 或 task id;退出并恢复会话时应保持不变。对于 Kimi Code Plan,此字段为必填以提高缓存命中率。”不传时按前缀自动匹配。 +- **流式行为**:官方指南明确"**流式返回时,最后一个 chunk 会携带 usage(含 cached_tokens)**"。 +- **计费折扣(官方定价,$/1M)**: + + | 模型 | 输入(未命中) | 输入(命中) | 折扣 | + |---|---|---|---| + | kimi-k3 | $3.00 | $0.30 | **10%** | + | kimi-k2.7-code | $0.95 | $0.19 | 20% | + | kimi-k2.6 | $0.95 | $0.16 | ≈16.8% | + | kimi-k2.5 | $0.60 | $0.10 | ≈16.7% | + | moonshot-v1 系列 | — | 无 | 无缓存折扣 | + +- **实验要点**:最后一轮流式 chunk 的 usage 是主战场;`cached_tokens` 与 `prompt_tokens_details.cached_tokens` 两个位置都要探测。 + +--- + +### 2.3 通义千问 Qwen(DashScope / 阿里云百炼) + +- **官方文档**:阿里云百炼《上下文缓存(Context Cache)》https://help.aliyun.com/zh/model-studio/context-cache +- **缓存机制(OpenAI 兼容模式与原生 DashScope 模式已分别核实)**: + - **隐式缓存(自动)**:对所有支持模型默认开启、不可关闭,按前缀匹配。OpenAI 兼容与 DashScope 均可命中。 + - **显式缓存(需主动开启)**:在 messages 的 content 中加 `"cache_control": {"type": "ephemeral"}`(仅此一种 type),从 messages 开头到标记位置创建缓存块;OpenAI 兼容、DashScope、Anthropic 兼容三种协议均支持。单次最多 4 个标记;向后回溯最近 20 个 content 块;最小缓存块 **1024 tokens**;有效期 **5 分钟(命中则重置)**。 +- **usage 字段**: + - OpenAI 兼容 · 隐式命中(官方示例原文): + ```json + "usage": { + "prompt_tokens": 3019, + "completion_tokens": 104, + "total_tokens": 3123, + "prompt_tokens_details": { "cached_tokens": 2048 } + } + ``` + - OpenAI 兼容 / DashScope · 显式缓存:同时上报创建与命中(官方示例原文): + ```json + // 第一次请求(创建缓存) // 第二次请求(命中缓存) + "cache_creation_input_tokens": 1605, "cache_creation_input_tokens": 0, + "cached_tokens": 0, "cached_tokens": 1605, + // 均位于 usage.prompt_tokens_details 下 + ``` + - 原生 DashScope · 视觉模型海外地域(新加坡):命中字段一度为顶层 `usage.cached_tokens`(文档注明"后续将升级至 `prompt_tokens_details.cached_tokens`");国内(北京)地域直接在 `usage.prompt_tokens_details.cached_tokens`。 + - Anthropic 兼容:`usage.cache_read_input_tokens`(命中,**不计入** `input_tokens`)、`usage.cache_creation_input_tokens`(创建)。 +- **计费折扣(官方)**: + - 隐式:命中 token 按输入标准价 **20%**(阿里百炼部署常规模型;`qwen3.8-max` 例外,以控制台为准)。 + - 显式:**创建**缓存 token 按标准输入价 **125%**;**命中**按 **10%**(qwen3.8-max 例外)。 +- **触发阈值(官方)**:阿里云百炼部署模型的隐式缓存最少 **256 tokens**;Qwen3.7 系列约 **2000 tokens**。 +- **实验要点**:多协议多形态是千问的特色——OpenAI 兼容/DashScope 看 `prompt_tokens_details`,Anthropic 兼容看 `cache_read_input_tokens`,部分海外地域 DashScope 看顶层 `cached_tokens`。本任务重点实验是 OpenAI 兼容 + 原生 DashScope 两种。 + +--- + +### 2.4 智谱 GLM(bigmodel.cn) + +- **官方文档**:《上下文缓存》https://docs.bigmodel.cn/cn/guide/capabilities/cache +- **缓存机制**:**自动(隐式)缓存**,默认启用,无需手动配置;基于内容相似度/前缀自动触发。 +- **usage 字段(官方原文)**:"响应字段 `usage.prompt_tokens_details.cached_tokens`"—— + ```json + "usage": { + "prompt_tokens": …, + "completion_tokens": …, + "total_tokens": …, + "prompt_tokens_details": { "cached_tokens": … } // ← 命中缓存 token + } + ``` + 官方示例代码取值方式:`response.usage.prompt_tokens_details.cached_tokens`(未命中时需判空/缺省为 0)。 +- **计费折扣(官方)**:缓存命中 Token 按优惠价格计费,"**通常为标准价格的 50%**";新内容按标准价、输出按标准价。GLM Coding Plan 套餐内积分抵扣口径(官方套餐页):GLM-5.3 Input 系数 6.9 / Cached Input 系数 1.7(≈24.6%)。 +- **有效期(官方)**:"缓存有合理的时效性,过期后会重新计算",未公布固定数值。 +- **第三方口径**(阿里云百炼文档):智谱部署的 GLM 触发隐式缓存最少 **512 tokens**;阿里云转售 GLM(ZHIPU/GLM-5.2 等)命中按 25%。 +- **实验要点**:普通对话请求即可验证;同一 system 前缀连续请求看 `prompt_tokens_details.cached_tokens` 是否增长。 + +--- + +### 2.5 字节豆包 Doubao(火山方舟 volces.com) + +- **官方文档** + - 《上下文缓存(Context API)(待下线)》https://www.volcengine.com/docs/82379/1396491 + - 《上下文缓存》主文档 https://www.volcengine.com/docs/82379/1398933 +- **缓存机制**:**仅显式缓存,无自动缓存**。两种 API: + 1. **Context API(待下线)**:先 `POST /api/v3/context/create` 创建缓存(`mode: "session"` 会话缓存 / `"common_prefix"` 前缀缓存,返回 `ctx-*` ID),再调用 `POST /api/v3/context/chat/completions`(请求体带 `context_id`)使用。TTL 可配,范围 1 小时–7 天([3600,604800] 秒),未使用则过期、使用则重置。 + 2. **Responses API(推荐)**:请求体传 `"caching": {"type": "enabled"}`(加 `"prefix": true` 为前缀缓存)创建 Session/前缀缓存,返回缓存 ID;后续用 `"previous_response_id": ""` 复用。过期时刻用 Unix 时间戳配置,最大当前时间 +604800 秒(7 天)。支持多模态与工具缓存、可手动删除任意缓存 ID。 + - 需在控制台「开通管理」→「推理(缓存)定价」开启缓存。 +- **usage 字段(官方示例原文,Context Chat API 响应)**: + ```json + "usage": { + "prompt_tokens": 28, + "completion_tokens": 4, + "total_tokens": 32, + "prompt_tokens_details": { "cached_tokens": 18 } // ← 缓存输入 token + } + ``` + 创建缓存接口的响应同样带 `usage.prompt_tokens_details.cached_tokens`(首建时为 0)。 +- **流式行为**:官方 SDK 示例用 `stream_options={"include_usage": True}` 后,chunk 的 `usage` 非空(含 cached_tokens)。 +- **计费(官方)**:四类——新输入(标准价);**缓存输入(折扣价,显著低于新输入)**;输出(标准价);**存储费**(元/千 token/小时,按每自然小时缓存最大 token 量计,直到 TTL 到期或删除)。官方举例存储单价 0.000017 元/千 token/小时(示例值);Doubao-1.5-pro-32k 示例缓存输入 1.6 元/千万 tokens。实际单价以《模型价格》页为准。 +- **实验要点**:不传缓存参数直接调 `/chat/completions` 是**不会**返回缓存字段的(有自动 KV 缓存但不在 usage 中体现);必须走 Context Chat API(`context_id`)或 Responses API(`caching`/`previous_response_id`)才能看到 `cached_tokens`。 + +--- + +### 2.6 MiniMax(platform.minimaxi.com 国内 / platform.minimax.io 海外) + +- **官方文档**:https://platform.minimax.io/docs/api-reference/text-prompt-caching(国内域名为同一套文档:platform.minimaxi.com) +- **缓存机制**:两套并行—— + 1. **自动缓存(被动 Prompt Caching)**:无需改调用方式。前缀匹配顺序为"工具列表 → 系统提示 → 用户消息"。有效期由系统按负载自动调整,命中则续期。 + 2. **显式缓存(仅 Anthropic 兼容 API)**:在 content 中加 `"cache_control": {"type": "ephemeral"}`,**5 分钟 TTL,命中自动续期**;首次写入缓存有额外费用。 +- **usage 字段**(OpenAI 格式官方示例原文): + ```json + "usage": { + "prompt_tokens": 1200, + "completion_tokens": 300, + "total_tokens": 1500, + "prompt_tokens_details": { "cached_tokens": 800 } // ← 自动缓存命中 + } + ``` + Anthropic 格式(显式/自动均可出现): + ```json + "usage": { "input_tokens": 108, "output_tokens": 91, + "cache_creation_input_tokens": 0, // 创建缓存(显式) + "cache_read_input_tokens": 14813 } // 命中缓存 + ``` +- **触发阈值(官方)**:自动缓存适用于**输入 ≥512 tokens** 的请求。 +- **计费折扣(官方 PayGo 示例)**: + - MiniMax-M3:输入 $0.60/M,命中 $0.12/M(**20%**); + - MiniMax-M2.7:输入 $0.30/M,命中 $0.06/M(20%),显式写入 $0.375/M; + - MiniMax-M2.5 / M2.1:输入 $0.30/M,命中 $0.03/M(10%),显式写入 $0.375/M。 +- **支持模型**:自动缓存——M3 / M2.7 / M2.5 / M2.1 系列;显式缓存——M2.7 / M2.5 / M2.1 / M2 系列。 +- **实验要点**:OpenAI 兼容接口看 `prompt_tokens_details.cached_tokens`;如果用 Anthropic 协议则看 `cache_read_input_tokens`。首次请求建立缓存(可能为 0),第二次请求读取。 + +--- + +### 2.7 阶跃星辰 Step(platform.stepfun.com) + +- **官方文档**:《Prompt 缓存最佳实践》https://platform.stepfun.com/docs/zh/guides/developer/prompt-cache +- **缓存机制**:**自动**;请求超过 **256 tokens 时自动启用**,按 Prompt 前缀匹配。缓存淘汰采用 **LRU(最近最少使用)**,不设固定 TTL,高峰期缓存更容易被逐出。 +- **usage 字段(官方示例原文)**——**顶层 `cached_tokens`,不在 details 里**: + ```json + "usage": { + "cached_tokens": 512, // ← 命中缓存 token(顶层!) + "prompt_tokens": 591, + "completion_tokens": 120, + "total_tokens": 711 + } + ``` + 官方判定方法原文:"如果 response.usage 存在 cached_tokens 字段,则表明该请求命中缓存,cached_tokens 的值即为命中的 Token 长度。" +- **流式行为**:官方 Web 搜索示例显示**每个流式 chunk 都带 usage(含 cached_tokens)**,与 OpenAI 惯例(仅末 chunk)不同,需多次读取。 +- **计费折扣(官方)**:缓存部分 Token 按"**对应模型费用的 20%**"计费。 +- **支持模型(官方)**:step-3.7-flash、step-3.5-flash、step-3.5-flash-2603、step-1o-turbo-vision 等(文档列出的系列);其他模型暂不支持。 +- **实验要点**:prompt 要 ≥256 tokens 才有缓存;命中读取顶层 `usage.cached_tokens`(注意与 DeepSeek 顶层字段不同名)。 + +--- + +### 2.8 百度文心 ERNIE(千帆 ModelBuilder) + +- **官方文档**:《prompt cache 上线公告》https://ai.baidu.com/ai-doc/WENXINWORKSHOP/Rm6uq7jy9 +- **缓存机制**:**自动**,对所有用户默认开启,无需修改代码(官方原文)。 +- **usage 字段(官方响应示例原文)**: + ```json + "usage": { + "prompt_tokens": 159, + "completion_tokens": 89, + "total_tokens": 248, + "prompt_tokens_details": { "cached_tokens": 128 } // ← 命中缓存 token + } + ``` + 官方说明:"当本次请求已命中缓存,usage 中返回 cached_tokens 字段……代表命中缓存的 token 数量。"(即未命中时该字段可能缺失。) +- **计费折扣(官方)**:命中缓存的 `cached_tokens` 按 `prompt_tokens` 单价的 **40%** 计算。模型示例:ERNIE-4.0-Turbo-8K 输入(命中)0.0012 元/千 tokens vs 输入(未命中)0.003 元/千 tokens,输出 0.009 元/千 tokens。 +- **有效期(官方)**:"系统将定期清理一段时间没有使用过的缓存";官方同时明确"命中概率并不是 100%,即使上下文完全一致的请求也存在无法命中的概率"。 +- **实验要点**:同一 prompt 连续请求(官方示例即为同样长文案换问题),观察 `prompt_tokens_details.cached_tokens`;未命中时字段可能缺失,需容错。 + +--- + +## 三、跨厂商对照(实验脚本设计要点) + +### 3.1 字段位置差异(最重要) + +| 厂商 | 命中字段完整路径 | 未命中时表现 | +|---|---|---| +| DeepSeek | `usage.prompt_cache_hit_tokens`(顶层,另有 `prompt_cache_miss_tokens`) | 返回 0 | +| Kimi | `usage.cached_tokens`(顶层)或 `usage.prompt_tokens_details.cached_tokens` | 文档两种示例并存 | +| Qwen(OpenAI/百炼) | `usage.prompt_tokens_details.cached_tokens`;显式另有 `cache_creation_input_tokens` | 未命中为 0/缺失 | +| Qwen(DashScope 海外部分模型) | `usage.cached_tokens`(顶层) | — | +| GLM | `usage.prompt_tokens_details.cached_tokens` | 缺失(官方例程判空) | +| 豆包 | `usage.prompt_tokens_details.cached_tokens` | 需要显式缓存才出现 | +| MiniMax | OpenAI 格式:`usage.prompt_tokens_details.cached_tokens`;Anthropic 格式:`usage.cache_read_input_tokens` / `cache_creation_input_tokens` | 首次请求命中可能为 0 | +| Step | `usage.cached_tokens`(顶层) | 未命中时无该字段(官方判定) | +| 百度千帆 | `usage.prompt_tokens_details.cached_tokens` | 缺失 | + +**兼容读取建议**:统一读取器按以下优先级取值—— +``` +candidates = [ + usage.get("prompt_cache_hit_tokens"), # DeepSeek + usage.get("cached_tokens"), # Kimi / Step / 部分 DashScope + (usage.get("prompt_tokens_details") or {}).get("cached_tokens"), # 其余各家 + (usage.get("prompt_tokens_details") or {}).get("cache_read_input_tokens"), # Anthropic 兼容 +] +``` + +### 3.2 流式 usage 位置差异 +- **DeepSeek**:末 chunk 或 include_usage 追加 chunk。 +- **Kimi**:流式末 chunk 携带 usage。 +- **Step**:每个 chunk 都可能带 usage。 +- **豆包**:SDK 需 `stream_options={"include_usage": True}`。 +- 其余(Qwen/GLM/MiniMax/千帆):按 OpenAI 惯例,`stream_options.include_usage=true` 时末 chunk 带 usage;非流式直接看响应 usage。 + +### 3.3 缓存触发阈值 +- Step:≥256 tokens;MiniMax:≥512 tokens;Qwen 隐式:≥256(部分模型更高);全局建议:构造 **≥2048 tokens 的稳定前缀** 再测,避开各家阈值差异。 + +### 3.4 显式 vs 自动(决定实验脚本形态) +- 只发普通请求即可验证:DeepSeek、Kimi、Qwen(隐式)、GLM、MiniMax、Step、百度千帆。 +- 必须额外走显式流程:**豆包**(先建缓存/传 caching 参数);Qwen 如需显式命中(cache_control ephemeral,5 分钟 TTL、1024 tokens 起)也需加标记。 + +--- + +## 四、来源清单与确定性分级 + +| 事实 | 确定性 | 依据 | +|---|---|---| +| DeepSeek usage 顶层 `prompt_cache_hit_tokens/miss_tokens`;V4 缓存命中价($0.014 vs $0.44 峰值) | 高(官方 API 参考与定价页原文) | https://api-docs.deepseek.com/api/create-chat-completion · /quick_start/pricing | +| Kimi `usage.cached_tokens`(顶层);`prompt_cache_key`;流式末 chunk 带 usage;K3 命中 10%($0.30/$3.00) | 高(官方指南/API/定价页;K3 价格亦有第三方 blog 复述一致) | https://platform.kimi.com/docs/api/chat · /docs/guides/context-caching · /docs/pricing/chat | +| Qwen OpenAI 兼容 & DashScope 隐式/显式缓存字段、1024 阈值、5min TTL、20%/10%/125% 计费 | 高(阿里云官方文档原文+示例 JSON) | https://help.aliyun.com/zh/model-studio/context-cache | +| GLM 自动缓存、`prompt_tokens_details.cached_tokens`、命中约 50% | 高(智谱官方);智谱部署 512 阈值、25% 折扣为阿里云文档转述(中) | https://docs.bigmodel.cn/cn/guide/capabilities/cache | +| 豆包仅显式缓存、`prompt_tokens_details.cached_tokens`、TTL 1h–7d、Responses API caching 参数 | 高(火山方舟官方文档原文) | https://www.volcengine.com/docs/82379/1396491 · 1398933 | +| MiniMax 自动≥512、OpenAI/Anthropic 双字段、M3 命中 20%($0.12/$0.60) | 高(官方文档);RooCode issue 表格亦一致 | https://platform.minimax.io/docs/api-reference/text-prompt-caching | +| Step 自动≥256、顶层 `cached_tokens`、20% 计费、LRU | 高(官方文档原文) | https://platform.stepfun.com/docs/zh/guides/developer/prompt-cache | +| 百度千帆自动默认开启、`prompt_tokens_details.cached_tokens`、40% 计费 | 高(官方公告响应示例) | https://ai.baidu.com/ai-doc/WENXINWORKSHOP/Rm6uq7jy9 | +| DeepSeek V4-Pro 人民币缓存价(0.1 元 vs 3 元/百万) | 低-中(仅第三方知乎转述,非官方) | 第三方报道 | +| Kimi 命中价逐模型数值 | 中(官方定价页为主,第三方 blog 佐证) | platform.kimi.com pricing + 第三方 blog | + +**验证状态**:以上均为**官方文档调研结论**,尚未做真实 API 请求实测。建议下一步按第三节要点构造实验脚本逐家验证字段如实返回。 \ No newline at end of file diff --git a/cache_research/official_china/usage_fields_reference.md b/cache_research/official_china/usage_fields_reference.md new file mode 100644 index 00000000..2c1e7c67 --- /dev/null +++ b/cache_research/official_china/usage_fields_reference.md @@ -0,0 +1,91 @@ +# 国内官方 LLM API 缓存字段速查(官方示例 JSON 摘录) + +> 配套报告:同目录 `README.md`。以下 JSON 均为各厂商**官方文档原文示例**摘录,直接复制进实验脚本对照。 + +## 1. DeepSeek —— usage 顶层命中/未命中 +```json +"usage": { + "prompt_tokens": 16, + "completion_tokens": 10, + "total_tokens": 26, + "prompt_cache_hit_tokens": 0, + "prompt_cache_miss_tokens": 16, + "completion_tokens_details": { "reasoning_tokens": 0 } +} +``` + +## 2. Moonshot Kimi —— usage 顶层 cached_tokens(官方示例原文) +非流式响应: +```json +"usage": { "prompt_tokens": 19, "completion_tokens": 21, "total_tokens": 40, "cached_tokens": 10 } +``` +流式响应(最后一个 chunk,finish_reason=stop 时携带): +```json +"usage": {"prompt_tokens":19,"completion_tokens":13,"total_tokens":32,"cached_tokens":12} +``` +请求参数 `prompt_cache_key`(官方原文):“用于缓存相似请求的响应以优化缓存命中率。对于 Coding Agent,通常是代表单个会话的 session id 或 task id;退出并恢复会话时应保持不变。对于 Kimi Code Plan,此字段为必填以提高缓存命中率。” +(官方《上下文缓存指南》PDF 示例亦出现 `usage.prompt_tokens_details.cached_tokens`,两处并存,实验需双读。) + +## 3. 通义千问 Qwen(阿里云百炼,OpenAI 兼容) +隐式命中: +```json +"usage": { "prompt_tokens": 3019, "completion_tokens": 104, "total_tokens": 3123, + "prompt_tokens_details": { "cached_tokens": 2048 } } +``` +显式(cache_control ephemeral): +```json +"usage": { "prompt_tokens": 2174, "completion_tokens": 0, + "prompt_tokens_details": { "cache_creation_input_tokens": 2156, "cached_tokens": 0 } } +// 第二次请求命中:cache_creation_input_tokens=0, cached_tokens=2156 +``` +原生 DashScope:`usage.prompt_tokens_details['cached_tokens']`(部分海外地域视觉模型为顶层 `usage.cached_tokens`,官方注明后续升级)。 + +## 4. 智谱 GLM +> 官方文档只给出字段名,未公布具体示例数字,以下为字段结构示意(值用占位符): +```json +"usage": { "prompt_tokens": , "completion_tokens": , "total_tokens": , + "prompt_tokens_details": { "cached_tokens": } } +``` + +## 5. 字节豆包(火山方舟 Context Chat API) +```json +"usage": { "prompt_tokens": 28, "completion_tokens": 4, "total_tokens": 32, + "prompt_tokens_details": { "cached_tokens": 18 } } +``` +(需创建 ctx-* 缓存并传 context_id;或 Responses API 传 `"caching":{"type":"enabled"}` / `previous_response_id`。) + +## 6. MiniMax +OpenAI 兼容格式: +```json +"usage": { "prompt_tokens": 1200, "completion_tokens": 300, "total_tokens": 1500, + "prompt_tokens_details": { "cached_tokens": 800 } } +``` +Anthropic/Messages 格式(自动或显式均可出现): +```json +"usage": { "input_tokens": 108, "output_tokens": 91, + "cache_creation_input_tokens": 0, "cache_read_input_tokens": 14813 } +``` + +## 7. 阶跃星辰 Step —— usage 顶层 cached_tokens +```json +"usage": { "cached_tokens": 512, "prompt_tokens": 591, "completion_tokens": 120, "total_tokens": 711 } +``` + +## 8. 百度文心(千帆 ModelBuilder) +```json +"usage": { "prompt_tokens": 159, "completion_tokens": 89, "total_tokens": 248, + "prompt_tokens_details": { "cached_tokens": 128 } } +``` + +## 统一读取优先级(实验脚本建议) +```python +usage = resp.get("usage") or {} +pdet = usage.get("prompt_tokens_details") or {} +cached = ( + usage.get("prompt_cache_hit_tokens") # DeepSeek + or usage.get("cached_tokens") # Kimi / Step / 部分 DashScope + or pdet.get("cached_tokens") # Qwen/GLM/豆包/MiniMax/千帆 + or pdet.get("cache_read_input_tokens") # Anthropic 兼容 + or 0 +) +``` \ No newline at end of file diff --git a/cache_research/official_overseas/_src/gemini-generate-content-api.txt b/cache_research/official_overseas/_src/gemini-generate-content-api.txt new file mode 100644 index 00000000..4e88b818 --- /dev/null +++ b/cache_research/official_overseas/_src/gemini-generate-content-api.txt @@ -0,0 +1,6810 @@ +![Gemini API](https://ai.google.dev/_static/googledevai/images/gemini-api-logo.svg) +![Gemini API](https://ai.google.dev/_static/googledevai/images/gemini-api-logo.svg) + +# Generating content + +The Gemini API supports content generation with images, audio, code, tools, and more. For details on each of these features, read on and check out the task-focused sample code, or read the comprehensive guides. + +## Method: models.generateContent + +Generates a model response given an input `GenerateContentRequest`. Refer to the [text generation guide](https://ai.google.dev/gemini-api/docs/text-generation) for detailed usage information. Input capabilities differ between models, including tuned models. Refer to the [model guide](https://ai.google.dev/gemini-api/docs/models/gemini) and [tuning guide](https://ai.google.dev/gemini-api/docs/model-tuning) for details. + +`GenerateContentRequest` + +### Endpoint + +`https://generativelanguage.googleapis.com/v1beta/{model=models/*}:generateContent` + +### Path parameters + +`model` +`string` + +Required. The name of the `Model` to use for generating the completion. + +`Model` + +Format: `models/{model}`. It takes the form `models/{model}`. + +`models/{model}` +`models/{model}` + +### Request body + +The request body contains data with the following structure: + +`contents[]` +`object (Content)` +`Content` + +Required. The content of the current conversation with the model. + +For single-turn queries, this is a single instance. For multi-turn queries like [chat](https://ai.google.dev/gemini-api/docs/text-generation#chat), this is a repeated field that contains the conversation history and the latest request. + +`tools[]` +`object (Tool)` +`Tool` + +Optional. A list of `Tools` the `Model` may use to generate the next response. + +`Tools` +`Model` + +A `Tool` is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the `Model`. Supported `Tool`s are `Function` and `codeExecution`. Refer to the [Function calling](https://ai.google.dev/gemini-api/docs/function-calling) and the [Code execution](https://ai.google.dev/gemini-api/docs/code-execution) guides to learn more. + +`Tool` +`Model` +`Tool` +`Function` +`codeExecution` +`toolConfig` +`object (ToolConfig)` +`ToolConfig` + +Optional. Tool configuration for any `Tool` specified in the request. Refer to the [Function calling guide](https://ai.google.dev/gemini-api/docs/function-calling#function_calling_mode) for a usage example. + +`Tool` +`safetySettings[]` +`object (SafetySetting)` +`SafetySetting` + +Optional. A list of unique `SafetySetting` instances for blocking unsafe content. + +`SafetySetting` + +This will be enforced on the `GenerateContentRequest.contents` and `GenerateContentResponse.candidates`. There should not be more than one setting for each `SafetyCategory` type. The API will block any contents and responses that fail to meet the thresholds set by these settings. This list overrides the default settings for each `SafetyCategory` specified in the safetySettings. If there is no `SafetySetting` for a given `SafetyCategory` provided in the list, the API will use the default safety setting for that category. Harm categories HARM\_CATEGORY\_HATE\_SPEECH, HARM\_CATEGORY\_SEXUALLY\_EXPLICIT, HARM\_CATEGORY\_DANGEROUS\_CONTENT, HARM\_CATEGORY\_HARASSMENT, HARM\_CATEGORY\_CIVIC\_INTEGRITY, HARM\_CATEGORY\_JAILBREAK are supported. Refer to the [guide](https://ai.google.dev/gemini-api/docs/safety-settings) for detailed information on available safety settings. Also refer to the [Safety guidance](https://ai.google.dev/gemini-api/docs/safety-guidance) to learn how to incorporate safety considerations in your AI applications. + +`GenerateContentRequest.contents` +`GenerateContentResponse.candidates` +`SafetyCategory` +`SafetyCategory` +`SafetySetting` +`SafetyCategory` +`systemInstruction` +`object (Content)` +`Content` + +Optional. Developer set [system instruction(s)](https://ai.google.dev/gemini-api/docs/system-instructions). Currently, text only. + +`generationConfig` +`object (GenerationConfig)` +`GenerationConfig` + +Optional. Configuration options for model generation and outputs. + +`cachedContent` +`string` + +Optional. The name of the content [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to serve the prediction. Format: `cachedContents/{cachedContent}` + +`cachedContents/{cachedContent}` +`serviceTier` +`enum (ServiceTier)` +`ServiceTier` + +Optional. The service tier of the request. + +`store` +`boolean` + +Optional. Configures the logging behavior for a given request. If set, it takes precedence over the project-level logging config. + +### Example request + +### Text + +### Python + +`from google import genai +client = genai.Client() +response = client.models.generate_content( +model="gemini-3.7-flash", contents="Write a story about a magic backpack." +) +print(response.text) + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: "Write a story about a magic backpack.", +}); +console.log(response.text); + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +contents := []*genai.Content{ + genai.NewContentFromText("Write a story about a magic backpack.", genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, nil) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +text_generation.go` + +### Shell + +`curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[{"text": "Write a story about a magic backpack."}] + }] + }' 2> /dev/null + +text_generation.sh` + +### Java + +`Client client = new Client(); +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + "Write a story about a magic backpack.", + null); +System.out.println(response.text()); + +TextGeneration.java` + +### Image + +### Python + +`from google import genai +import PIL.Image +client = genai.Client() +organ = PIL.Image.open(media / "organ.jpg") +response = client.models.generate_content( +model="gemini-3.7-flash", contents=["Tell me about this instrument", organ] +) +print(response.text) + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const organ = await ai.files.upload({ + file: path.join(media, "organ.jpg"), +}); +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: [ + createUserContent([ + "Tell me about this instrument", + createPartFromUri(organ.uri, organ.mimeType) + ]), + ], +}); +console.log(response.text); + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "organ.jpg"), + &genai.UploadFileConfig{ + MIMEType : "image/jpeg", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromText("Tell me about this instrument"), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, nil) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +text_generation.go` + +### Shell + +`# Use a temporary file to hold the base64 encoded image data +TEMP_B64=$(mktemp) +trap 'rm -f "$TEMP_B64"' EXIT +base64 $B64FLAGS $IMG_PATH > "$TEMP_B64" +# Use a temporary file to hold the JSON payload +TEMP_JSON=$(mktemp) +trap 'rm -f "$TEMP_JSON"' EXIT +cat > "$TEMP_JSON" << EOF +{ + "contents": [{ + "parts":[ + {"text": "Tell me about this instrument"}, + { + "inline_data": { + "mime_type":"image/jpeg", + "data": "$(cat "$TEMP_B64")" + } + } + ] + }] +} +EOF +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d "@$TEMP_JSON" 2> /dev/null + +text_generation.sh` + +### Java + +`Client client = new Client(); +String path = media_path + "organ.jpg"; +byte[] imageData = Files.readAllBytes(Paths.get(path)); +Content content = + Content.fromParts( + Part.fromText("Tell me about this instrument."), + Part.fromBytes(imageData, "image/jpeg")); +GenerateContentResponse response = client.models.generateContent("gemini-3.7-flash", content, null); +System.out.println(response.text()); + +TextGeneration.java` + +### Audio + +### Python + +`from google import genai +client = genai.Client() +sample_audio = client.files.upload(file=media / "sample.mp3") +response = client.models.generate_content( +model="gemini-3.7-flash", +contents=["Give me a summary of this audio file.", sample_audio], +) +print(response.text) + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const audio = await ai.files.upload({ + file: path.join(media, "sample.mp3"), +}); +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: [ + createUserContent([ + "Give me a summary of this audio file.", + createPartFromUri(audio.uri, audio.mimeType), + ]), + ], +}); +console.log(response.text); + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "sample.mp3"), + &genai.UploadFileConfig{ + MIMEType : "audio/mpeg", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromText("Give me a summary of this audio file."), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, nil) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +text_generation.go` + +### Shell + +`# Use File API to upload audio data to API request. +MIME_TYPE=$(file -b --mime-type "${AUDIO_PATH}") +NUM_BYTES=$(wc -c < "${AUDIO_PATH}") +DISPLAY_NAME=AUDIO +tmp_header_file=upload-header.tmp +# Initial resumable request defining metadata. +# The upload url is in the response headers dump them to a file. +curl "${BASE_URL}/upload/v1beta/files?key=${GEMINI_API_KEY}" \ + -D upload-header.tmp \ + -H "X-Goog-Upload-Protocol: resumable" \ + -H "X-Goog-Upload-Command: start" \ + -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ + -H "Content-Type: application/json" \ + -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null +upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") +rm "${tmp_header_file}" +# Upload the actual bytes. +curl "${upload_url}" \ + -H "Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Offset: 0" \ + -H "X-Goog-Upload-Command: upload, finalize" \ + --data-binary "@${AUDIO_PATH}" 2> /dev/null > file_info.json +file_uri=$(jq ".file.uri" file_info.json) +echo file_uri=$file_uri +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Please describe this file."}, + {"file_data":{"mime_type": "audio/mpeg", "file_uri": '$file_uri'}}] + }] + }' 2> /dev/null > response.json +cat response.json +echo +jq ".candidates[].content.parts[].text" response.json + +text_generation.sh` + +### Video + +### Python + +`from google import genai +import time +client = genai.Client() +# Video clip (CC BY 3.0) from https://peach.blender.org/download/ +myfile = client.files.upload(file=media / "Big_Buck_Bunny.mp4") +print(f"{myfile=}") +# Poll until the video file is completely processed (state becomes ACTIVE). +while not myfile.state or myfile.state.name != "ACTIVE": +print("Processing video...") +print("File state:", myfile.state) +time.sleep(5) +myfile = client.files.get(name=myfile.name) +response = client.models.generate_content( +model="gemini-3.7-flash", contents=[myfile, "Describe this video clip"] +) +print(f"{response.text=}") + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +let video = await ai.files.upload({ + file: path.join(media, 'Big_Buck_Bunny.mp4'), +}); +// Poll until the video file is completely processed (state becomes ACTIVE). +while (!video.state || video.state.toString() !== 'ACTIVE') { + console.log('Processing video...'); + console.log('File state: ', video.state); + await sleep(5000); + video = await ai.files.get({name: video.name}); +} +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: [ + createUserContent([ + "Describe this video clip", + createPartFromUri(video.uri, video.mimeType), + ]), + ], +}); +console.log(response.text); + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "Big_Buck_Bunny.mp4"), + &genai.UploadFileConfig{ + MIMEType : "video/mp4", + }, +) +if err != nil { + log.Fatal(err) +} +// Poll until the video file is completely processed (state becomes ACTIVE). +for file.State == genai.FileStateUnspecified || file.State != genai.FileStateActive { + fmt.Println("Processing video...") + fmt.Println("File state:", file.State) + time.Sleep(5 * time.Second) + file, err = client.Files.Get(ctx, file.Name, nil) + if err != nil { + log.Fatal(err) + } +} +parts := []*genai.Part{ + genai.NewPartFromText("Describe this video clip"), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, nil) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +text_generation.go` + +### Shell + +`# Use File API to upload audio data to API request. +MIME_TYPE=$(file -b --mime-type "${VIDEO_PATH}") +NUM_BYTES=$(wc -c < "${VIDEO_PATH}") +DISPLAY_NAME=VIDEO +# Initial resumable request defining metadata. +# The upload url is in the response headers dump them to a file. +curl "${BASE_URL}/upload/v1beta/files?key=${GEMINI_API_KEY}" \ + -D "${tmp_header_file}" \ + -H "X-Goog-Upload-Protocol: resumable" \ + -H "X-Goog-Upload-Command: start" \ + -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ + -H "Content-Type: application/json" \ + -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null +upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") +rm "${tmp_header_file}" +# Upload the actual bytes. +curl "${upload_url}" \ + -H "Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Offset: 0" \ + -H "X-Goog-Upload-Command: upload, finalize" \ + --data-binary "@${VIDEO_PATH}" 2> /dev/null > file_info.json +file_uri=$(jq ".file.uri" file_info.json) +echo file_uri=$file_uri +state=$(jq ".file.state" file_info.json) +echo state=$state +name=$(jq ".file.name" file_info.json) +echo name=$name +while [[ "($state)" = *"PROCESSING"* ]]; +do + echo "Processing video..." + sleep 5 + # Get the file of interest to check state + curl https://generativelanguage.googleapis.com/v1beta/files/$name > file_info.json + state=$(jq ".file.state" file_info.json) +done +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Transcribe the audio from this video, giving timestamps for salient events in the video. Also provide visual descriptions."}, + {"file_data":{"mime_type": "video/mp4", "file_uri": '$file_uri'}}] + }] + }' 2> /dev/null > response.json +cat response.json +echo +jq ".candidates[].content.parts[].text" response.json + +text_generation.sh` + +### PDF + +### Python + +`from google import genai +client = genai.Client() +sample_pdf = client.files.upload(file=media / "test.pdf") +response = client.models.generate_content( +model="gemini-3.7-flash", +contents=["Give me a summary of this document:", sample_pdf], +) +print(f"{response.text=}") + +text_generation.py` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "test.pdf"), + &genai.UploadFileConfig{ + MIMEType : "application/pdf", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromText("Give me a summary of this document:"), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, nil) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +text_generation.go` + +### Shell + +`MIME_TYPE=$(file -b --mime-type "${PDF_PATH}") +NUM_BYTES=$(wc -c < "${PDF_PATH}") +DISPLAY_NAME=TEXT +echo $MIME_TYPE +tmp_header_file=upload-header.tmp +# Initial resumable request defining metadata. +# The upload url is in the response headers dump them to a file. +curl "${BASE_URL}/upload/v1beta/files?key=${GEMINI_API_KEY}" \ + -D upload-header.tmp \ + -H "X-Goog-Upload-Protocol: resumable" \ + -H "X-Goog-Upload-Command: start" \ + -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ + -H "Content-Type: application/json" \ + -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null +upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") +rm "${tmp_header_file}" +# Upload the actual bytes. +curl "${upload_url}" \ + -H "Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Offset: 0" \ + -H "X-Goog-Upload-Command: upload, finalize" \ + --data-binary "@${PDF_PATH}" 2> /dev/null > file_info.json +file_uri=$(jq ".file.uri" file_info.json) +echo file_uri=$file_uri +# Now generate content using that file +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Can you add a few more lines to this poem?"}, + {"file_data":{"mime_type": "application/pdf", "file_uri": '$file_uri'}}] + }] + }' 2> /dev/null > response.json +cat response.json +echo +jq ".candidates[].content.parts[].text" response.json + +text_generation.sh` + +### Chat + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +# Pass initial history using the "history" argument +chat = client.chats.create( +model="gemini-3.7-flash", +history=[ +types.Content(role="user", parts=[types.Part(text="Hello")]), +types.Content( +role="model", +parts=[ +types.Part( +text="Great to meet you. What would you like to know?" +) +], +), +], +) +response = chat.send_message(message="I have 2 dogs in my house.") +print(response.text) +response = chat.send_message(message="How many paws are in my house?") +print(response.text) + +chat.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const chat = ai.chats.create({ + model: "gemini-3.7-flash", + history: [ + { + role: "user", + parts: [{ text: "Hello" }], + }, + { + role: "model", + parts: [{ text: "Great to meet you. What would you like to know?" }], + }, + ], +}); +const response1 = await chat.sendMessage({ + message: "I have 2 dogs in my house.", +}); +console.log("Chat response 1:", response1.text); +const response2 = await chat.sendMessage({ + message: "How many paws are in my house?", +}); +console.log("Chat response 2:", response2.text); + +chat.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +// Pass initial history using the History field. +history := []*genai.Content{ + genai.NewContentFromText("Hello", genai.RoleUser), + genai.NewContentFromText("Great to meet you. What would you like to know?", genai.RoleModel), +} +chat, err := client.Chats.Create(ctx, "gemini-3.7-flash", nil, history) +if err != nil { + log.Fatal(err) +} +firstResp, err := chat.SendMessage(ctx, genai.Part{Text: "I have 2 dogs in my house."}) +if err != nil { + log.Fatal(err) +} +fmt.Println(firstResp.Text()) +secondResp, err := chat.SendMessage(ctx, genai.Part{Text: "How many paws are in my house?"}) +if err != nil { + log.Fatal(err) +} +fmt.Println(secondResp.Text()) + +chat.go` + +### Shell + +`curl https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [ + {"role":"user", + "parts":[{ + "text": "Hello"}]}, + {"role": "model", + "parts":[{ + "text": "Great to meet you. What would you like to know?"}]}, + {"role":"user", + "parts":[{ + "text": "I have two dogs in my house. How many paws are in my house?"}]}, + ] + }' 2> /dev/null | grep "text" + +chat.sh` + +### Java + +`Client client = new Client(); +Content userContent = Content.fromParts(Part.fromText("Hello")); +Content modelContent = + Content.builder() + .role("model") + .parts( + Collections.singletonList( + Part.fromText("Great to meet you. What would you like to know?") + ) + ).build(); +Chat chat = client.chats.create( + "gemini-3.7-flash", + GenerateContentConfig.builder() + .systemInstruction(userContent) + .systemInstruction(modelContent) + .build() +); +GenerateContentResponse response1 = chat.sendMessage("I have 2 dogs in my house."); +System.out.println(response1.text()); +GenerateContentResponse response2 = chat.sendMessage("How many paws are in my house?"); +System.out.println(response2.text()); + +ChatSession.java` + +### Cache + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +document = client.files.upload(file=media / "a11.txt") +model_name = "gemini-3.7-flash" +cache = client.caches.create( +model=model_name, +config=types.CreateCachedContentConfig( +contents=[document], +system_instruction="You are an expert analyzing transcripts.", +), +) +print(cache) +response = client.models.generate_content( +model=model_name, +contents="Please summarize this transcript", +config=types.GenerateContentConfig(cached_content=cache.name), +) +print(response.text) + +cache.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const filePath = path.join(media, "a11.txt"); +const document = await ai.files.upload({ + file: filePath, + config: { mimeType: "text/plain" }, +}); +console.log("Uploaded file name:", document.name); +const modelName = "gemini-3.7-flash"; +const contents = [ + createUserContent(createPartFromUri(document.uri, document.mimeType)), +]; +const cache = await ai.caches.create({ + model: modelName, + config: { + contents: contents, + systemInstruction: "You are an expert analyzing transcripts.", + }, +}); +console.log("Cache created:", cache); +const response = await ai.models.generateContent({ + model: modelName, + contents: "Please summarize this transcript", + config: { cachedContent: cache.name }, +}); +console.log("Response text:", response.text); + +cache.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +modelName := "gemini-3.7-flash" +document, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "a11.txt"), + &genai.UploadFileConfig{ + MIMEType : "text/plain", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromURI(document.URI, document.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +cache, err := client.Caches.Create(ctx, modelName, &genai.CreateCachedContentConfig{ + Contents: contents, + SystemInstruction: genai.NewContentFromText( + "You are an expert analyzing transcripts.", genai.RoleUser, + ), +}) +if err != nil { + log.Fatal(err) +} +fmt.Println("Cache created:") +fmt.Println(cache) +// Use the cache for generating content. +response, err := client.Models.GenerateContent( + ctx, + modelName, + genai.Text("Please summarize this transcript"), + &genai.GenerateContentConfig{ + CachedContent: cache.Name, + }, +) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +cache.go` + +### Tuned Model + +### Python + +`# With Gemini 2 we're launching a new SDK. See the following doc for details. +# https://ai.google.dev/gemini-api/docs/migrate + +README.md` + +### JSON Mode + +### Python + +`from google import genai +from google.genai import types +from typing_extensions import TypedDict +class Recipe(TypedDict): +recipe_name: str +ingredients: list[str] +client = genai.Client() +result = client.models.generate_content( +model="gemini-3.7-flash", +contents="List a few popular cookie recipes.", +config=types.GenerateContentConfig( +response_mime_type="application/json", response_schema=list[Recipe] +), +) +print(result) + +controlled_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: "List a few popular cookie recipes.", + config: { + responseMimeType: "application/json", + responseSchema: { + type: "array", + items: { + type: "object", + properties: { + recipeName: { type: "string" }, + ingredients: { type: "array", items: { type: "string" } }, + }, + required: ["recipeName", "ingredients"], + }, + }, + }, +}); +console.log(response.text); + +controlled_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +schema := &genai.Schema{ + Type: genai.TypeArray, + Items: &genai.Schema{ + Type: genai.TypeObject, + Properties: map[string]*genai.Schema{ + "recipe_name": {Type: genai.TypeString}, + "ingredients": { + Type: genai.TypeArray, + Items: &genai.Schema{Type: genai.TypeString}, + }, + }, + Required: []string{"recipe_name"}, + }, +} +config := &genai.GenerateContentConfig{ + ResponseMIMEType: "application/json", + ResponseSchema: schema, +} +response, err := client.Models.GenerateContent( + ctx, + "gemini-3.7-flash", + genai.Text("List a few popular cookie recipes."), + config, +) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +controlled_generation.go` + +### Shell + +`curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ +-H 'Content-Type: application/json' \ +-d '{ + "contents": [{ + "parts":[ + {"text": "List 5 popular cookie recipes"} + ] + }], + "generationConfig": { + "response_mime_type": "application/json", + "response_schema": { + "type": "ARRAY", + "items": { + "type": "OBJECT", + "properties": { + "recipe_name": {"type":"STRING"}, + } + } + } + } +}' 2> /dev/null | head + +controlled_generation.sh` + +### Java + +`Client client = new Client(); +Schema recipeSchema = Schema.builder() + .type(Array.class.getSimpleName()) + .items(Schema.builder() + .type(Object.class.getSimpleName()) + .properties( + Map.of("recipe_name", Schema.builder() + .type(String.class.getSimpleName()) + .build(), + "ingredients", Schema.builder() + .type(Array.class.getSimpleName()) + .items(Schema.builder() + .type(String.class.getSimpleName()) + .build()) + .build()) + ) + .required(List.of("recipe_name", "ingredients")) + .build()) + .build(); +GenerateContentConfig config = + GenerateContentConfig.builder() + .responseMimeType("application/json") + .responseSchema(recipeSchema) + .build(); +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + "List a few popular cookie recipes.", + config); +System.out.println(response.text()); + +ControlledGeneration.java` + +### Code execution + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +response = client.models.generate_content( +model="gemini-3.7-flash", +contents=( +"Write and execute code that calculates the sum of the first 50 prime numbers. " +"Ensure that only the executable code and its resulting output are generated." +), +) +# Each part may contain text, executable code, or an execution result. +for part in response.candidates[0].content.parts: +print(part, "\n") +print("-" * 80) +# The .text accessor concatenates the parts into a markdown-formatted text. +print("\n", response.text) + +code_execution.py` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +response, err := client.Models.GenerateContent( + ctx, + "gemini-3.7-flash", + genai.Text( + `Write and execute code that calculates the sum of the first 50 prime numbers. + Ensure that only the executable code and its resulting output are generated.`, + ), + &genai.GenerateContentConfig{}, +) +if err != nil { + log.Fatal(err) +} +// Print the response. +printResponse(response) +fmt.Println("--------------------------------------------------------------------------------") +fmt.Println(response.Text()) + +code_execution.go` + +### Java + +`Client client = new Client(); +String prompt = """ + Write and execute code that calculates the sum of the first 50 prime numbers. + Ensure that only the executable code and its resulting output are generated. + """; +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + prompt, + null); +for (Part part : response.candidates().get().getFirst().content().get().parts().get()) { + System.out.println(part + "\n"); +} +System.out.println("-".repeat(80)); +System.out.println(response.text()); + +CodeExecution.java` + +### Function Calling + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +def add(a: float, b: float) -> float: + """returns a + b.""" +return a + b +def subtract(a: float, b: float) -> float: + """returns a - b.""" +return a - b +def multiply(a: float, b: float) -> float: + """returns a * b.""" +return a * b +def divide(a: float, b: float) -> float: + """returns a / b.""" +return a / b +# Create a chat session; function calling (via tools) is enabled in the config. +chat = client.chats.create( +model="gemini-3.7-flash", +config=types.GenerateContentConfig(tools=[add, subtract, multiply, divide]), +) +response = chat.send_message( +message="I have 57 cats, each owns 44 mittens, how many mittens is that in total?" +) +print(response.text) + +function_calling.py` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +modelName := "gemini-3.7-flash" +// Create the function declarations for arithmetic operations. +addDeclaration := createArithmeticToolDeclaration("addNumbers", "Return the result of adding two numbers.") +subtractDeclaration := createArithmeticToolDeclaration("subtractNumbers", "Return the result of subtracting the second number from the first.") +multiplyDeclaration := createArithmeticToolDeclaration("multiplyNumbers", "Return the product of two numbers.") +divideDeclaration := createArithmeticToolDeclaration("divideNumbers", "Return the quotient of dividing the first number by the second.") +// Group the function declarations as a tool. +tools := []*genai.Tool{ + { + FunctionDeclarations: []*genai.FunctionDeclaration{ + addDeclaration, + subtractDeclaration, + multiplyDeclaration, + divideDeclaration, + }, + }, +} +// Create the content prompt. +contents := []*genai.Content{ + genai.NewContentFromText( + "I have 57 cats, each owns 44 mittens, how many mittens is that in total?", genai.RoleUser, + ), +} +// Set up the generate content configuration with function calling enabled. +config := &genai.GenerateContentConfig{ + Tools: tools, + ToolConfig: &genai.ToolConfig{ + FunctionCallingConfig: &genai.FunctionCallingConfig{ + // The mode equivalent to FunctionCallingConfigMode.ANY in JS. + Mode: genai.FunctionCallingConfigModeAny, + }, + }, +} +genContentResp, err := client.Models.GenerateContent(ctx, modelName, contents, config) +if err != nil { + log.Fatal(err) +} +// Assume the response includes a list of function calls. +if len(genContentResp.FunctionCalls()) == 0 { + log.Println("No function call returned from the AI.") + return nil +} +functionCall := genContentResp.FunctionCalls()[0] +log.Printf("Function call: %+v\n", functionCall) +// Marshal the Args map into JSON bytes. +argsMap, err := json.Marshal(functionCall.Args) +if err != nil { + log.Fatal(err) +} +// Unmarshal the JSON bytes into the ArithmeticArgs struct. +var args ArithmeticArgs +if err := json.Unmarshal(argsMap, &args); err != nil { + log.Fatal(err) +} +// Map the function name to the actual arithmetic function. +var result float64 +switch functionCall.Name { + case "addNumbers": + result = add(args.FirstParam, args.SecondParam) + case "subtractNumbers": + result = subtract(args.FirstParam, args.SecondParam) + case "multiplyNumbers": + result = multiply(args.FirstParam, args.SecondParam) + case "divideNumbers": + result = divide(args.FirstParam, args.SecondParam) + default: + return fmt.Errorf("unimplemented function: %s", functionCall.Name) +} +log.Printf("Function result: %v\n", result) +// Prepare the final result message as content. +resultContents := []*genai.Content{ + genai.NewContentFromText("The final result is " + fmt.Sprintf("%v", result), genai.RoleUser), +} +// Use GenerateContent to send the final result. +finalResponse, err := client.Models.GenerateContent(ctx, modelName, resultContents, &genai.GenerateContentConfig{}) +if err != nil { + log.Fatal(err) +} +printResponse(finalResponse) + +function_calling.go` + +### Node.js + + `// Make sure to include the following import: + // import {GoogleGenAI} from '@google/genai'; + const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + /** + * The add function returns the sum of two numbers. + * @param {number} a + * @param {number} b + * @returns {number} + */ + function add(a, b) { + return a + b; + } + /** + * The subtract function returns the difference (a - b). + * @param {number} a + * @param {number} b + * @returns {number} + */ + function subtract(a, b) { + return a - b; + } + /** + * The multiply function returns the product of two numbers. + * @param {number} a + * @param {number} b + * @returns {number} + */ + function multiply(a, b) { + return a * b; + } + /** + * The divide function returns the quotient of a divided by b. + * @param {number} a + * @param {number} b + * @returns {number} + */ + function divide(a, b) { + return a / b; + } + const addDeclaration = { + name: "addNumbers", + parameters: { + type: "object", + description: "Return the result of adding two numbers.", + properties: { + firstParam: { + type: "number", + description: + "The first parameter which can be an integer or a floating point number.", + }, + secondParam: { + type: "number", + description: + "The second parameter which can be an integer or a floating point number.", + }, + }, + required: ["firstParam", "secondParam"], + }, + }; + const subtractDeclaration = { + name: "subtractNumbers", + parameters: { + type: "object", + description: + "Return the result of subtracting the second number from the first.", + properties: { + firstParam: { + type: "number", + description: "The first parameter.", + }, + secondParam: { + type: "number", + description: "The second parameter.", + }, + }, + required: ["firstParam", "secondParam"], + }, + }; + const multiplyDeclaration = { + name: "multiplyNumbers", + parameters: { + type: "object", + description: "Return the product of two numbers.", + properties: { + firstParam: { + type: "number", + description: "The first parameter.", + }, + secondParam: { + type: "number", + description: "The second parameter.", + }, + }, + required: ["firstParam", "secondParam"], + }, + }; + const divideDeclaration = { + name: "divideNumbers", + parameters: { + type: "object", + description: + "Return the quotient of dividing the first number by the second.", + properties: { + firstParam: { + type: "number", + description: "The first parameter.", + }, + secondParam: { + type: "number", + description: "The second parameter.", + }, + }, + required: ["firstParam", "secondParam"], + }, + }; + // Step 1: Call generateContent with function calling enabled. + const generateContentResponse = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: + "I have 57 cats, each owns 44 mittens, how many mittens is that in total?", + config: { + toolConfig: { + functionCallingConfig: { + mode: FunctionCallingConfigMode.ANY, + }, + }, + tools: [ + { + functionDeclarations: [ + addDeclaration, + subtractDeclaration, + multiplyDeclaration, + divideDeclaration, + ], + }, + ], + }, + }); + // Step 2: Extract the function call.( + // Assuming the response contains a 'functionCalls' array. + const functionCall = + generateContentResponse.functionCalls && + generateContentResponse.functionCalls[0]; + console.log(functionCall); + // Parse the arguments. + const args = functionCall.args; + // Expected args format: { firstParam: number, secondParam: number } + // Step 3: Invoke the actual function based on the function name. + const functionMapping = { + addNumbers: add, + subtractNumbers: subtract, + multiplyNumbers: multiply, + divideNumbers: divide, + }; + const func = functionMapping[functionCall.name]; + if (!func) { + console.error("Unimplemented error:", functionCall.name); + return generateContentResponse; + } + const resultValue = func(args.firstParam, args.secondParam); + console.log("Function result:", resultValue); + // Step 4: Use the chat API to send the result as the final answer. + const chat = ai.chats.create({ model: "gemini-3.7-flash" }); + const chatResponse = await chat.sendMessage({ + message: "The final result is " + resultValue, + }); + console.log(chatResponse.text); + return chatResponse; +} + +function_calling.js` + +### Shell + +`cat > tools.json << EOF +{ + "function_declarations": [ + { + "name": "enable_lights", + "description": "Turn on the lighting system." + }, + { + "name": "set_light_color", + "description": "Set the light color. Lights must be enabled for this to work.", + "parameters": { + "type": "object", + "properties": { + "rgb_hex": { + "type": "string", + "description": "The light color as a 6-digit hex string, e.g. ff0000 for red." + } + }, + "required": [ + "rgb_hex" + ] + } + }, + { + "name": "stop_lights", + "description": "Turn off the lighting system." + } + ] +} +EOF +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -d @<(echo ' + { + "system_instruction": { + "parts": { + "text": "You are a helpful lighting system bot. You can turn lights on and off, and you can set the color. Do not perform any other tasks." + } + }, + "tools": ['$(cat tools.json)'], + "tool_config": { + "function_calling_config": {"mode": "auto"} + }, + "contents": { + "role": "user", + "parts": { + "text": "Turn on the lights please." + } + } + } +') 2>/dev/null |sed -n '/"content"/,/"finishReason"/p' + +function_calling.sh` + +### Java + +`Client client = new Client(); +FunctionDeclaration addFunction = + FunctionDeclaration.builder() + .name("addNumbers") + .parameters( + Schema.builder() + .type("object") + .properties(Map.of( + "firstParam", Schema.builder().type("number").description("First number").build(), + "secondParam", Schema.builder().type("number").description("Second number").build())) + .required(Arrays.asList("firstParam", "secondParam")) + .build()) + .build(); +FunctionDeclaration subtractFunction = + FunctionDeclaration.builder() + .name("subtractNumbers") + .parameters( + Schema.builder() + .type("object") + .properties(Map.of( + "firstParam", Schema.builder().type("number").description("First number").build(), + "secondParam", Schema.builder().type("number").description("Second number").build())) + .required(Arrays.asList("firstParam", "secondParam")) + .build()) + .build(); +FunctionDeclaration multiplyFunction = + FunctionDeclaration.builder() + .name("multiplyNumbers") + .parameters( + Schema.builder() + .type("object") + .properties(Map.of( + "firstParam", Schema.builder().type("number").description("First number").build(), + "secondParam", Schema.builder().type("number").description("Second number").build())) + .required(Arrays.asList("firstParam", "secondParam")) + .build()) + .build(); +FunctionDeclaration divideFunction = + FunctionDeclaration.builder() + .name("divideNumbers") + .parameters( + Schema.builder() + .type("object") + .properties(Map.of( + "firstParam", Schema.builder().type("number").description("First number").build(), + "secondParam", Schema.builder().type("number").description("Second number").build())) + .required(Arrays.asList("firstParam", "secondParam")) + .build()) + .build(); +GenerateContentConfig config = GenerateContentConfig.builder() + .toolConfig(ToolConfig.builder().functionCallingConfig( + FunctionCallingConfig.builder().mode("ANY").build() + ).build()) + .tools( + Collections.singletonList( + Tool.builder().functionDeclarations( + Arrays.asList( + addFunction, + subtractFunction, + divideFunction, + multiplyFunction + ) + ).build() + ) + ) + .build(); +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + "I have 57 cats, each owns 44 mittens, how many mittens is that in total?", + config); +if (response.functionCalls() == null || response.functionCalls().isEmpty()) { + System.err.println("No function call received"); + return null; +} +var functionCall = response.functionCalls().getFirst(); +String functionName = functionCall.name().get(); +var arguments = functionCall.args(); +Map> functionMapping = new HashMap<>(); +functionMapping.put("addNumbers", (a, b) -> a + b); +functionMapping.put("subtractNumbers", (a, b) -> a - b); +functionMapping.put("multiplyNumbers", (a, b) -> a * b); +functionMapping.put("divideNumbers", (a, b) -> b != 0 ? a / b : Double.NaN); +BiFunction function = functionMapping.get(functionName); +Number firstParam = (Number) arguments.get().get("firstParam"); +Number secondParam = (Number) arguments.get().get("secondParam"); +Double result = function.apply(firstParam.doubleValue(), secondParam.doubleValue()); +System.out.println(result); + +FunctionCalling.java` + +### Generation config + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +response = client.models.generate_content( +model="gemini-3.7-flash", +contents="Tell me a story about a magic backpack.", +config=types.GenerateContentConfig( +candidate_count=1, +stop_sequences=["x"], +max_output_tokens=20, +temperature=1.0, +), +) +print(response.text) + +configure_model_parameters.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: "Tell me a story about a magic backpack.", + config: { + candidateCount: 1, + stopSequences: ["x"], + maxOutputTokens: 20, + temperature: 1.0, + }, +}); +console.log(response.text); + +configure_model_parameters.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +// Create local variables for parameters. +candidateCount := int32(1) +maxOutputTokens := int32(20) +temperature := float32(1.0) +response, err := client.Models.GenerateContent( + ctx, + "gemini-3.7-flash", + genai.Text("Tell me a story about a magic backpack."), + &genai.GenerateContentConfig{ + CandidateCount: candidateCount, + StopSequences: []string{"x"}, + MaxOutputTokens: maxOutputTokens, + Temperature: &temperature, + }, +) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +configure_model_parameters.go` + +### Shell + +`curl https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Explain how AI works"} + ] + }], + "generationConfig": { + "stopSequences": [ + "Title" + ], + "temperature": 1.0, + "maxOutputTokens": 800, + "topP": 0.8, + "topK": 10 + } + }' 2> /dev/null | grep "text" + +configure_model_parameters.sh` + +### Java + +`Client client = new Client(); +GenerateContentConfig config = + GenerateContentConfig.builder() + .candidateCount(1) + .stopSequences(List.of("x")) + .maxOutputTokens(20) + .temperature(1.0F) + .build(); +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + "Tell me a story about a magic backpack.", + config); +System.out.println(response.text()); + +ConfigureModelParameters.java` + +### Safety Settings + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +unsafe_prompt = ( +"I support Martians Soccer Club and I think Jupiterians Football Club sucks! " +"Write a ironic phrase about them including expletives." +) +response = client.models.generate_content( +model="gemini-3.7-flash", +contents=unsafe_prompt, +config=types.GenerateContentConfig( +safety_settings=[ +types.SafetySetting( +category="HARM_CATEGORY_HATE_SPEECH", +threshold="BLOCK_MEDIUM_AND_ABOVE", +), +types.SafetySetting( +category="HARM_CATEGORY_HARASSMENT", threshold="BLOCK_ONLY_HIGH" +), +] +), +) +try: +print(response.text) +except Exception: +print("No information generated by the model.") +print(response.candidates[0].safety_ratings) + +safety_settings.py` + +### Node.js + + `// Make sure to include the following import: + // import {GoogleGenAI} from '@google/genai'; + const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + const unsafePrompt = + "I support Martians Soccer Club and I think Jupiterians Football Club sucks! Write a ironic phrase about them including expletives."; + const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: unsafePrompt, + config: { + safetySettings: [ + { + category: "HARM_CATEGORY_HATE_SPEECH", + threshold: "BLOCK_MEDIUM_AND_ABOVE", + }, + { + category: "HARM_CATEGORY_HARASSMENT", + threshold: "BLOCK_ONLY_HIGH", + }, + ], + }, + }); + try { + console.log("Generated text:", response.text); + } catch (error) { + console.log("No information generated by the model."); + } + console.log("Safety ratings:", response.candidates[0].safetyRatings); + return response; +} + +safety_settings.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +unsafePrompt := "I support Martians Soccer Club and I think Jupiterians Football Club sucks! " + + "Write a ironic phrase about them including expletives." +config := &genai.GenerateContentConfig{ + SafetySettings: []*genai.SafetySetting{ + { + Category: "HARM_CATEGORY_HATE_SPEECH", + Threshold: "BLOCK_MEDIUM_AND_ABOVE", + }, + { + Category: "HARM_CATEGORY_HARASSMENT", + Threshold: "BLOCK_ONLY_HIGH", + }, + }, +} +contents := []*genai.Content{ + genai.NewContentFromText(unsafePrompt, genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, config) +if err != nil { + log.Fatal(err) +} +// Print the generated text. +text := response.Text() +fmt.Println("Generated text:", text) +// Print the and safety ratings from the first candidate. +if len(response.Candidates) > 0 { + fmt.Println("Finish reason:", response.Candidates[0].FinishReason) + safetyRatings, err := json.MarshalIndent(response.Candidates[0].SafetyRatings, "", " ") + if err != nil { + return err + } + fmt.Println("Safety ratings:", string(safetyRatings)) +} else { + fmt.Println("No candidate returned.") +} + +safety_settings.go` + +### Shell + +`echo '{ + "safetySettings": [ + {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_ONLY_HIGH"}, + {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"} + ], + "contents": [{ + "parts":[{ + "text": "'I support Martians Soccer Club and I think Jupiterians Football Club sucks! Write a ironic phrase about them.'"}]}]}' > request.json +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d @request.json 2> /dev/null + +safety_settings.sh` + +### Java + +`Client client = new Client(); +String unsafePrompt = """ + I support Martians Soccer Club and I think Jupiterians Football Club sucks! + Write a ironic phrase about them including expletives. + """; +GenerateContentConfig config = + GenerateContentConfig.builder() + .safetySettings(Arrays.asList( + SafetySetting.builder() + .category("HARM_CATEGORY_HATE_SPEECH") + .threshold("BLOCK_MEDIUM_AND_ABOVE") + .build(), + SafetySetting.builder() + .category("HARM_CATEGORY_HARASSMENT") + .threshold("BLOCK_ONLY_HIGH") + .build() + )).build(); +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + unsafePrompt, + config); +try { + System.out.println(response.text()); +} catch (Exception e) { + System.out.println("No information generated by the model"); +} +System.out.println(response.candidates().get().getFirst().safetyRatings()); + +SafetySettings.java` + +### System Instruction + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +response = client.models.generate_content( +model="gemini-3.7-flash", +contents="Good morning! How are you?", +config=types.GenerateContentConfig( +system_instruction="You are a cat. Your name is Neko." +), +) +print(response.text) + +system_instruction.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const response = await ai.models.generateContent({ + model: "gemini-3.7-flash", + contents: "Good morning! How are you?", + config: { + systemInstruction: "You are a cat. Your name is Neko.", + }, +}); +console.log(response.text); + +system_instruction.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +// Construct the user message contents. +contents := []*genai.Content{ + genai.NewContentFromText("Good morning! How are you?", genai.RoleUser), +} +// Set the system instruction as a *genai.Content. +config := &genai.GenerateContentConfig{ + SystemInstruction: genai.NewContentFromText("You are a cat. Your name is Neko.", genai.RoleUser), +} +response, err := client.Models.GenerateContent(ctx, "gemini-3.7-flash", contents, config) +if err != nil { + log.Fatal(err) +} +printResponse(response) + +system_instruction.go` + +### Shell + +`curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ +-H 'Content-Type: application/json' \ +-d '{ "system_instruction": { + "parts": + { "text": "You are a cat. Your name is Neko."}}, + "contents": { + "parts": { + "text": "Hello there"}}}' + +system_instruction.sh` + +### Java + +`Client client = new Client(); +Part textPart = Part.builder().text("You are a cat. Your name is Neko.").build(); +Content content = Content.builder().role("system").parts(ImmutableList.of(textPart)).build(); +GenerateContentConfig config = GenerateContentConfig.builder() + .systemInstruction(content) + .build(); +GenerateContentResponse response = + client.models.generateContent( + "gemini-3.7-flash", + "Good morning! How are you?", + config); +System.out.println(response.text()); + +SystemInstruction.java` + +### Response body + +If successful, the response body contains an instance of `GenerateContentResponse`. + +`GenerateContentResponse` + +## Method: models.streamGenerateContent + +Generates a [streamed response](https://ai.google.dev/gemini-api/docs/text-generation?lang=python#generate-a-text-stream) from the model given an input `GenerateContentRequest`. + +`GenerateContentRequest` + +### Endpoint + +`https://generativelanguage.googleapis.com/v1beta/{model=models/*}:streamGenerateContent` + +### Path parameters + +`model` +`string` + +Required. The name of the `Model` to use for generating the completion. + +`Model` + +Format: `models/{model}`. It takes the form `models/{model}`. + +`models/{model}` +`models/{model}` + +### Request body + +The request body contains data with the following structure: + +`contents[]` +`object (Content)` +`Content` + +Required. The content of the current conversation with the model. + +For single-turn queries, this is a single instance. For multi-turn queries like [chat](https://ai.google.dev/gemini-api/docs/text-generation#chat), this is a repeated field that contains the conversation history and the latest request. + +`tools[]` +`object (Tool)` +`Tool` + +Optional. A list of `Tools` the `Model` may use to generate the next response. + +`Tools` +`Model` + +A `Tool` is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the `Model`. Supported `Tool`s are `Function` and `codeExecution`. Refer to the [Function calling](https://ai.google.dev/gemini-api/docs/function-calling) and the [Code execution](https://ai.google.dev/gemini-api/docs/code-execution) guides to learn more. + +`Tool` +`Model` +`Tool` +`Function` +`codeExecution` +`toolConfig` +`object (ToolConfig)` +`ToolConfig` + +Optional. Tool configuration for any `Tool` specified in the request. Refer to the [Function calling guide](https://ai.google.dev/gemini-api/docs/function-calling#function_calling_mode) for a usage example. + +`Tool` +`safetySettings[]` +`object (SafetySetting)` +`SafetySetting` + +Optional. A list of unique `SafetySetting` instances for blocking unsafe content. + +`SafetySetting` + +This will be enforced on the `GenerateContentRequest.contents` and `GenerateContentResponse.candidates`. There should not be more than one setting for each `SafetyCategory` type. The API will block any contents and responses that fail to meet the thresholds set by these settings. This list overrides the default settings for each `SafetyCategory` specified in the safetySettings. If there is no `SafetySetting` for a given `SafetyCategory` provided in the list, the API will use the default safety setting for that category. Harm categories HARM\_CATEGORY\_HATE\_SPEECH, HARM\_CATEGORY\_SEXUALLY\_EXPLICIT, HARM\_CATEGORY\_DANGEROUS\_CONTENT, HARM\_CATEGORY\_HARASSMENT, HARM\_CATEGORY\_CIVIC\_INTEGRITY, HARM\_CATEGORY\_JAILBREAK are supported. Refer to the [guide](https://ai.google.dev/gemini-api/docs/safety-settings) for detailed information on available safety settings. Also refer to the [Safety guidance](https://ai.google.dev/gemini-api/docs/safety-guidance) to learn how to incorporate safety considerations in your AI applications. + +`GenerateContentRequest.contents` +`GenerateContentResponse.candidates` +`SafetyCategory` +`SafetyCategory` +`SafetySetting` +`SafetyCategory` +`systemInstruction` +`object (Content)` +`Content` + +Optional. Developer set [system instruction(s)](https://ai.google.dev/gemini-api/docs/system-instructions). Currently, text only. + +`generationConfig` +`object (GenerationConfig)` +`GenerationConfig` + +Optional. Configuration options for model generation and outputs. + +`cachedContent` +`string` + +Optional. The name of the content [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to serve the prediction. Format: `cachedContents/{cachedContent}` + +`cachedContents/{cachedContent}` +`serviceTier` +`enum (ServiceTier)` +`ServiceTier` + +Optional. The service tier of the request. + +`store` +`boolean` + +Optional. Configures the logging behavior for a given request. If set, it takes precedence over the project-level logging config. + +### Example request + +### Text + +### Python + +`from google import genai +client = genai.Client() +response = client.models.generate_content_stream( +model="gemini-3.7-flash", contents="Write a story about a magic backpack." +) +for chunk in response: +print(chunk.text) +print("_" * 80) + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const response = await ai.models.generateContentStream({ + model: "gemini-3.7-flash", + contents: "Write a story about a magic backpack.", +}); +let text = ""; +for await (const chunk of response) { + console.log(chunk.text); + text += chunk.text; +} + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +contents := []*genai.Content{ + genai.NewContentFromText("Write a story about a magic backpack.", genai.RoleUser), +} +for response, err := range client.Models.GenerateContentStream( + ctx, + "gemini-3.7-flash", + contents, + nil, +) { + if err != nil { + log.Fatal(err) + } + fmt.Print(response.Candidates[0].Content.Parts[0].Text) +} + +text_generation.go` + +### Shell + +`curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=${GEMINI_API_KEY}" \ + -H 'Content-Type: application/json' \ + --no-buffer \ + -d '{ "contents":[{"parts":[{"text": "Write a story about a magic backpack."}]}]}' + +text_generation.sh` + +### Java + +`Client client = new Client(); +ResponseStream responseStream = + client.models.generateContentStream( + "gemini-3.7-flash", + "Write a story about a magic backpack.", + null); +StringBuilder response = new StringBuilder(); +for (GenerateContentResponse res : responseStream) { + System.out.print(res.text()); + response.append(res.text()); +} +responseStream.close(); + +TextGeneration.java` + +### Image + +### Python + +`from google import genai +import PIL.Image +client = genai.Client() +organ = PIL.Image.open(media / "organ.jpg") +response = client.models.generate_content_stream( +model="gemini-3.7-flash", contents=["Tell me about this instrument", organ] +) +for chunk in response: +print(chunk.text) +print("_" * 80) + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const organ = await ai.files.upload({ + file: path.join(media, "organ.jpg"), +}); +const response = await ai.models.generateContentStream({ + model: "gemini-3.7-flash", + contents: [ + createUserContent([ + "Tell me about this instrument", + createPartFromUri(organ.uri, organ.mimeType) + ]), + ], +}); +let text = ""; +for await (const chunk of response) { + console.log(chunk.text); + text += chunk.text; +} + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "organ.jpg"), + &genai.UploadFileConfig{ + MIMEType : "image/jpeg", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromText("Tell me about this instrument"), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +for response, err := range client.Models.GenerateContentStream( + ctx, + "gemini-3.7-flash", + contents, + nil, +) { + if err != nil { + log.Fatal(err) + } + fmt.Print(response.Candidates[0].Content.Parts[0].Text) +} + +text_generation.go` + +### Shell + +`cat > "$TEMP_JSON" << EOF +{ + "contents": [{ + "parts":[ + {"text": "Tell me about this instrument"}, + { + "inline_data": { + "mime_type":"image/jpeg", + "data": "$(cat "$TEMP_B64")" + } + } + ] + }] +} +EOF +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d "@$TEMP_JSON" 2> /dev/null + +text_generation.sh` + +### Java + +`Client client = new Client(); +String path = media_path + "organ.jpg"; +byte[] imageData = Files.readAllBytes(Paths.get(path)); +Content content = + Content.fromParts( + Part.fromText("Tell me about this instrument."), + Part.fromBytes(imageData, "image/jpeg")); +ResponseStream responseStream = + client.models.generateContentStream( + "gemini-3.7-flash", + content, + null); +StringBuilder response = new StringBuilder(); +for (GenerateContentResponse res : responseStream) { + System.out.print(res.text()); + response.append(res.text()); +} +responseStream.close(); + +TextGeneration.java` + +### Audio + +### Python + +`from google import genai +client = genai.Client() +sample_audio = client.files.upload(file=media / "sample.mp3") +response = client.models.generate_content_stream( +model="gemini-3.7-flash", +contents=["Give me a summary of this audio file.", sample_audio], +) +for chunk in response: +print(chunk.text) +print("_" * 80) + +text_generation.py` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "sample.mp3"), + &genai.UploadFileConfig{ + MIMEType : "audio/mpeg", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromText("Give me a summary of this audio file."), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +for result, err := range client.Models.GenerateContentStream( + ctx, + "gemini-3.7-flash", + contents, + nil, +) { + if err != nil { + log.Fatal(err) + } + fmt.Print(result.Candidates[0].Content.Parts[0].Text) +} + +text_generation.go` + +### Shell + +`# Use File API to upload audio data to API request. +MIME_TYPE=$(file -b --mime-type "${AUDIO_PATH}") +NUM_BYTES=$(wc -c < "${AUDIO_PATH}") +DISPLAY_NAME=AUDIO +tmp_header_file=upload-header.tmp +# Initial resumable request defining metadata. +# The upload url is in the response headers dump them to a file. +curl "${BASE_URL}/upload/v1beta/files?key=${GEMINI_API_KEY}" \ + -D upload-header.tmp \ + -H "X-Goog-Upload-Protocol: resumable" \ + -H "X-Goog-Upload-Command: start" \ + -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ + -H "Content-Type: application/json" \ + -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null +upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") +rm "${tmp_header_file}" +# Upload the actual bytes. +curl "${upload_url}" \ + -H "Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Offset: 0" \ + -H "X-Goog-Upload-Command: upload, finalize" \ + --data-binary "@${AUDIO_PATH}" 2> /dev/null > file_info.json +file_uri=$(jq ".file.uri" file_info.json) +echo file_uri=$file_uri +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Please describe this file."}, + {"file_data":{"mime_type": "audio/mpeg", "file_uri": '$file_uri'}}] + }] + }' 2> /dev/null > response.json +cat response.json +echo + +text_generation.sh` + +### Video + +### Python + +`from google import genai +import time +client = genai.Client() +# Video clip (CC BY 3.0) from https://peach.blender.org/download/ +myfile = client.files.upload(file=media / "Big_Buck_Bunny.mp4") +print(f"{myfile=}") +# Poll until the video file is completely processed (state becomes ACTIVE). +while not myfile.state or myfile.state.name != "ACTIVE": +print("Processing video...") +print("File state:", myfile.state) +time.sleep(5) +myfile = client.files.get(name=myfile.name) +response = client.models.generate_content_stream( +model="gemini-3.7-flash", contents=[myfile, "Describe this video clip"] +) +for chunk in response: +print(chunk.text) +print("_" * 80) + +text_generation.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +let video = await ai.files.upload({ + file: path.join(media, 'Big_Buck_Bunny.mp4'), +}); +// Poll until the video file is completely processed (state becomes ACTIVE). +while (!video.state || video.state.toString() !== 'ACTIVE') { + console.log('Processing video...'); + console.log('File state: ', video.state); + await sleep(5000); + video = await ai.files.get({name: video.name}); +} +const response = await ai.models.generateContentStream({ + model: "gemini-3.7-flash", + contents: [ + createUserContent([ + "Describe this video clip", + createPartFromUri(video.uri, video.mimeType), + ]), + ], +}); +let text = ""; +for await (const chunk of response) { + console.log(chunk.text); + text += chunk.text; +} + +text_generation.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "Big_Buck_Bunny.mp4"), + &genai.UploadFileConfig{ + MIMEType : "video/mp4", + }, +) +if err != nil { + log.Fatal(err) +} +// Poll until the video file is completely processed (state becomes ACTIVE). +for file.State == genai.FileStateUnspecified || file.State != genai.FileStateActive { + fmt.Println("Processing video...") + fmt.Println("File state:", file.State) + time.Sleep(5 * time.Second) + file, err = client.Files.Get(ctx, file.Name, nil) + if err != nil { + log.Fatal(err) + } +} +parts := []*genai.Part{ + genai.NewPartFromText("Describe this video clip"), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +for result, err := range client.Models.GenerateContentStream( + ctx, + "gemini-3.7-flash", + contents, + nil, +) { + if err != nil { + log.Fatal(err) + } + fmt.Print(result.Candidates[0].Content.Parts[0].Text) +} + +text_generation.go` + +### Shell + +`# Use File API to upload audio data to API request. +MIME_TYPE=$(file -b --mime-type "${VIDEO_PATH}") +NUM_BYTES=$(wc -c < "${VIDEO_PATH}") +DISPLAY_NAME=VIDEO_PATH +# Initial resumable request defining metadata. +# The upload url is in the response headers dump them to a file. +curl "${BASE_URL}/upload/v1beta/files?key=${GEMINI_API_KEY}" \ + -D upload-header.tmp \ + -H "X-Goog-Upload-Protocol: resumable" \ + -H "X-Goog-Upload-Command: start" \ + -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ + -H "Content-Type: application/json" \ + -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null +upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") +rm "${tmp_header_file}" +# Upload the actual bytes. +curl "${upload_url}" \ + -H "Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Offset: 0" \ + -H "X-Goog-Upload-Command: upload, finalize" \ + --data-binary "@${VIDEO_PATH}" 2> /dev/null > file_info.json +file_uri=$(jq ".file.uri" file_info.json) +echo file_uri=$file_uri +state=$(jq ".file.state" file_info.json) +echo state=$state +while [[ "($state)" = *"PROCESSING"* ]]; +do + echo "Processing video..." + sleep 5 + # Get the file of interest to check state + curl https://generativelanguage.googleapis.com/v1beta/files/$name > file_info.json + state=$(jq ".file.state" file_info.json) +done +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Please describe this file."}, + {"file_data":{"mime_type": "video/mp4", "file_uri": '$file_uri'}}] + }] + }' 2> /dev/null > response.json +cat response.json +echo + +text_generation.sh` + +### PDF + +### Python + +`from google import genai +client = genai.Client() +sample_pdf = client.files.upload(file=media / "test.pdf") +response = client.models.generate_content_stream( +model="gemini-3.7-flash", +contents=["Give me a summary of this document:", sample_pdf], +) +for chunk in response: +print(chunk.text) +print("_" * 80) + +text_generation.py` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +file, err := client.Files.UploadFromPath( + ctx, + filepath.Join(getMedia(), "test.pdf"), + &genai.UploadFileConfig{ + MIMEType : "application/pdf", + }, +) +if err != nil { + log.Fatal(err) +} +parts := []*genai.Part{ + genai.NewPartFromText("Give me a summary of this document:"), + genai.NewPartFromURI(file.URI, file.MIMEType), +} +contents := []*genai.Content{ + genai.NewContentFromParts(parts, genai.RoleUser), +} +for result, err := range client.Models.GenerateContentStream( + ctx, + "gemini-3.7-flash", + contents, + nil, +) { + if err != nil { + log.Fatal(err) + } + fmt.Print(result.Candidates[0].Content.Parts[0].Text) +} + +text_generation.go` + +### Shell + +`MIME_TYPE=$(file -b --mime-type "${PDF_PATH}") +NUM_BYTES=$(wc -c < "${PDF_PATH}") +DISPLAY_NAME=TEXT +echo $MIME_TYPE +tmp_header_file=upload-header.tmp +# Initial resumable request defining metadata. +# The upload url is in the response headers dump them to a file. +curl "${BASE_URL}/upload/v1beta/files?key=${GEMINI_API_KEY}" \ + -D upload-header.tmp \ + -H "X-Goog-Upload-Protocol: resumable" \ + -H "X-Goog-Upload-Command: start" \ + -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ + -H "Content-Type: application/json" \ + -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null +upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") +rm "${tmp_header_file}" +# Upload the actual bytes. +curl "${upload_url}" \ + -H "Content-Length: ${NUM_BYTES}" \ + -H "X-Goog-Upload-Offset: 0" \ + -H "X-Goog-Upload-Command: upload, finalize" \ + --data-binary "@${PDF_PATH}" 2> /dev/null > file_info.json +file_uri=$(jq ".file.uri" file_info.json) +echo file_uri=$file_uri +# Now generate content using that file +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [{ + "parts":[ + {"text": "Can you add a few more lines to this poem?"}, + {"file_data":{"mime_type": "application/pdf", "file_uri": '$file_uri'}}] + }] + }' 2> /dev/null > response.json +cat response.json +echo + +text_generation.sh` + +### Chat + +### Python + +`from google import genai +from google.genai import types +client = genai.Client() +chat = client.chats.create( +model="gemini-3.7-flash", +history=[ +types.Content(role="user", parts=[types.Part(text="Hello")]), +types.Content( +role="model", +parts=[ +types.Part( +text="Great to meet you. What would you like to know?" +) +], +), +], +) +response = chat.send_message_stream(message="I have 2 dogs in my house.") +for chunk in response: +print(chunk.text) +print("_" * 80) +response = chat.send_message_stream(message="How many paws are in my house?") +for chunk in response: +print(chunk.text) +print("_" * 80) +print(chat.get_history()) + +chat.py` + +### Node.js + +`// Make sure to include the following import: +// import {GoogleGenAI} from '@google/genai'; +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +const chat = ai.chats.create({ + model: "gemini-3.7-flash", + history: [ + { + role: "user", + parts: [{ text: "Hello" }], + }, + { + role: "model", + parts: [{ text: "Great to meet you. What would you like to know?" }], + }, + ], +}); +console.log("Streaming response for first message:"); +const stream1 = await chat.sendMessageStream({ + message: "I have 2 dogs in my house.", +}); +for await (const chunk of stream1) { + console.log(chunk.text); + console.log("_".repeat(80)); +} +console.log("Streaming response for second message:"); +const stream2 = await chat.sendMessageStream({ + message: "How many paws are in my house?", +}); +for await (const chunk of stream2) { + console.log(chunk.text); + console.log("_".repeat(80)); +} +console.log(chat.getHistory()); + +chat.js` + +### Go + +`ctx := context.Background() +client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: os.Getenv("GEMINI_API_KEY"), + Backend: genai.BackendGeminiAPI, +}) +if err != nil { + log.Fatal(err) +} +history := []*genai.Content{ + genai.NewContentFromText("Hello", genai.RoleUser), + genai.NewContentFromText("Great to meet you. What would you like to know?", genai.RoleModel), +} +chat, err := client.Chats.Create(ctx, "gemini-3.7-flash", nil, history) +if err != nil { + log.Fatal(err) +} +for chunk, err := range chat.SendMessageStream(ctx, genai.Part{Text: "I have 2 dogs in my house."}) { + if err != nil { + log.Fatal(err) + } + fmt.Println(chunk.Text()) + fmt.Println(strings.Repeat("_", 64)) +} +for chunk, err := range chat.SendMessageStream(ctx, genai.Part{Text: "How many paws are in my house?"}) { + if err != nil { + log.Fatal(err) + } + fmt.Println(chunk.Text()) + fmt.Println(strings.Repeat("_", 64)) +} +fmt.Println(chat.History(false)) + +chat.go` + +### Shell + +`curl https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=$GEMINI_API_KEY \ + -H 'Content-Type: application/json' \ + -X POST \ + -d '{ + "contents": [ + {"role":"user", + "parts":[{ + "text": "Hello"}]}, + {"role": "model", + "parts":[{ + "text": "Great to meet you. What would you like to know?"}]}, + {"role":"user", + "parts":[{ + "text": "I have two dogs in my house. How many paws are in my house?"}]}, + ] + }' 2> /dev/null | grep "text" + +chat.sh` + +### Response body + +If successful, the response body contains a stream of `GenerateContentResponse` instances. + +`GenerateContentResponse` + +## GenerateContentResponse + +Response from the model supporting multiple candidate responses. + +Safety ratings and content filtering are reported for both prompt in `GenerateContentResponse.prompt_feedback` and for each candidate in `finishReason` and in `safetyRatings`. The API: - Returns either all requested candidates or none of them - Returns no candidates at all only if there was something wrong with the prompt (check `promptFeedback`) - Reports feedback on each candidate in `finishReason` and `safetyRatings`. + +`GenerateContentResponse.prompt_feedback` +`finishReason` +`safetyRatings` +`promptFeedback` +`finishReason` +`safetyRatings` +`candidates[]` +`object (Candidate)` +`Candidate` + +Candidate responses from the model. + +`promptFeedback` +`object (PromptFeedback)` +`PromptFeedback` + +Returns the prompt's feedback related to the content filters. + +`usageMetadata` +`object (UsageMetadata)` +`UsageMetadata` + +Output only. Metadata on the generation requests' token usage. + +`modelVersion` +`string` + +Output only. The model version used to generate the response. + +`responseId` +`string` + +Output only. responseId is used to identify each response. + +`modelStatus` +`object (ModelStatus)` +`ModelStatus` + +Output only. The current model status of this model. + +| JSON representation | +| --- | +| ``` { "candidates": [ { object (Candidate) } ], "promptFeedback": { object (PromptFeedback) }, "usageMetadata": { object (UsageMetadata) }, "modelVersion": string, "responseId": string, "modelStatus": { object (ModelStatus) } } ``` | + +`Candidate` +`PromptFeedback` +`UsageMetadata` +`ModelStatus` + +## PromptFeedback + +A set of the feedback metadata the prompt specified in `GenerateContentRequest.content`. + +`GenerateContentRequest.content` +`blockReason` +`enum (BlockReason)` +`BlockReason` + +Optional. If set, the prompt was blocked and no candidates are returned. Rephrase the prompt. + +`safetyRatings[]` +`object (SafetyRating)` +`SafetyRating` + +Ratings for safety of the prompt. There is at most one rating per category. + +| JSON representation | +| --- | +| ``` { "blockReason": enum (BlockReason), "safetyRatings": [ { object (SafetyRating) } ] } ``` | + +`BlockReason` +`SafetyRating` + +## BlockReason + +Specifies the reason why the prompt was blocked. + +| Enums | | +| --- | --- | +| `BLOCK_REASON_UNSPECIFIED` | Default value. This value is unused. | +| `SAFETY` | Prompt was blocked due to safety reasons. Inspect `safetyRatings` to understand which safety category blocked it. | +| `OTHER` | Prompt was blocked due to unknown reasons. | +| `BLOCKLIST` | Prompt was blocked due to the terms which are included from the terminology blocklist. | +| `PROHIBITED_CONTENT` | Prompt was blocked due to prohibited content. | +| `IMAGE_SAFETY` | Candidates blocked due to unsafe image generation content. | + +`BLOCK_REASON_UNSPECIFIED` +`SAFETY` +`safetyRatings` +`OTHER` +`BLOCKLIST` +`PROHIBITED_CONTENT` +`IMAGE_SAFETY` + +## UsageMetadata + +Metadata on the generation request's token usage. + +`promptTokenCount` +`integer` + +Number of tokens in the prompt. When `cachedContent` is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content. + +`cachedContent` +`cachedContentTokenCount` +`integer` + +Number of tokens in the cached part of the prompt (the cached content) + +`candidatesTokenCount` +`integer` + +Total number of tokens across all the generated response candidates. + +`toolUsePromptTokenCount` +`integer` + +Output only. Number of tokens present in tool-use prompt(s). + +`thoughtsTokenCount` +`integer` + +Output only. Number of tokens of thoughts for thinking models. + +`totalTokenCount` +`integer` + +Total token count for the generation request (prompt + thoughts + response candidates). + +`promptTokensDetails[]` +`object (ModalityTokenCount)` +`ModalityTokenCount` + +Output only. List of modalities that were processed in the request input. + +`cacheTokensDetails[]` +`object (ModalityTokenCount)` +`ModalityTokenCount` + +Output only. List of modalities of the cached content in the request input. + +`candidatesTokensDetails[]` +`object (ModalityTokenCount)` +`ModalityTokenCount` + +Output only. List of modalities that were returned in the response. + +`toolUsePromptTokensDetails[]` +`object (ModalityTokenCount)` +`ModalityTokenCount` + +Output only. List of modalities that were processed for tool-use request inputs. + +`serviceTier` +`enum (ServiceTier)` +`ServiceTier` + +Output only. Service tier of the request. + +| JSON representation | +| --- | +| ``` { "promptTokenCount": integer, "cachedContentTokenCount": integer, "candidatesTokenCount": integer, "toolUsePromptTokenCount": integer, "thoughtsTokenCount": integer, "totalTokenCount": integer, "promptTokensDetails": [ { object (ModalityTokenCount) } ], "cacheTokensDetails": [ { object (ModalityTokenCount) } ], "candidatesTokensDetails": [ { object (ModalityTokenCount) } ], "toolUsePromptTokensDetails": [ { object (ModalityTokenCount) } ], "serviceTier": enum (ServiceTier) } ``` | + +`ModalityTokenCount` +`ModalityTokenCount` +`ModalityTokenCount` +`ModalityTokenCount` +`ServiceTier` + +## ModelStatus + +The status of the underlying model. This is used to indicate the stage of the underlying model and the retirement time if applicable. + +`modelStage` +`enum (ModelStage)` +`ModelStage` + +The stage of the underlying model. + +`retirementTime` +`string (Timestamp format)` +`Timestamp` + +The time at which the model will be retired. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` +`message` +`string` + +A message explaining the model status. + +| JSON representation | +| --- | +| ``` { "modelStage": enum (ModelStage), "retirementTime": string, "message": string } ``` | + +`ModelStage` + +## ModelStage + +Defines the stage of the underlying model. + +| Enums | | +| --- | --- | +| `MODEL_STAGE_UNSPECIFIED` | Unspecified model stage. | +| `UNSTABLE_EXPERIMENTAL` | The underlying model is subject to lots of tunings. This item is deprecated! | +| `EXPERIMENTAL` | Models in this stage are for experimental purposes only. | +| `PREVIEW` | Models in this stage are more mature than experimental models. | +| `STABLE` | Models in this stage are considered stable and ready for production use. | +| `LEGACY` | If the model is on this stage, it means that this model is on the path to deprecation in near future. Only existing customers can use this model. | +| `DEPRECATED` | Models in this stage are deprecated. These models cannot be used. This item is deprecated! | +| `RETIRED` | Models in this stage are retired. These models cannot be used. | + +`MODEL_STAGE_UNSPECIFIED` +`UNSTABLE_EXPERIMENTAL` + +The underlying model is subject to lots of tunings. + +This item is deprecated! + +`EXPERIMENTAL` +`PREVIEW` +`STABLE` +`LEGACY` +`DEPRECATED` + +Models in this stage are deprecated. These models cannot be used. + +This item is deprecated! + +`RETIRED` + +## Candidate + +A response candidate generated from the model. + +`content` +`object (Content)` +`Content` + +Output only. Generated content returned from the model. + +`finishReason` +`enum (FinishReason)` +`FinishReason` + +Optional. Output only. The reason why the model stopped generating tokens. + +If empty, the model has not stopped generating tokens. + +`safetyRatings[]` +`object (SafetyRating)` +`SafetyRating` + +List of ratings for the safety of a response candidate. + +There is at most one rating per category. + +`citationMetadata` +`object (CitationMetadata)` +`CitationMetadata` + +Output only. Citation information for model-generated candidate. + +This field may be populated with recitation information for any text included in the `content`. These are passages that are "recited" from copyrighted material in the foundational LLM's training data. + +`content` +`tokenCount` +`integer` + +Output only. Token count for this candidate. + +`groundingAttributions[]` +`object (GroundingAttribution)` +`GroundingAttribution` + +Output only. Attribution information for sources that contributed to a grounded answer. + +This field is populated for `GenerateAnswer` calls. + +`GenerateAnswer` +`groundingMetadata` +`object (GroundingMetadata)` +`GroundingMetadata` + +Output only. Grounding metadata for the candidate. + +This field is populated for `GenerateContent` calls. + +`GenerateContent` +`avgLogprobs` +`number` + +Output only. Average log probability score of the candidate. + +`logprobsResult` +`object (LogprobsResult)` +`LogprobsResult` + +Output only. Log-likelihood scores for the response tokens and top tokens + +`urlContextMetadata` +`object (UrlContextMetadata)` +`UrlContextMetadata` + +Output only. Metadata related to url context retrieval tool. + +`index` +`integer` + +Output only. Index of the candidate in the list of response candidates. + +`finishMessage` +`string` + +Optional. Output only. Details the reason why the model stopped generating tokens. This is populated only when `finishReason` is set. + +`finishReason` + +| JSON representation | +| --- | +| ``` { "content": { object (Content) }, "finishReason": enum (FinishReason), "safetyRatings": [ { object (SafetyRating) } ], "citationMetadata": { object (CitationMetadata) }, "tokenCount": integer, "groundingAttributions": [ { object (GroundingAttribution) } ], "groundingMetadata": { object (GroundingMetadata) }, "avgLogprobs": number, "logprobsResult": { object (LogprobsResult) }, "urlContextMetadata": { object (UrlContextMetadata) }, "index": integer, "finishMessage": string } ``` | + +`Content` +`FinishReason` +`SafetyRating` +`CitationMetadata` +`GroundingAttribution` +`GroundingMetadata` +`LogprobsResult` +`UrlContextMetadata` + +## FinishReason + +Defines the reason why the model stopped generating tokens. + +| Enums | | +| --- | --- | +| `FINISH_REASON_UNSPECIFIED` | Default value. This value is unused. | +| `STOP` | Natural stop point of the model or provided stop sequence. | +| `MAX_TOKENS` | The maximum number of tokens as specified in the request was reached. | +| `SAFETY` | The response candidate content was flagged for safety reasons. | +| `RECITATION` | The response candidate content was flagged for recitation reasons. | +| `LANGUAGE` | The response candidate content was flagged for using an unsupported language. | +| `OTHER` | Unknown reason. | +| `BLOCKLIST` | Token generation stopped because the content contains forbidden terms. | +| `PROHIBITED_CONTENT` | Token generation stopped for potentially containing prohibited content. | +| `SPII` | Token generation stopped because the content potentially contains Sensitive Personally Identifiable Information (SPII). | +| `MALFORMED_FUNCTION_CALL` | The function call generated by the model is invalid. | +| `IMAGE_SAFETY` | Token generation stopped because generated images contain safety violations. | +| `IMAGE_PROHIBITED_CONTENT` | Image generation stopped because generated images has other prohibited content. | +| `IMAGE_OTHER` | Image generation stopped because of other miscellaneous issue. | +| `NO_IMAGE` | The model was expected to generate an image, but none was generated. | +| `IMAGE_RECITATION` | Image generation stopped due to recitation. | +| `UNEXPECTED_TOOL_CALL` | Model generated a tool call but no tools were enabled in the request. | +| `TOO_MANY_TOOL_CALLS` | Model called too many tools consecutively, thus the system exited execution. | +| `MISSING_THOUGHT_SIGNATURE` | Request has at least one thought signature missing. | +| `MALFORMED_RESPONSE` | Finished due to malformed response. | +| `ESCALATION` | Request was filtered by an escalation rule. | + +`FINISH_REASON_UNSPECIFIED` +`STOP` +`MAX_TOKENS` +`SAFETY` +`RECITATION` +`LANGUAGE` +`OTHER` +`BLOCKLIST` +`PROHIBITED_CONTENT` +`SPII` +`MALFORMED_FUNCTION_CALL` +`IMAGE_SAFETY` +`IMAGE_PROHIBITED_CONTENT` +`IMAGE_OTHER` +`NO_IMAGE` +`IMAGE_RECITATION` +`UNEXPECTED_TOOL_CALL` +`TOO_MANY_TOOL_CALLS` +`MISSING_THOUGHT_SIGNATURE` +`MALFORMED_RESPONSE` +`ESCALATION` + +## GroundingAttribution + +Attribution for a source that contributed to an answer. + +`sourceId` +`object (AttributionSourceId)` +`AttributionSourceId` + +Output only. Identifier for the source contributing to this attribution. + +`content` +`object (Content)` +`Content` + +Grounding source content that makes up this attribution. + +| JSON representation | +| --- | +| ``` { "sourceId": { object (AttributionSourceId) }, "content": { object (Content) } } ``` | + +`AttributionSourceId` +`Content` + +## AttributionSourceId + +Identifier for the source contributing to this attribution. + +`source` +`Union type` +`source` +`groundingPassage` +`object (GroundingPassageId)` +`GroundingPassageId` + +Identifier for an inline passage. + +`semanticRetrieverChunk` +`object (SemanticRetrieverChunk)` +`SemanticRetrieverChunk` + +Identifier for a `Chunk` fetched via Semantic Retriever. + +`Chunk` + +| JSON representation | +| --- | +| ``` { // source "groundingPassage": { object (GroundingPassageId) }, "semanticRetrieverChunk": { object (SemanticRetrieverChunk) } // Union type } ``` | + +`GroundingPassageId` +`SemanticRetrieverChunk` + +## GroundingPassageId + +Identifier for a part within a `GroundingPassage`. + +`GroundingPassage` +`passageId` +`string` + +Output only. ID of the passage matching the `GenerateAnswerRequest`'s `GroundingPassage.id`. + +`GenerateAnswerRequest` +`GroundingPassage.id` +`partIndex` +`integer` + +Output only. Index of the part within the `GenerateAnswerRequest`'s `GroundingPassage.content`. + +`GenerateAnswerRequest` +`GroundingPassage.content` + +| JSON representation | +| --- | +| ``` { "passageId": string, "partIndex": integer } ``` | + +## SemanticRetrieverChunk + +Identifier for a `Chunk` retrieved via Semantic Retriever specified in the `GenerateAnswerRequest` using `SemanticRetrieverConfig`. + +`Chunk` +`GenerateAnswerRequest` +`SemanticRetrieverConfig` +`source` +`string` + +Output only. Name of the source matching the request's `SemanticRetrieverConfig.source`. Example: `corpora/123` or `corpora/123/documents/abc` + +`SemanticRetrieverConfig.source` +`corpora/123` +`corpora/123/documents/abc` +`chunk` +`string` + +Output only. Name of the `Chunk` containing the attributed text. Example: `corpora/123/documents/abc/chunks/xyz` + +`Chunk` +`corpora/123/documents/abc/chunks/xyz` + +| JSON representation | +| --- | +| ``` { "source": string, "chunk": string } ``` | + +## GroundingMetadata + +Metadata returned to client when grounding is enabled. + +`groundingChunks[]` +`object (GroundingChunk)` +`GroundingChunk` + +List of supporting references retrieved from specified grounding source. When streaming, this only contains the grounding chunks that have not been included in the grounding metadata of previous responses. + +`groundingSupports[]` +`object (GroundingSupport)` +`GroundingSupport` + +List of grounding support. + +`webSearchQueries[]` +`string` + +Web search queries for the following-up web search. + +`imageSearchQueries[]` +`string` + +Image search queries used for grounding. + +`searchEntryPoint` +`object (SearchEntryPoint)` +`SearchEntryPoint` + +Optional. Google search entry for the following-up web searches. + +`retrievalMetadata` +`object (RetrievalMetadata)` +`RetrievalMetadata` + +Metadata related to retrieval in the grounding flow. + +`googleMapsWidgetContextToken` +`string` + +Optional. Resource name of the Google Maps widget context token that can be used with the PlacesContextElement widget in order to render contextual data. Only populated in the case that grounding with Google Maps is enabled. + +| JSON representation | +| --- | +| ``` { "groundingChunks": [ { object (GroundingChunk) } ], "groundingSupports": [ { object (GroundingSupport) } ], "webSearchQueries": [ string ], "imageSearchQueries": [ string ], "searchEntryPoint": { object (SearchEntryPoint) }, "retrievalMetadata": { object (RetrievalMetadata) }, "googleMapsWidgetContextToken": string } ``` | + +`GroundingChunk` +`GroundingSupport` +`SearchEntryPoint` +`RetrievalMetadata` + +## SearchEntryPoint + +Google search entry point. + +`renderedContent` +`string` + +Optional. Web content snippet that can be embedded in a web page or an app webview. + +`sdkBlob` +`string (bytes format)` + +Optional. Base64 encoded JSON representing array of tuple. + +A base64-encoded string. + +| JSON representation | +| --- | +| ``` { "renderedContent": string, "sdkBlob": string } ``` | + +## GroundingChunk + +A `GroundingChunk` represents a segment of supporting evidence that grounds the model's response. It can be a chunk from the web, a retrieved context from a file, or information from Google Maps. + +`GroundingChunk` +`chunk_type` +`Union type` +`chunk_type` +`web` +`object (Web)` +`Web` + +Grounding chunk from the web. + +`image` +`object (Image)` +`Image` + +Optional. Grounding chunk from image search. + +`retrievedContext` +`object (RetrievedContext)` +`RetrievedContext` + +Optional. Grounding chunk from context retrieved by the file search tool. + +`maps` +`object (Maps)` +`Maps` + +Optional. Grounding chunk from Google Maps. + +| JSON representation | +| --- | +| ``` { // chunk_type "web": { object (Web) }, "image": { object (Image) }, "retrievedContext": { object (RetrievedContext) }, "maps": { object (Maps) } // Union type } ``` | + +`Web` +`Image` +`RetrievedContext` +`Maps` + +## Web + +Chunk from the web. + +`uri` +`string` + +Output only. URI reference of the chunk. + +`title` +`string` + +Output only. Title of the chunk. + +| JSON representation | +| --- | +| ``` { "uri": string, "title": string } ``` | + +## Image + +Chunk from image search. + +`sourceUri` +`string` + +The web page URI for attribution. + +`imageUri` +`string` + +The image asset URL. + +`title` +`string` + +The title of the web page that the image is from. + +`domain` +`string` + +The root domain of the web page that the image is from, e.g. "example.com". + +| JSON representation | +| --- | +| ``` { "sourceUri": string, "imageUri": string, "title": string, "domain": string } ``` | + +## RetrievedContext + +Chunk from context retrieved by the file search tool. + +`customMetadata[]` +`object (CustomMetadata)` +`CustomMetadata` + +Optional. User-provided metadata about the retrieved context. + +`uri` +`string` + +Optional. URI reference of the semantic retrieval document. + +`title` +`string` + +Optional. Title of the document. + +`text` +`string` + +Optional. Text of the chunk. + +`fileSearchStore` +`string` + +Optional. Name of the `FileSearchStore` containing the document. Example: `fileSearchStores/123` + +`FileSearchStore` +`fileSearchStores/123` +`pageNumber` +`integer` + +Optional. Page number of the retrieved context, if applicable. + +`mediaId` +`string` + +Optional. The media blob resource name for multimodal file search results. Format: fileSearchStores/{file\_search\_store\_id}/media/{blobId} + +| JSON representation | +| --- | +| ``` { "customMetadata": [ { object (CustomMetadata) } ], "uri": string, "title": string, "text": string, "fileSearchStore": string, "pageNumber": integer, "mediaId": string } ``` | + +`CustomMetadata` + +## CustomMetadata + +User provided metadata about the GroundingFact. + +`key` +`string` + +The key of the metadata. + +`value` +`Union type` +`value` +`stringValue` +`string` + +Optional. The string value of the metadata. + +`stringListValue` +`object (StringList)` +`StringList` + +Optional. A list of string values for the metadata. + +`numericValue` +`number` + +Optional. The numeric value of the metadata. The expected range for this value depends on the specific `key` used. + +`key` + +| JSON representation | +| --- | +| ``` { "key": string, // value "stringValue": string, "stringListValue": { object (StringList) }, "numericValue": number // Union type } ``` | + +`StringList` + +## StringList + +A list of string values. + +`values[]` +`string` + +The string values of the list. + +| JSON representation | +| --- | +| ``` { "values": [ string ] } ``` | + +## Maps + +A grounding chunk from Google Maps. A Maps chunk corresponds to a single place. + +`uri` +`string` + +URI reference of the place. + +`title` +`string` + +Title of the place. + +`text` +`string` + +Text description of the place answer. + +`placeId` +`string` + +The ID of the place, in `places/{placeId}` format. A user can use this ID to look up that place. + +`places/{placeId}` +`placeAnswerSources` +`object (PlaceAnswerSources)` +`PlaceAnswerSources` + +Sources that provide answers about the features of a given place in Google Maps. + +| JSON representation | +| --- | +| ``` { "uri": string, "title": string, "text": string, "placeId": string, "placeAnswerSources": { object (PlaceAnswerSources) } } ``` | + +`PlaceAnswerSources` + +## PlaceAnswerSources + +Collection of sources that provide answers about the features of a given place in Google Maps. Each PlaceAnswerSources message corresponds to a specific place in Google Maps. The Google Maps tool used these sources in order to answer questions about features of the place (e.g: "does Bar Foo have Wifi" or "is Foo Bar wheelchair accessible?"). Currently we only support review snippets as sources. + +`reviewSnippets[]` +`object (ReviewSnippet)` +`ReviewSnippet` + +Snippets of reviews that are used to generate answers about the features of a given place in Google Maps. + +| JSON representation | +| --- | +| ``` { "reviewSnippets": [ { object (ReviewSnippet) } ] } ``` | + +`ReviewSnippet` + +## ReviewSnippet + +Encapsulates a snippet of a user review that answers a question about the features of a specific place in Google Maps. + +`reviewId` +`string` + +The ID of the review snippet. + +`googleMapsUri` +`string` + +A link that corresponds to the user review on Google Maps. + +`title` +`string` + +Title of the review. + +| JSON representation | +| --- | +| ``` { "reviewId": string, "googleMapsUri": string, "title": string } ``` | + +## GroundingSupport + +Grounding support. + +`groundingChunkIndices[]` +`integer` + +Optional. A list of indices (into 'grounding\_chunk' in `response.candidate.grounding_metadata`) specifying the citations associated with the claim. For instance [1,3,4] means that grounding\_chunk[1], grounding\_chunk[3], grounding\_chunk[4] are the retrieved content attributed to the claim. If the response is streaming, the groundingChunkIndices refer to the indices across all responses. It is the client's responsibility to accumulate the grounding chunks from all responses (while maintaining the same order). + +`response.candidate.grounding_metadata` +`confidenceScores[]` +`number` + +Optional. Confidence score of the support references. Ranges from 0 to 1. 1 is the most confident. This list must have the same size as the groundingChunkIndices. + +`renderedParts[]` +`integer` + +Output only. Indices into the `parts` field of the candidate's content. These indices specify which rendered parts are associated with this support source. + +`parts` +`segment` +`object (Segment)` +`Segment` + +Segment of the content this support belongs to. + +| JSON representation | +| --- | +| ``` { "groundingChunkIndices": [ integer ], "confidenceScores": [ number ], "renderedParts": [ integer ], "segment": { object (Segment) } } ``` | + +`Segment` + +## Segment + +Segment of the content. + +`partIndex` +`integer` + +The index of a Part object within its parent Content object. + +`startIndex` +`integer` + +Start index in the given Part, measured in bytes. Offset from the start of the Part, inclusive, starting at zero. + +`endIndex` +`integer` + +End index in the given Part, measured in bytes. Offset from the start of the Part, exclusive, starting at zero. + +`text` +`string` + +The text corresponding to the segment from the response. + +| JSON representation | +| --- | +| ``` { "partIndex": integer, "startIndex": integer, "endIndex": integer, "text": string } ``` | + +## RetrievalMetadata + +Metadata related to retrieval in the grounding flow. + +`googleSearchDynamicRetrievalScore` +`number` + +Optional. Score indicating how likely information from google search could help answer the prompt. The score is in the range [0, 1], where 0 is the least likely and 1 is the most likely. This score is only populated when google search grounding and dynamic retrieval is enabled. It will be compared to the threshold to determine whether to trigger google search. + +| JSON representation | +| --- | +| ``` { "googleSearchDynamicRetrievalScore": number } ``` | + +## LogprobsResult + +Logprobs Result + +`topCandidates[]` +`object (TopCandidates)` +`TopCandidates` + +Length = total number of decoding steps. + +`chosenCandidates[]` +`object (Candidate)` +`Candidate` + +Length = total number of decoding steps. The chosen candidates may or may not be in topCandidates. + +`logProbabilitySum` +`number` + +Sum of log probabilities for all tokens. + +| JSON representation | +| --- | +| ``` { "topCandidates": [ { object (TopCandidates) } ], "chosenCandidates": [ { object (Candidate) } ], "logProbabilitySum": number } ``` | + +`TopCandidates` +`Candidate` + +## TopCandidates + +Candidates with top log probabilities at each decoding step. + +`candidates[]` +`object (Candidate)` +`Candidate` + +Sorted by log probability in descending order. + +| JSON representation | +| --- | +| ``` { "candidates": [ { object (Candidate) } ] } ``` | + +`Candidate` + +## Candidate + +Candidate for the logprobs token and score. + +`token` +`string` + +The candidate’s token string value. + +`tokenId` +`integer` + +The candidate’s token id value. + +`logProbability` +`number` + +The candidate's log probability. + +| JSON representation | +| --- | +| ``` { "token": string, "tokenId": integer, "logProbability": number } ``` | + +## UrlContextMetadata + +Metadata related to url context retrieval tool. + +`urlMetadata[]` +`object (UrlMetadata)` +`UrlMetadata` + +List of url context. + +| JSON representation | +| --- | +| ``` { "urlMetadata": [ { object (UrlMetadata) } ] } ``` | + +`UrlMetadata` + +## UrlMetadata + +Context of the a single url retrieval. + +`retrievedUrl` +`string` + +Retrieved url by the tool. + +`urlRetrievalStatus` +`enum (UrlRetrievalStatus)` +`UrlRetrievalStatus` + +Status of the url retrieval. + +| JSON representation | +| --- | +| ``` { "retrievedUrl": string, "urlRetrievalStatus": enum (UrlRetrievalStatus) } ``` | + +`UrlRetrievalStatus` + +## UrlRetrievalStatus + +Status of the url retrieval. + +| Enums | | +| --- | --- | +| `URL_RETRIEVAL_STATUS_UNSPECIFIED` | Default value. This value is unused. | +| `URL_RETRIEVAL_STATUS_SUCCESS` | Url retrieval is successful. | +| `URL_RETRIEVAL_STATUS_ERROR` | Url retrieval is failed due to error. | +| `URL_RETRIEVAL_STATUS_PAYWALL` | Url retrieval is failed because the content is behind paywall. | +| `URL_RETRIEVAL_STATUS_UNSAFE` | Url retrieval is failed because the content is unsafe. | + +`URL_RETRIEVAL_STATUS_UNSPECIFIED` +`URL_RETRIEVAL_STATUS_SUCCESS` +`URL_RETRIEVAL_STATUS_ERROR` +`URL_RETRIEVAL_STATUS_PAYWALL` +`URL_RETRIEVAL_STATUS_UNSAFE` + +## CitationMetadata + +A collection of source attributions for a piece of content. + +`citationSources[]` +`object (CitationSource)` +`CitationSource` + +Citations to sources for a specific response. + +| JSON representation | +| --- | +| ``` { "citationSources": [ { object (CitationSource) } ] } ``` | + +`CitationSource` + +## CitationSource + +A citation to a source for a portion of a specific response. + +`startIndex` +`integer` + +Optional. Start of segment of the response that is attributed to this source. + +Index indicates the start of the segment, measured in bytes. + +`endIndex` +`integer` + +Optional. End of the attributed segment, exclusive. + +`uri` +`string` + +Optional. URI that is attributed as a source for a portion of the text. + +`license` +`string` + +Optional. License for the GitHub project that is attributed as a source for segment. + +License info is required for code citations. + +| JSON representation | +| --- | +| ``` { "startIndex": integer, "endIndex": integer, "uri": string, "license": string } ``` | + +## HarmCategory + +The category of a rating. + +These categories cover various kinds of harms that developers may wish to adjust. + +| Enums | | +| --- | --- | +| `HARM_CATEGORY_UNSPECIFIED` | Category is unspecified. | +| `HARM_CATEGORY_DEROGATORY` | **PaLM** - Negative or harmful comments targeting identity and/or protected attribute. | +| `HARM_CATEGORY_TOXICITY` | **PaLM** - Content that is rude, disrespectful, or profane. | +| `HARM_CATEGORY_VIOLENCE` | **PaLM** - Describes scenarios depicting violence against an individual or group, or general descriptions of gore. | +| `HARM_CATEGORY_SEXUAL` | **PaLM** - Contains references to sexual acts or other lewd content. | +| `HARM_CATEGORY_MEDICAL` | **PaLM** - Promotes unchecked medical advice. | +| `HARM_CATEGORY_DANGEROUS` | **PaLM** - Dangerous content that promotes, facilitates, or encourages harmful acts. | +| `HARM_CATEGORY_HARASSMENT` | **Gemini** - Harassment content. | +| `HARM_CATEGORY_HATE_SPEECH` | **Gemini** - Hate speech and content. | +| `HARM_CATEGORY_SEXUALLY_EXPLICIT` | **Gemini** - Sexually explicit content. | +| `HARM_CATEGORY_DANGEROUS_CONTENT` | **Gemini** - Dangerous content. | +| `HARM_CATEGORY_CIVIC_INTEGRITY` | **Gemini** - Content that may be used to harm civic integrity. DEPRECATED: use enableEnhancedCivicAnswers instead. This item is deprecated! | +| `HARM_CATEGORY_JAILBREAK` | **Gemini** - Prompts attempting to bypass or subvert the model's safety guidelines (jailbreak attempts). | + +`HARM_CATEGORY_UNSPECIFIED` +`HARM_CATEGORY_DEROGATORY` +`HARM_CATEGORY_TOXICITY` +`HARM_CATEGORY_VIOLENCE` +`HARM_CATEGORY_SEXUAL` +`HARM_CATEGORY_MEDICAL` +`HARM_CATEGORY_DANGEROUS` +`HARM_CATEGORY_HARASSMENT` +`HARM_CATEGORY_HATE_SPEECH` +`HARM_CATEGORY_SEXUALLY_EXPLICIT` +`HARM_CATEGORY_DANGEROUS_CONTENT` +`HARM_CATEGORY_CIVIC_INTEGRITY` + +**Gemini** - Content that may be used to harm civic integrity. DEPRECATED: use enableEnhancedCivicAnswers instead. + +This item is deprecated! + +`HARM_CATEGORY_JAILBREAK` + +## ModalityTokenCount + +Represents token counting info for a single modality. + +`modality` +`enum (Modality)` +`Modality` + +The modality associated with this token count. + +`tokenCount` +`integer` + +Number of tokens. + +| JSON representation | +| --- | +| ``` { "modality": enum (Modality), "tokenCount": integer } ``` | + +`Modality` + +## Modality + +Content Part modality + +| Enums | | +| --- | --- | +| `MODALITY_UNSPECIFIED` | Unspecified modality. | +| `TEXT` | Plain text. | +| `IMAGE` | Image. | +| `VIDEO` | Video. | +| `AUDIO` | Audio. | +| `DOCUMENT` | Document, e.g. PDF. | + +`MODALITY_UNSPECIFIED` +`TEXT` +`IMAGE` +`VIDEO` +`AUDIO` +`DOCUMENT` + +## SafetyRating + +Safety rating for a piece of content. + +The safety rating contains the category of harm and the harm probability level in that category for a piece of content. Content is classified for safety across a number of harm categories and the probability of the harm classification is included here. + +`category` +`enum (HarmCategory)` +`HarmCategory` + +Required. The category for this rating. + +`probability` +`enum (HarmProbability)` +`HarmProbability` + +Required. The probability of harm for this content. + +`blocked` +`boolean` + +Was this content blocked because of this rating? + +| JSON representation | +| --- | +| ``` { "category": enum (HarmCategory), "probability": enum (HarmProbability), "blocked": boolean } ``` | + +`HarmCategory` +`HarmProbability` + +## HarmProbability + +The probability that a piece of content is harmful. + +The classification system gives the probability of the content being unsafe. This does not indicate the severity of harm for a piece of content. + +| Enums | | +| --- | --- | +| `HARM_PROBABILITY_UNSPECIFIED` | Probability is unspecified. | +| `NEGLIGIBLE` | Content has a negligible chance of being unsafe. | +| `LOW` | Content has a low chance of being unsafe. | +| `MEDIUM` | Content has a medium chance of being unsafe. | +| `HIGH` | Content has a high chance of being unsafe. | + +`HARM_PROBABILITY_UNSPECIFIED` +`NEGLIGIBLE` +`LOW` +`MEDIUM` +`HIGH` + +## SafetySetting + +Safety setting, affecting the safety-blocking behavior. + +Passing a safety setting for a category changes the allowed probability that content is blocked. + +`category` +`enum (HarmCategory)` +`HarmCategory` + +Required. The category for this setting. + +`threshold` +`enum (HarmBlockThreshold)` +`HarmBlockThreshold` + +Required. Controls the probability threshold at which harm is blocked. + +| JSON representation | +| --- | +| ``` { "category": enum (HarmCategory), "threshold": enum (HarmBlockThreshold) } ``` | + +`HarmCategory` +`HarmBlockThreshold` + +## HarmBlockThreshold + +Block at and beyond a specified harm probability. + +| Enums | | +| --- | --- | +| `HARM_BLOCK_THRESHOLD_UNSPECIFIED` | Threshold is unspecified. | +| `BLOCK_LOW_AND_ABOVE` | Content with NEGLIGIBLE will be allowed. | +| `BLOCK_MEDIUM_AND_ABOVE` | Content with NEGLIGIBLE and LOW will be allowed. | +| `BLOCK_ONLY_HIGH` | Content with NEGLIGIBLE, LOW, and MEDIUM will be allowed. | +| `BLOCK_NONE` | All content will be allowed. | +| `OFF` | Turn off the safety filter. | + +`HARM_BLOCK_THRESHOLD_UNSPECIFIED` +`BLOCK_LOW_AND_ABOVE` +`BLOCK_MEDIUM_AND_ABOVE` +`BLOCK_ONLY_HIGH` +`BLOCK_NONE` +`OFF` + +## ServiceTier + +Service tier of the request. + +| Enums | | +| --- | --- | +| `unspecified` | Default service tier, which is standard. | +| `standard` | Standard service tier. | +| `flex` | Flex service tier. | +| `priority` | Priority service tier. | + +`unspecified` +`standard` +`flex` +`priority` + +## Content + +The base structured datatype containing multi-part content of a message. + +A `Content` includes a `role` field designating the producer of the `Content` and a `parts` field containing multi-part data that contains the content of the message turn. + +`Content` +`role` +`Content` +`parts` +`parts[]` +`object (Part)` +`Part` + +Ordered `Parts` that constitute a single message. Parts may have different MIME types. + +`Parts` +`role` +`string` + +Optional. The producer of the content. Must be either 'user' or 'model'. + +Useful to set for multi-turn conversations, otherwise can be left blank or unset. + +| JSON representation | +| --- | +| ``` { "parts": [ { object (Part) } ], "role": string } ``` | + +`Part` + +## Part + +A datatype containing media that is part of a multi-part `Content` message. + +`Content` + +A `Part` consists of data which has an associated datatype. A `Part` can only contain one of the accepted types in `Part.data`. + +`Part` +`Part` +`Part.data` + +A `Part` must have a fixed IANA MIME type identifying the type and subtype of the media if the `inlineData` field is filled with raw bytes. + +`Part` +`inlineData` +`thought` +`boolean` + +Optional. Indicates if the part is thought from the model. + +`thoughtSignature` +`string (bytes format)` + +Optional. An opaque signature for the thought so it can be reused in subsequent requests. + +A base64-encoded string. + +`partMetadata` +`object (Struct format)` +`Struct` + +Custom metadata associated with the Part. Agents using genai.Part as content representation may need to keep track of the additional information. For example it can be name of a file/source from which the Part originates or a way to multiplex multiple Part streams. + +`mediaResolution` +`object (MediaResolution)` +`MediaResolution` + +Optional. Media resolution for the input media. + +`mediaProcessing` +`enum (MediaProcessing)` +`MediaProcessing` + +Optional. How the model processes this part's media for understanding. Only meaningful for video parts (`inlineData` or `fileData` with video mime). Non-video parts ignore this field. + +`inlineData` +`fileData` +`data` +`Union type` +`data` +`text` +`string` + +Inline text. + +`inlineData` +`object (Blob)` +`Blob` + +Inline media bytes. + +`functionCall` +`object (FunctionCall)` +`FunctionCall` + +A predicted `FunctionCall` returned from the model that contains a string representing the `FunctionDeclaration.name` with the arguments and their values. + +`FunctionCall` +`FunctionDeclaration.name` +`functionResponse` +`object (FunctionResponse)` +`FunctionResponse` + +The result output of a `FunctionCall` that contains a string representing the `FunctionDeclaration.name` and a structured JSON object containing any output from the function is used as context to the model. + +`FunctionCall` +`FunctionDeclaration.name` +`fileData` +`object (FileData)` +`FileData` + +URI based data. + +`executableCode` +`object (ExecutableCode)` +`ExecutableCode` + +Code generated by the model that is meant to be executed. + +`codeExecutionResult` +`object (CodeExecutionResult)` +`CodeExecutionResult` + +Result of executing the `ExecutableCode`. + +`ExecutableCode` +`toolCall` +`object (ToolCall)` +`ToolCall` + +Server-side tool call. This field is populated when the model predicts a tool invocation that should be executed on the server. The client is expected to echo this message back to the API. + +`toolResponse` +`object (ToolResponse)` +`ToolResponse` + +The output from a server-side `ToolCall` execution. This field is populated by the client with the results of executing the corresponding `ToolCall`. + +`ToolCall` +`ToolCall` +`metadata` +`Union type` +`metadata` +`videoMetadata` +`object (VideoMetadata)` +`VideoMetadata` + +Optional. Video metadata. The metadata should only be specified while the video data is presented in inlineData or fileData. + +| JSON representation | +| --- | +| ``` { "thought": boolean, "thoughtSignature": string, "partMetadata": { object }, "mediaResolution": { object (MediaResolution) }, "mediaProcessing": enum (MediaProcessing), // data "text": string, "inlineData": { object (Blob) }, "functionCall": { object (FunctionCall) }, "functionResponse": { object (FunctionResponse) }, "fileData": { object (FileData) }, "executableCode": { object (ExecutableCode) }, "codeExecutionResult": { object (CodeExecutionResult) }, "toolCall": { object (ToolCall) }, "toolResponse": { object (ToolResponse) } // Union type // metadata "videoMetadata": { object (VideoMetadata) } // Union type } ``` | + +`MediaResolution` +`MediaProcessing` +`Blob` +`FunctionCall` +`FunctionResponse` +`FileData` +`ExecutableCode` +`CodeExecutionResult` +`ToolCall` +`ToolResponse` +`VideoMetadata` + +## Blob + +Raw media bytes. + +Text should not be sent as raw bytes, use the 'text' field. + +`mimeType` +`string` + +The IANA standard MIME type of the source data. Examples of supported types: - Images: image/png, image/jpeg, image/jpg, image/webp, image/heic, image/heif, image/gif, image/avif - Audio: audio/\*, video/audio/s16le, video/audio/wav - Video: video/\* - Text: text/plain, text/html, text/css, text/javascript, text/x-typescript, text/csv, text/markdown, text/x-python, text/xml, text/rtf, video/text/timestamp - Applications: application/x-javascript, application/x-typescript, application/x-python-code, application/json, application/x-ipynb+json, application/rtf, application/pdf For additional context, see [Supported file formats](https://ai.google.dev/gemini-api/docs/file-input-methods#supported-content-types). // + +`data` +`string (bytes format)` + +Raw bytes for media formats. + +A base64-encoded string. + +| JSON representation | +| --- | +| ``` { "mimeType": string, "data": string } ``` | + +## FunctionCall + +A predicted `FunctionCall` returned from the model that contains a string representing the `FunctionDeclaration.name` with the arguments and their values. + +`FunctionCall` +`FunctionDeclaration.name` +`id` +`string` + +Optional. Unique identifier of the function call. If populated, the client to execute the `functionCall` and return the response with the matching `id`. + +`functionCall` +`id` +`name` +`string` + +Required. The name of the function to call. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 128. + +`args` +`object (Struct format)` +`Struct` + +Optional. The function parameters and values in JSON object format. + +| JSON representation | +| --- | +| ``` { "id": string, "name": string, "args": { object } } ``` | + +## FunctionResponse + +The result output from a `FunctionCall` that contains a string representing the `FunctionDeclaration.name` and a structured JSON object containing any output from the function is used as context to the model. This should contain the result of a`FunctionCall` made based on model prediction. + +`FunctionCall` +`FunctionDeclaration.name` +`FunctionCall` +`id` +`string` + +Optional. The identifier of the function call this response is for. Populated by the client to match the corresponding function call `id`. + +`id` +`name` +`string` + +Required. The name of the function to call. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 128. + +`response` +`object (Struct format)` +`Struct` + +Required. The function response in JSON object format. Callers can use any keys of their choice that fit the function's syntax to return the function output, e.g. "output", "result", etc. In particular, if the function call failed to execute, the response can have an "error" key to return error details to the model. + +Multimedia can be included by using a subobject containing a single "$ref" key whose value is the `inlineData.display_name` of a `FunctionResponsePart` holding the multimedia. See . + +`inlineData.display_name` +`FunctionResponsePart` +`parts[]` +`object (FunctionResponsePart)` +`FunctionResponsePart` + +Optional. Ordered `Parts` that constitute a function response. Parts may have different IANA MIME types. + +`Parts` +`willContinue` +`boolean` + +Optional. Signals that function call continues, and more responses will be returned, turning the function call into a generator. Is only applicable to NON\_BLOCKING function calls, is ignored otherwise. If set to false, future responses will not be considered. It is allowed to return empty `response` with `willContinue=False` to signal that the function call is finished. This may still trigger the model generation. To avoid triggering the generation and finish the function call, additionally set `scheduling` to `SILENT`. + +`response` +`willContinue=False` +`scheduling` +`SILENT` +`scheduling` +`enum (Scheduling)` +`Scheduling` + +Optional. Specifies how the response should be scheduled in the conversation. Only applicable to NON\_BLOCKING function calls, is ignored otherwise. Defaults to WHEN\_IDLE. + +| JSON representation | +| --- | +| ``` { "id": string, "name": string, "response": { object }, "parts": [ { object (FunctionResponsePart) } ], "willContinue": boolean, "scheduling": enum (Scheduling) } ``` | + +`FunctionResponsePart` +`Scheduling` + +## FunctionResponsePart + +A datatype containing media that is part of a `FunctionResponse` message. + +`FunctionResponse` + +A `FunctionResponsePart` consists of data which has an associated datatype. A `FunctionResponsePart` can only contain one of the accepted types in `FunctionResponsePart.data`. + +`FunctionResponsePart` +`FunctionResponsePart` +`FunctionResponsePart.data` + +A `FunctionResponsePart` must have a fixed IANA MIME type identifying the type and subtype of the media if the `inlineData` field is filled with raw bytes. + +`FunctionResponsePart` +`inlineData` +`data` +`Union type` +`data` +`inlineData` +`object (FunctionResponseBlob)` +`FunctionResponseBlob` + +Inline media bytes. + +| JSON representation | +| --- | +| ``` { // data "inlineData": { object (FunctionResponseBlob) } // Union type } ``` | + +`FunctionResponseBlob` + +## FunctionResponseBlob + +Raw media bytes for function response. + +Text should not be sent as raw bytes, use the 'FunctionResponse.response' field. + +`mimeType` +`string` + +The IANA standard MIME type of the source data. Examples: - image/png - image/jpeg If an unsupported MIME type is provided, an error will be returned. For a complete list of supported types, see [Supported file formats](https://ai.google.dev/gemini-api/docs/prompting_with_media#supported_file_formats). + +`data` +`string (bytes format)` + +Raw bytes for media formats. + +A base64-encoded string. + +| JSON representation | +| --- | +| ``` { "mimeType": string, "data": string } ``` | + +## Scheduling + +Specifies how the response should be scheduled in the conversation. + +| Enums | | +| --- | --- | +| `SCHEDULING_UNSPECIFIED` | This value is unused. | +| `SILENT` | Only add the result to the conversation context, do not interrupt or trigger generation. | +| `WHEN_IDLE` | Add the result to the conversation context, and prompt to generate output without interrupting ongoing generation. | +| `INTERRUPT` | Add the result to the conversation context, interrupt ongoing generation and prompt to generate output. | + +`SCHEDULING_UNSPECIFIED` +`SILENT` +`WHEN_IDLE` +`INTERRUPT` + +## FileData + +URI based data. + +`mimeType` +`string` + +Optional. The IANA standard MIME type of the source data. + +`fileUri` +`string` + +Required. URI. + +| JSON representation | +| --- | +| ``` { "mimeType": string, "fileUri": string } ``` | + +## ExecutableCode + +Code generated by the model that is meant to be executed, and the result returned to the model. + +Only generated when using the `CodeExecution` tool, in which the code will be automatically executed, and a corresponding `CodeExecutionResult` will also be generated. + +`CodeExecution` +`CodeExecutionResult` +`id` +`string` + +Optional. Unique identifier of the `ExecutableCode` part. The server returns the `CodeExecutionResult` with the matching `id`. + +`ExecutableCode` +`CodeExecutionResult` +`id` +`language` +`enum (Language)` +`Language` + +Required. Programming language of the `code`. + +`code` +`code` +`string` + +Required. The code to be executed. + +| JSON representation | +| --- | +| ``` { "id": string, "language": enum (Language), "code": string } ``` | + +`Language` + +## Language + +Supported programming languages for the generated code. + +| Enums | | +| --- | --- | +| `LANGUAGE_UNSPECIFIED` | Unspecified language. This value should not be used. | +| `PYTHON` | Python >= 3.10, with numpy and simpy available. Python is the default language. | + +`LANGUAGE_UNSPECIFIED` +`PYTHON` + +## CodeExecutionResult + +Result of executing the `ExecutableCode`. + +`ExecutableCode` + +Generated only when the `CodeExecution` tool is used. + +`CodeExecution` +`id` +`string` + +Optional. The identifier of the `ExecutableCode` part this result is for. Only populated if the corresponding `ExecutableCode` has an id. + +`ExecutableCode` +`ExecutableCode` +`outcome` +`enum (Outcome)` +`Outcome` + +Required. Outcome of the code execution. + +`output` +`string` + +Optional. Contains stdout when code execution is successful, stderr or other description otherwise. + +| JSON representation | +| --- | +| ``` { "id": string, "outcome": enum (Outcome), "output": string } ``` | + +`Outcome` + +## Outcome + +Enumeration of possible outcomes of the code execution. + +| Enums | | +| --- | --- | +| `OUTCOME_UNSPECIFIED` | Unspecified status. This value should not be used. | +| `OUTCOME_OK` | Code execution completed successfully. `output` contains the stdout, if any. | +| `OUTCOME_FAILED` | Code execution failed. `output` contains the stderr and stdout, if any. | +| `OUTCOME_DEADLINE_EXCEEDED` | Code execution ran for too long, and was cancelled. There may or may not be a partial `output` present. | + +`OUTCOME_UNSPECIFIED` +`OUTCOME_OK` +`output` +`OUTCOME_FAILED` +`output` +`OUTCOME_DEADLINE_EXCEEDED` +`output` + +## ToolCall + +A predicted server-side `ToolCall` returned from the model. This message contains information about a tool that the model wants to invoke. The client is NOT expected to execute this `ToolCall`. Instead, the client should pass this `ToolCall` back to the API in a subsequent turn within a `Content` message, along with the corresponding `ToolResponse`. + +`ToolCall` +`ToolCall` +`ToolCall` +`Content` +`ToolResponse` +`id` +`string` + +Optional. Unique identifier of the tool call. The server returns the tool response with the matching `id`. + +`id` +`toolName` +`string` + +Optional. The name of the tool that was called. + +`toolType` +`enum (ToolType)` +`ToolType` + +Required. The type of tool that was called. + +`args` +`object (Struct format)` +`Struct` + +Optional. The tool call arguments. Example: {"arg1" : "value1", "arg2" : "value2" , ...} + +| JSON representation | +| --- | +| ``` { "id": string, "toolName": string, "toolType": enum (ToolType), "args": { object } } ``` | + +`ToolType` + +## ToolType + +The type of tool in the function call. + +| Enums | | +| --- | --- | +| `TOOL_TYPE_UNSPECIFIED` | Unspecified tool type. | +| `GOOGLE_SEARCH_WEB` | Google search tool, maps to Tool.google\_search.search\_types.web\_search. | +| `GOOGLE_SEARCH_IMAGE` | Image search tool, maps to Tool.google\_search.search\_types.image\_search. | +| `URL_CONTEXT` | URL context tool, maps to Tool.url\_context. | +| `GOOGLE_MAPS` | Google maps tool, maps to Tool.google\_maps. | +| `FILE_SEARCH` | File search tool, maps to Tool.file\_search. | + +`TOOL_TYPE_UNSPECIFIED` +`GOOGLE_SEARCH_WEB` +`GOOGLE_SEARCH_IMAGE` +`URL_CONTEXT` +`GOOGLE_MAPS` +`FILE_SEARCH` + +## ToolResponse + +The output from a server-side `ToolCall` execution. This message contains the results of a tool invocation that was initiated by a `ToolCall` from the model. The client should pass this `ToolResponse` back to the API in a subsequent turn within a `Content` message, along with the corresponding `ToolCall`. + +`ToolCall` +`ToolCall` +`ToolResponse` +`Content` +`ToolCall` +`id` +`string` + +Optional. The identifier of the tool call this response is for. + +`toolType` +`enum (ToolType)` +`ToolType` + +Required. The type of tool that was called, matching the `toolType` in the corresponding `ToolCall`. + +`toolType` +`ToolCall` +`response` +`object (Struct format)` +`Struct` + +Optional. The tool response. + +| JSON representation | +| --- | +| ``` { "id": string, "toolType": enum (ToolType), "response": { object } } ``` | + +`ToolType` + +## VideoMetadata + +This item is deprecated! + +Deprecated: Use `GenerateContentRequest.processing_options` instead. Metadata describes the input video content. + +`GenerateContentRequest.processing_options` +`startOffset` +`string (Duration format)` +`Duration` + +Optional. The start offset of the video. + +A duration in seconds with up to nine fractional digits, ending with '`s`'. Example: `"3.5s"`. + +`s` +`"3.5s"` +`endOffset` +`string (Duration format)` +`Duration` + +Optional. The end offset of the video. + +A duration in seconds with up to nine fractional digits, ending with '`s`'. Example: `"3.5s"`. + +`s` +`"3.5s"` +`fps` +`number` + +Optional. The frame rate of the video sent to the model. If not specified, the default value will be 1.0. The fps range is (0.0, 24.0]. + +| JSON representation | +| --- | +| ``` { "startOffset": string, "endOffset": string, "fps": number } ``` | + +## MediaResolution + +Media resolution for tokenization. + +`value` +`Union type` +`value` +`level` +`enum (Level)` +`Level` + +The tokenization quality used for given media. for Gemini API support . + +| JSON representation | +| --- | +| ``` { // value "level": enum (Level) // Union type } ``` | + +`Level` + +## Level + +The media resolution level. + +| Enums | | +| --- | --- | +| `MEDIA_RESOLUTION_UNSPECIFIED` | Media resolution has not been set. | +| `MEDIA_RESOLUTION_LOW` | Media resolution set to low. | +| `MEDIA_RESOLUTION_MEDIUM` | Media resolution set to medium. | +| `MEDIA_RESOLUTION_HIGH` | Media resolution set to high. | +| `MEDIA_RESOLUTION_ULTRA_HIGH` | Media resolution set to ultra high. | + +`MEDIA_RESOLUTION_UNSPECIFIED` +`MEDIA_RESOLUTION_LOW` +`MEDIA_RESOLUTION_MEDIUM` +`MEDIA_RESOLUTION_HIGH` +`MEDIA_RESOLUTION_ULTRA_HIGH` + +## MediaProcessing + +How the model processes input media for understanding. + +| Enums | | +| --- | --- | +| `MEDIA_PROCESSING_UNSPECIFIED` | Default. Uses model-specific processing (3.5 Pro+ -> `AGENTIC`, older models -> `STATIC`). | +| `STATIC` | Fixed-rate frame extraction. All frames placed in context. | +| `AGENTIC` | Model-driven dynamic navigation. Recommended for most use cases. | + +`MEDIA_PROCESSING_UNSPECIFIED` +`AGENTIC` +`STATIC` +`STATIC` +`AGENTIC` + +## Environment + +An execution environment for an agent. + +`id` +`string` + +Required. Output only. The ID of the environment. + +`sources[]` +`object (Source)` +`Source` + +Sources to be mounted into the environment. + +`created` +`string` + +Output only. The time at which the environment was created in ISO 8601 format (YYYY-MM-DDThh:mm:ssZ). + +`updated` +`string` + +Output only. The time at which the environment was last updated in ISO 8601 format (YYYY-MM-DDThh:mm:ssZ). + +`lastAccessed` +`string` + +Output only. The time at which the environment was last accessed in ISO 8601 format (YYYY-MM-DDThh:mm:ssZ). + +`status` +`enum (Status)` +`Status` + +Output only. The status of the environment container. + +`fileCount` +`string (int64 format)` + +Output only. The number of files in the environment, output only. + +`sizeBytes` +`string (int64 format)` + +Output only. The total size of the environment files in bytes, output only. + +`network` +`Union type` +`network` +`networkAllowlist` +`object (EnvironmentNetworkEgressAllowlist)` +`EnvironmentNetworkEgressAllowlist` + +Allow only specific domains. + +`networkMode` +`enum (NetworkMode)` +`NetworkMode` + +Network egress mode. + +| JSON representation | +| --- | +| ``` { "id": string, "sources": [ { object (Source) } ], "created": string, "updated": string, "lastAccessed": string, "status": enum (Status), "fileCount": string, "sizeBytes": string, // network "networkAllowlist": { object (EnvironmentNetworkEgressAllowlist) }, "networkMode": enum (NetworkMode) // Union type } ``` | + +`Source` +`Status` +`EnvironmentNetworkEgressAllowlist` +`NetworkMode` + +## Status + +Status of the environment. + +| Enums | | +| --- | --- | +| `STATUS_UNSPECIFIED` | | +| `ACTIVE` | | +| `EXPIRED` | | + +`STATUS_UNSPECIFIED` +`ACTIVE` +`EXPIRED` + +## NetworkMode + +Network egress mode for non-allowlist configurations. + +| Enums | | +| --- | --- | +| `NETWORK_MODE_UNSPECIFIED` | Default value. Unused. | +| `DISABLED` | All network egress is blocked. | + +`NETWORK_MODE_UNSPECIFIED` +`DISABLED` + +## Schema + +The `Schema` object allows the definition of input and output data types. These types can be objects, but also primitives and arrays. Represents a select subset of an [OpenAPI 3.0 schema object](https://spec.openapis.org/oas/v3.0.3#schema). + +`Schema` +`type` +`enum (Type)` +`Type` + +Required. Data type. + +`format` +`string` + +Optional. The format of the data. Any value is allowed, but most do not trigger any special functionality. + +`title` +`string` + +Optional. The title of the schema. + +`description` +`string` + +Optional. A brief description of the parameter. This could contain examples of use. Parameter description may be formatted as Markdown. + +`nullable` +`boolean` + +Optional. Indicates if the value may be null. + +`enum[]` +`string` + +Optional. Possible values of the element of Type.STRING with enum format. For example we can define an Enum Direction as : {type:STRING, format:enum, enum:["EAST", NORTH", "SOUTH", "WEST"]} + +`maxItems` +`string (int64 format)` + +Optional. Maximum number of the elements for Type.ARRAY. + +`minItems` +`string (int64 format)` + +Optional. Minimum number of the elements for Type.ARRAY. + +`properties` +`map (key: string, value: object (Schema))` +`Schema` + +Optional. Properties of Type.OBJECT. + +An object containing a list of `"key": value` pairs. Example: `{ "name": "wrench", "mass": "1.3kg", "count": "3" }`. + +`"key": value` +`{ "name": "wrench", "mass": "1.3kg", "count": "3" }` +`required[]` +`string` + +Optional. Required properties of Type.OBJECT. + +`minProperties` +`string (int64 format)` + +Optional. Minimum number of the properties for Type.OBJECT. + +`maxProperties` +`string (int64 format)` + +Optional. Maximum number of the properties for Type.OBJECT. + +`minLength` +`string (int64 format)` + +Optional. SCHEMA FIELDS FOR TYPE STRING Minimum length of the Type.STRING + +`maxLength` +`string (int64 format)` + +Optional. Maximum length of the Type.STRING + +`pattern` +`string` + +Optional. Pattern of the Type.STRING to restrict a string to a regular expression. + +`example` +`value (Value format)` +`Value` + +Optional. Example of the object. Will only populated when the object is the root. + +`anyOf[]` +`object (Schema)` +`Schema` + +Optional. The value should be validated against any (one or more) of the subschemas in the list. + +`propertyOrdering[]` +`string` + +Optional. The order of the properties. Not a standard field in open api spec. Used to determine the order of the properties in the response. + +`default` +`value (Value format)` +`Value` + +Optional. Default value of the field. Per JSON Schema, this field is intended for documentation generators and doesn't affect validation. Thus it's included here and ignored so that developers who send schemas with a `default` field don't get unknown-field errors. + +`default` +`items` +`object (Schema)` +`Schema` + +Optional. Schema of the elements of Type.ARRAY. + +`minimum` +`number` + +Optional. SCHEMA FIELDS FOR TYPE INTEGER and NUMBER Minimum value of the Type.INTEGER and Type.NUMBER + +`maximum` +`number` + +Optional. Maximum value of the Type.INTEGER and Type.NUMBER + +| JSON representation | +| --- | +| ``` { "type": enum (Type), "format": string, "title": string, "description": string, "nullable": boolean, "enum": [ string ], "maxItems": string, "minItems": string, "properties": { string: { object (Schema) }, ... }, "required": [ string ], "minProperties": string, "maxProperties": string, "minLength": string, "maxLength": string, "pattern": string, "example": value, "anyOf": [ { object (Schema) } ], "propertyOrdering": [ string ], "default": value, "items": { object (Schema) }, "minimum": number, "maximum": number } ``` | + +`Type` +`Schema` +`Schema` +`Schema` + +## Type + +Type contains the list of OpenAPI data types as defined by + +| Enums | | +| --- | --- | +| `TYPE_UNSPECIFIED` | Not specified, should not be used. | +| `STRING` | String type. | +| `NUMBER` | Number type. | +| `INTEGER` | Integer type. | +| `BOOLEAN` | Boolean type. | +| `ARRAY` | Array type. | +| `OBJECT` | Object type. | +| `NULL` | Null type. | + +`TYPE_UNSPECIFIED` +`STRING` +`NUMBER` +`INTEGER` +`BOOLEAN` +`ARRAY` +`OBJECT` +`NULL` + +## Tool + +Tool details that the model may use to generate response. + +A `Tool` is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. + +`Tool` + +Next ID: 17 + +`functionDeclarations[]` +`object (FunctionDeclaration)` +`FunctionDeclaration` + +Optional. A list of `FunctionDeclarations` available to the model that can be used for function calling. + +`FunctionDeclarations` + +The model or system does not execute the function. Instead the defined function may be returned as a `FunctionCall` with arguments to the client side for execution. The model may decide to call a subset of these functions by populating `FunctionCall` in the response. The next conversation turn may contain a `FunctionResponse` with the `Content.role` "function" generation context for the next model turn. + +`FunctionCall` +`FunctionCall` +`FunctionResponse` +`Content.role` +`googleSearchRetrieval` +`object (GoogleSearchRetrieval)` +`GoogleSearchRetrieval` + +Optional. Retrieval tool that is powered by Google search. + +`codeExecution` +`object (CodeExecution)` +`CodeExecution` + +Optional. Enables the model to execute code as part of generation. + +`googleSearch` +`object (GoogleSearch)` +`GoogleSearch` + +Optional. GoogleSearch tool type. Tool to support Google Search in Model. Powered by Google. + +`computerUse` +`object (ComputerUse)` +`ComputerUse` + +Optional. Tool to support the model interacting directly with the computer. If enabled, it automatically populates computer-use specific Function Declarations. + +`urlContext` +`object (UrlContext)` +`UrlContext` + +Optional. Tool to support URL context retrieval. + +`fileSearch` +`object (FileSearch)` +`FileSearch` + +Optional. FileSearch tool type. Tool to retrieve knowledge from Semantic Retrieval corpora. + +`mcpServers[]` +`object (McpServer)` +`McpServer` + +Optional. MCP Servers to connect to. + +`googleMaps` +`object (GoogleMaps)` +`GoogleMaps` + +Optional. Tool that allows grounding the model's response with geospatial context related to the user's query. + +| JSON representation | +| --- | +| ``` { "functionDeclarations": [ { object (FunctionDeclaration) } ], "googleSearchRetrieval": { object (GoogleSearchRetrieval) }, "codeExecution": { object (CodeExecution) }, "googleSearch": { object (GoogleSearch) }, "computerUse": { object (ComputerUse) }, "urlContext": { object (UrlContext) }, "fileSearch": { object (FileSearch) }, "mcpServers": [ { object (McpServer) } ], "googleMaps": { object (GoogleMaps) } } ``` | + +`FunctionDeclaration` +`GoogleSearchRetrieval` +`CodeExecution` +`GoogleSearch` +`ComputerUse` +`UrlContext` +`FileSearch` +`McpServer` +`GoogleMaps` + +## FunctionDeclaration + +Structured representation of a function declaration as defined by the [OpenAPI 3.03 specification](https://spec.openapis.org/oas/v3.0.3). Included in this declaration are the function name and parameters. This FunctionDeclaration is a representation of a block of code that can be used as a `Tool` by the model and executed by the client. + +`Tool` +`name` +`string` + +Required. The name of the function. Must be a-z, A-Z, 0-9, or contain underscores, colons, dots, and dashes, with a maximum length of 128. + +`description` +`string` + +Required. A brief description of the function. + +`behavior` +`enum (Behavior)` +`Behavior` + +Optional. Specifies the function Behavior. Currently only supported by the BidiGenerateContent method. + +`parameters` +`object (Schema)` +`Schema` + +Optional. Describes the parameters to this function. Reflects the Open API 3.03 Parameter Object string Key: the name of the parameter. Parameter names are case sensitive. Schema Value: the Schema defining the type used for the parameter. + +`parametersJsonSchema` +`value (Value format)` +`Value` + +Optional. Describes the parameters to the function in JSON Schema format. The schema must describe an object where the properties are the parameters to the function. For example: + +`{ +"type": "object", +"properties": { +"name": { "type": "string" }, +"age": { "type": "integer" } +}, +"additionalProperties": false, +"required": ["name", "age"], +"propertyOrdering": ["name", "age"] +}` + +This field is mutually exclusive with `parameters`. + +`parameters` +`response` +`object (Schema)` +`Schema` + +Optional. Describes the output from this function in JSON Schema format. Reflects the Open API 3.03 Response Object. The Schema defines the type used for the response value of the function. + +`responseJsonSchema` +`value (Value format)` +`Value` + +Optional. Describes the output from this function in JSON Schema format. The value specified by the schema is the response value of the function. + +This field is mutually exclusive with `response`. + +`response` + +| JSON representation | +| --- | +| ``` { "name": string, "description": string, "behavior": enum (Behavior), "parameters": { object (Schema) }, "parametersJsonSchema": value, "response": { object (Schema) }, "responseJsonSchema": value } ``` | + +`Behavior` +`Schema` +`Schema` + +## Behavior + +Defines the function behavior. Defaults to `BLOCKING`. + +`BLOCKING` + +| Enums | | +| --- | --- | +| `UNSPECIFIED` | This value is unused. | +| `BLOCKING` | If set, the system will wait to receive the function response before continuing the conversation. | +| `NON_BLOCKING` | If set, the system will not wait to receive the function response. Instead, it will attempt to handle function responses as they become available while maintaining the conversation between the user and the model. | + +`UNSPECIFIED` +`BLOCKING` +`NON_BLOCKING` + +## GoogleSearchRetrieval + +Tool to retrieve public web data for grounding, powered by Google. + +`dynamicRetrievalConfig` +`object (DynamicRetrievalConfig)` +`DynamicRetrievalConfig` + +Specifies the dynamic retrieval configuration for the given source. + +| JSON representation | +| --- | +| ``` { "dynamicRetrievalConfig": { object (DynamicRetrievalConfig) } } ``` | + +`DynamicRetrievalConfig` + +## DynamicRetrievalConfig + +Describes the options to customize dynamic retrieval. + +`mode` +`enum (Mode)` +`Mode` + +The mode of the predictor to be used in dynamic retrieval. + +`dynamicThreshold` +`number` + +The threshold to be used in dynamic retrieval. If not set, a system default value is used. + +| JSON representation | +| --- | +| ``` { "mode": enum (Mode), "dynamicThreshold": number } ``` | + +`Mode` + +## Mode + +The mode of the predictor to be used in dynamic retrieval. + +| Enums | | +| --- | --- | +| `MODE_UNSPECIFIED` | Always trigger retrieval. | +| `MODE_DYNAMIC` | Run retrieval only when system decides it is necessary. | + +`MODE_UNSPECIFIED` +`MODE_DYNAMIC` + +## CodeExecution + +This type has no fields. + +Tool that executes code generated by the model, and automatically returns the result to the model. + +See also `ExecutableCode` and `CodeExecutionResult` which are only generated when using this tool. + +`ExecutableCode` +`CodeExecutionResult` + +## GoogleSearch + +GoogleSearch tool type. Tool to support Google Search in Model. Powered by Google. + +`timeRangeFilter` +`object (Interval)` +`Interval` + +Optional. Filter search results to a specific time range. If customers set a start time, they must set an end time (and vice versa). + +`searchTypes` +`object (SearchTypes)` +`SearchTypes` + +Optional. The set of search types to enable. If not set, web search is enabled by default. + +| JSON representation | +| --- | +| ``` { "timeRangeFilter": { object (Interval) }, "searchTypes": { object (SearchTypes) } } ``` | + +`Interval` +`SearchTypes` + +## Interval + +Represents a time interval, encoded as a Timestamp start (inclusive) and a Timestamp end (exclusive). + +The start must be less than or equal to the end. When the start equals the end, the interval is empty (matches no time). When both start and end are unspecified, the interval matches any time. + +`startTime` +`string (Timestamp format)` +`Timestamp` + +Optional. Inclusive start of the interval. + +If specified, a Timestamp matching this interval will have to be the same or after the start. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` +`endTime` +`string (Timestamp format)` +`Timestamp` + +Optional. Exclusive end of the interval. + +If specified, a Timestamp matching this interval will have to be before the end. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` + +| JSON representation | +| --- | +| ``` { "startTime": string, "endTime": string } ``` | + +## SearchTypes + +Different types of search that can be enabled on the GoogleSearch tool. + +`webSearch` +`object (WebSearch)` +`WebSearch` + +Optional. Enables web search. Only text results are returned. + +`imageSearch` +`object (ImageSearch)` +`ImageSearch` + +Optional. Enables image search. Image bytes are returned. + +| JSON representation | +| --- | +| ``` { "webSearch": { object (WebSearch) }, "imageSearch": { object (ImageSearch) } } ``` | + +`WebSearch` +`ImageSearch` + +## WebSearch + +This type has no fields. + +Standard web search for grounding and related configurations. + +## ImageSearch + +This type has no fields. + +Image search for grounding and related configurations. + +## ComputerUse + +Computer Use tool type. + +`environment` +`enum (Environment)` +`Environment` + +Required. The environment being operated. + +`excludedPredefinedFunctions[]` +`string` + +Optional. By default, predefined functions are included in the final model call. Some of them can be explicitly excluded from being automatically included. This can serve two purposes: 1. Using a more restricted / different action space. 2. Improving the definitions / instructions of predefined functions. + +`enablePromptInjectionDetection` +`boolean` + +Optional. Whether enable the prompt injection detection check on computer-use request. + +`disabledSafetyPolicies[]` +`enum (SafetyPolicy)` +`SafetyPolicy` + +Optional. Disabled safety policies for computer use. + +| JSON representation | +| --- | +| ``` { "environment": enum (Environment), "excludedPredefinedFunctions": [ string ], "enablePromptInjectionDetection": boolean, "disabledSafetyPolicies": [ enum (SafetyPolicy) ] } ``` | + +`Environment` +`SafetyPolicy` + +## Environment + +Represents the environment being operated, such as a web browser. + +| Enums | | +| --- | --- | +| `ENVIRONMENT_UNSPECIFIED` | Defaults to browser. | +| `ENVIRONMENT_BROWSER` | Operates in a web browser. | +| `ENVIRONMENT_MOBILE` | Operates in a mobile environment. | +| `ENVIRONMENT_DESKTOP` | Operates in a desktop environment. | + +`ENVIRONMENT_UNSPECIFIED` +`ENVIRONMENT_BROWSER` +`ENVIRONMENT_MOBILE` +`ENVIRONMENT_DESKTOP` + +## SafetyPolicy + +Predefined safety policies for computer use. + +| Enums | | +| --- | --- | +| `SAFETY_POLICY_UNSPECIFIED` | Unspecified safety policy. | +| `FINANCIAL_TRANSACTIONS` | Safety policy for financial transactions. | +| `SENSITIVE_DATA_MODIFICATION` | Safety policy for sensitive data modification. | +| `COMMUNICATION_TOOL` | Safety policy for communication tools (e.g. Gmail, Chat, Meet). | +| `ACCOUNT_CREATION` | Safety policy for account creation. | +| `DATA_MODIFICATION` | Safety policy for data modification. | +| `USER_CONSENT_MANAGEMENT` | Safety policy for user consent management. | +| `LEGAL_TERMS_AND_AGREEMENTS` | Safety policy for legal terms and agreements. | + +`SAFETY_POLICY_UNSPECIFIED` +`FINANCIAL_TRANSACTIONS` +`SENSITIVE_DATA_MODIFICATION` +`COMMUNICATION_TOOL` +`ACCOUNT_CREATION` +`DATA_MODIFICATION` +`USER_CONSENT_MANAGEMENT` +`LEGAL_TERMS_AND_AGREEMENTS` + +## UrlContext + +This type has no fields. + +Tool to support URL context retrieval. + +## FileSearch + +The FileSearch tool that retrieves knowledge from Semantic Retrieval corpora. Files are imported to Semantic Retrieval corpora using the ImportFile API. + +`fileSearchStoreNames[]` +`string` + +Required. The names of the fileSearchStores to retrieve from. Example: `fileSearchStores/my-file-search-store-123` + +`fileSearchStores/my-file-search-store-123` +`metadataFilter` +`string` + +Optional. Metadata filter to apply to the semantic retrieval documents and chunks. + +`topK` +`integer` + +Optional. The number of semantic retrieval chunks to retrieve. + +| JSON representation | +| --- | +| ``` { "fileSearchStoreNames": [ string ], "metadataFilter": string, "topK": integer } ``` | + +## McpServer + +A MCPServer is a server that can be called by the model to perform actions. It is a server that implements the MCP protocol. Next ID: 6 + +`name` +`string` + +The name of the MCPServer. + +`transport` +`Union type` +`transport` +`streamableHttpTransport` +`object (StreamableHttpTransport)` +`StreamableHttpTransport` + +A transport that can stream HTTP requests and responses. + +| JSON representation | +| --- | +| ``` { "name": string, // transport "streamableHttpTransport": { object (StreamableHttpTransport) } // Union type } ``` | + +`StreamableHttpTransport` + +## StreamableHttpTransport + +A transport that can stream HTTP requests and responses. Next ID: 6 + +`url` +`string` + +The full URL for the MCPServer endpoint. Example: "https://api.example.com/mcp" + +`headers` +`map (key: string, value: string)` + +Optional: Fields for authentication headers, timeouts, etc., if needed. + +An object containing a list of `"key": value` pairs. Example: `{ "name": "wrench", "mass": "1.3kg", "count": "3" }`. + +`"key": value` +`{ "name": "wrench", "mass": "1.3kg", "count": "3" }` +`timeout` +`string (Duration format)` +`Duration` + +HTTP timeout for regular operations. + +A duration in seconds with up to nine fractional digits, ending with '`s`'. Example: `"3.5s"`. + +`s` +`"3.5s"` +`sseReadTimeout` +`string (Duration format)` +`Duration` + +Timeout for SSE read operations. + +A duration in seconds with up to nine fractional digits, ending with '`s`'. Example: `"3.5s"`. + +`s` +`"3.5s"` +`terminateOnClose` +`boolean` + +Whether to close the client session when the transport closes. + +| JSON representation | +| --- | +| ``` { "url": string, "headers": { string: string, ... }, "timeout": string, "sseReadTimeout": string, "terminateOnClose": boolean } ``` | + +## GoogleMaps + +The GoogleMaps Tool that provides geospatial context for the user's query. + +`enableWidget` +`boolean` + +Optional. Whether to return a widget context token in the GroundingMetadata of the response. Developers can use the widget context token to render a Google Maps widget with geospatial context related to the places that the model references in the response. + +| JSON representation | +| --- | +| ``` { "enableWidget": boolean } ``` | + +## REST Resource: auth\_tokens + +## Resource: AuthToken + +A request to create an ephemeral authentication token. + +`name` +`string` + +Output only. Identifier. The token itself. + +`expireTime` +`string (Timestamp format)` +`Timestamp` + +Optional. Input only. Immutable. An optional time after which, when using the resulting token, messages in BidiGenerateContent sessions will be rejected. (Gemini may preemptively close the session after this time.) + +If not set then this defaults to 30 minutes in the future. If set, this value must be less than 20 hours in the future. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` +`newSessionExpireTime` +`string (Timestamp format)` +`Timestamp` + +Optional. Input only. Immutable. The time after which new Live API sessions using the token resulting from this request will be rejected. + +If not set this defaults to 60 seconds in the future. If set, this value must be less than 20 hours in the future. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` +`fieldMask` +`string (FieldMask format)` +`FieldMask` + +Optional. Input only. Immutable. If fieldMask is empty, and `bidiGenerateContentSetup` is not present, then the effective `BidiGenerateContentSetup` message is taken from the Live API connection. + +`bidiGenerateContentSetup` +`BidiGenerateContentSetup` + +If fieldMask is empty, and `bidiGenerateContentSetup` *is* present, then the effective `BidiGenerateContentSetup` message is taken entirely from `bidiGenerateContentSetup` in this request. The setup message from the Live API connection is ignored. + +`bidiGenerateContentSetup` +`BidiGenerateContentSetup` +`bidiGenerateContentSetup` + +If fieldMask is not empty, then the corresponding fields from `bidiGenerateContentSetup` will overwrite the fields from the setup message in the Live API connection. + +`bidiGenerateContentSetup` + +This is a comma-separated list of fully qualified names of fields. Example: `"user.displayName,photo"`. + +`"user.displayName,photo"` +`config` +`Union type` +`config` +`bidiGenerateContentSetup` +`object (BidiGenerateContentSetup)` +`BidiGenerateContentSetup` + +Optional. Input only. Immutable. Configuration specific to `BidiGenerateContent`. + +`BidiGenerateContent` +`uses` +`integer` + +Optional. Input only. Immutable. The number of times the token can be used. If this value is zero then no limit is applied. Resuming a Live API session does not count as a use. If unspecified, the default is 1. + +| JSON representation | +| --- | +| ``` { "name": string, "expireTime": string, "newSessionExpireTime": string, "fieldMask": string, // config "bidiGenerateContentSetup": { object (BidiGenerateContentSetup) } // Union type "uses": integer } ``` | + +`BidiGenerateContentSetup` + +## BidiGenerateContentSetup + +Message to be sent in the first (and only in the first) `BidiGenerateContentClientMessage`. Contains configuration that will apply for the duration of the streaming RPC. + +`BidiGenerateContentClientMessage` + +Clients should wait for a `BidiGenerateContentSetupComplete` message before sending any additional messages. + +`BidiGenerateContentSetupComplete` +`model` +`string` + +Required. The model's resource name. This serves as an ID for the Model to use. + +Format: `models/{model}` + +`models/{model}` +`generationConfig` +`object (GenerationConfig)` +`GenerationConfig` + +Optional. Generation config. + +The following fields are not supported: + +`responseLogprobs` +`responseMimeType` +`logprobs` +`responseSchema` +`responseJsonSchema` +`stop_sequence` +`skipResponseCache` +`routing_config` +`audio_timestamp` +`systemInstruction` +`object (Content)` +`Content` + +Optional. The user provided system instructions for the model. + +Note: Only text should be used in parts and content in each part will be in a separate paragraph. + +`tools[]` +`object (Tool)` +`Tool` + +Optional. A list of `Tools` the model may use to generate the next response. + +`Tools` + +A `Tool` is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. + +`Tool` +`realtimeInputConfig` +`object (RealtimeInputConfig)` +`RealtimeInputConfig` + +Optional. Configures the handling of realtime input. + +`sessionResumption` +`object (SessionResumptionConfig)` +`SessionResumptionConfig` + +Optional. Configures session resumption mechanism. + +If included, the server will send `SessionResumptionUpdate` messages. + +`SessionResumptionUpdate` +`contextWindowCompression` +`object (ContextWindowCompressionConfig)` +`ContextWindowCompressionConfig` + +Optional. Configures a context window compression mechanism. + +If included, the server will automatically reduce the size of the context when it exceeds the configured length. + +`inputAudioTranscription` +`object (AudioTranscriptionConfig)` +`AudioTranscriptionConfig` + +Optional. If set, enables transcription of voice input. The transcription aligns with the input audio language, if configured. + +`outputAudioTranscription` +`object (AudioTranscriptionConfig)` +`AudioTranscriptionConfig` + +Optional. If set, enables transcription of the model's audio output. The transcription aligns with the language code specified for the output audio, if configured. + +`historyConfig` +`object (HistoryConfig)` +`HistoryConfig` + +Optional. Configures the exchange of history between the client and the server. + +| JSON representation | +| --- | +| ``` { "model": string, "generationConfig": { object (GenerationConfig) }, "systemInstruction": { object (Content) }, "tools": [ { object (Tool) } ], "realtimeInputConfig": { object (RealtimeInputConfig) }, "sessionResumption": { object (SessionResumptionConfig) }, "contextWindowCompression": { object (ContextWindowCompressionConfig) }, "inputAudioTranscription": { object (AudioTranscriptionConfig) }, "outputAudioTranscription": { object (AudioTranscriptionConfig) }, "historyConfig": { object (HistoryConfig) } } ``` | + +`GenerationConfig` +`Content` +`Tool` +`RealtimeInputConfig` +`SessionResumptionConfig` +`ContextWindowCompressionConfig` +`AudioTranscriptionConfig` +`AudioTranscriptionConfig` +`HistoryConfig` + +## GenerationConfig + +Configuration options for model generation and outputs. Not all parameters are configurable for every model. + +`stopSequences[]` +`string` + +Optional. The set of character sequences (up to 5) that will stop output generation. If specified, the API will stop at the first appearance of a `stop_sequence`. The stop sequence will not be included as part of the response. + +`stop_sequence` +`responseMimeType` +`string` + +Optional. MIME type of the generated candidate text. Supported MIME types are: `text/plain`: (default) Text output. `application/json`: JSON response in the response candidates. `text/x.enum`: ENUM as a string response in the response candidates. Refer to the [docs](https://ai.google.dev/gemini-api/docs/prompting_with_media#plain_text_formats) for a list of all supported text MIME types. + +`text/plain` +`application/json` +`text/x.enum` +`responseSchema +(deprecated)` +`object (Schema)` +`Schema` + +This item is deprecated! + +Optional. Output schema of the generated candidate text. Schemas must be a subset of the [OpenAPI schema](https://spec.openapis.org/oas/v3.0.3#schema) and can be objects, primitives or arrays. + +If set, a compatible `responseMimeType` must also be set. Compatible MIME types: `application/json`: Schema for JSON response. Refer to the [JSON text generation guide](https://ai.google.dev/gemini-api/docs/json-mode) for more details. + +`responseMimeType` +`application/json` +`_responseJsonSchema +(deprecated)` +`value (Value format)` +`Value` + +This item is deprecated! + +Optional. Output schema of the generated response. This is an alternative to `responseSchema` that accepts [JSON Schema](https://json-schema.org/). + +`responseSchema` + +If set, `responseSchema` must be omitted, but `responseMimeType` is required. + +`responseSchema` +`responseMimeType` + +While the full JSON Schema may be sent, not all features are supported. Specifically, only the following properties are supported: + +`$id` +`$defs` +`$ref` +`$anchor` +`type` +`format` +`title` +`description` +`enum` +`items` +`prefixItems` +`minItems` +`maxItems` +`minimum` +`maximum` +`anyOf` +`oneOf` +`anyOf` +`properties` +`additionalProperties` +`required` + +The non-standard `propertyOrdering` property may also be set. + +`propertyOrdering` + +Cyclic references are unrolled to a limited degree and, as such, may only be used within non-required properties. (Nullable properties are not sufficient.) If `$ref` is set on a sub-schema, no other properties, except for than those starting as a `$`, may be set. + +`$ref` +`$` +`responseJsonSchema` +`value (Value format)` +`Value` + +Optional. An internal detail. Use `responseJsonSchema` rather than this field. + +`responseJsonSchema` +`responseModalities[]` +`enum (Modality)` +`Modality` + +Optional. The requested modalities of the response. Represents the set of modalities that the model can return, and should be expected in the response. This is an exact match to the modalities of the response. + +A model may have multiple combinations of supported modalities. If the requested modalities do not match any of the supported combinations, an error will be returned. + +An empty list is equivalent to requesting only text. + +`candidateCount` +`integer` + +Optional. Number of generated responses to return. If unset, this will default to 1. Please note that this doesn't work for previous generation models (Gemini 1.0 family) + +`maxOutputTokens` +`integer` + +Optional. The maximum number of tokens to include in a response candidate. + +Note: The default value varies by model, see the `Model.output_token_limit` attribute of the `Model` returned from the `getModel` function. + +`Model.output_token_limit` +`Model` +`getModel` +`temperature` +`number` + +Optional. Controls the randomness of the output. + +Note: The default value varies by model, see the `Model.temperature` attribute of the `Model` returned from the `getModel` function. + +`Model.temperature` +`Model` +`getModel` + +Values can range from [0.0, 2.0]. + +`topP` +`number` + +Optional. The maximum cumulative probability of tokens to consider when sampling. + +The model uses combined Top-k and Top-p (nucleus) sampling. + +Tokens are sorted based on their assigned probabilities so that only the most likely tokens are considered. Top-k sampling directly limits the maximum number of tokens to consider, while Nucleus sampling limits the number of tokens based on the cumulative probability. + +Note: The default value varies by `Model` and is specified by the`Model.top_p` attribute returned from the `getModel` function. An empty `topK` attribute indicates that the model doesn't apply top-k sampling and doesn't allow setting `topK` on requests. + +`Model` +`Model.top_p` +`getModel` +`topK` +`topK` +`topK` +`integer` + +Optional. The maximum number of tokens to consider when sampling. + +Gemini models use Top-p (nucleus) sampling or a combination of Top-k and nucleus sampling. Top-k sampling considers the set of `topK` most probable tokens. Models running with nucleus sampling don't allow topK setting. + +`topK` + +Note: The default value varies by `Model` and is specified by the`Model.top_p` attribute returned from the `getModel` function. An empty `topK` attribute indicates that the model doesn't apply top-k sampling and doesn't allow setting `topK` on requests. + +`Model` +`Model.top_p` +`getModel` +`topK` +`topK` +`seed` +`integer` + +Optional. Seed used in decoding. If not set, the request uses a randomly generated seed. + +`presencePenalty` +`number` + +Optional. Presence penalty applied to the next token's logprobs if the token has already been seen in the response. + +This penalty is binary on/off and not dependant on the number of times the token is used (after the first). Use `frequencyPenalty` for a penalty that increases with each use. + +`frequencyPenalty` + +A positive penalty will discourage the use of tokens that have already been used in the response, increasing the vocabulary. + +A negative penalty will encourage the use of tokens that have already been used in the response, decreasing the vocabulary. + +`frequencyPenalty` +`number` + +Optional. Frequency penalty applied to the next token's logprobs, multiplied by the number of times each token has been seen in the respponse so far. + +A positive penalty will discourage the use of tokens that have already been used, proportional to the number of times the token has been used: The more a token is used, the more difficult it is for the model to use that token again increasing the vocabulary of responses. + +Caution: A *negative* penalty will encourage the model to reuse tokens proportional to the number of times the token has been used. Small negative values will reduce the vocabulary of a response. Larger negative values will cause the model to start repeating a common token until it hits the `maxOutputTokens` limit. + +`maxOutputTokens` +`responseLogprobs` +`boolean` + +Optional. If true, export the logprobs results in response. + +`logprobs` +`integer` + +Optional. Only valid if `responseLogprobs=True`. This sets the number of top logprobs, including the chosen candidate, to return at each decoding step in the `Candidate.logprobs_result`. The number must be in the range of [0, 20]. + +`responseLogprobs=True` +`Candidate.logprobs_result` +`enableEnhancedCivicAnswers` +`boolean` + +Optional. Enables enhanced civic answers. It may not be available for all models. + +`speechConfig` +`object (SpeechConfig)` +`SpeechConfig` + +Optional. The speech generation config. + +`thinkingConfig` +`object (ThinkingConfig)` +`ThinkingConfig` + +Optional. Config for thinking features. An error will be returned if this field is set for models that don't support thinking. + +`imageConfig` +`object (ImageConfig)` +`ImageConfig` + +Optional. Config for image generation. An error will be returned if this field is set for models that don't support these config options. + +`mediaResolution` +`enum (MediaResolution)` +`MediaResolution` + +Optional. If specified, the media resolution specified will be used. + +`enableAffectiveDialog` +`boolean` + +Optional. If enabled, the model will detect emotions and adapt its responses accordingly. + +`responseFormat` +`object (ResponseFormatConfig)` +`ResponseFormatConfig` + +Optional. Configuration for the response output format. Allows specifying output configuration per modality (text, audio, image) in a flat structure. + +`translationConfig` +`object (TranslationConfig)` +`TranslationConfig` + +Optional. Config for translation. + +`audioTranscriptionConfig` +`object (AudioTranscriptionConfig)` +`AudioTranscriptionConfig` + +Optional. Config for audio transcription (speech recognition). + +| JSON representation | +| --- | +| ``` { "stopSequences": [ string ], "responseMimeType": string, "responseSchema": { object (Schema) }, "_responseJsonSchema": value, "responseJsonSchema": value, "responseModalities": [ enum (Modality) ], "candidateCount": integer, "maxOutputTokens": integer, "temperature": number, "topP": number, "topK": integer, "seed": integer, "presencePenalty": number, "frequencyPenalty": number, "responseLogprobs": boolean, "logprobs": integer, "enableEnhancedCivicAnswers": boolean, "speechConfig": { object (SpeechConfig) }, "thinkingConfig": { object (ThinkingConfig) }, "imageConfig": { object (ImageConfig) }, "mediaResolution": enum (MediaResolution), "enableAffectiveDialog": boolean, "responseFormat": { object (ResponseFormatConfig) }, "translationConfig": { object (TranslationConfig) }, "audioTranscriptionConfig": { object (AudioTranscriptionConfig) } } ``` | + +`Schema` +`Modality` +`SpeechConfig` +`ThinkingConfig` +`ImageConfig` +`MediaResolution` +`ResponseFormatConfig` +`TranslationConfig` +`AudioTranscriptionConfig` + +## Modality + +Supported modalities of the response. + +| Enums | | +| --- | --- | +| `MODALITY_UNSPECIFIED` | Default value. | +| `TEXT` | Indicates the model should return text. | +| `IMAGE` | Indicates the model should return images. | +| `AUDIO` | Indicates the model should return audio. | + +`MODALITY_UNSPECIFIED` +`TEXT` +`IMAGE` +`AUDIO` + +## SpeechConfig + +Config for speech generation and transcription. + +`voiceConfig` +`object (VoiceConfig)` +`VoiceConfig` + +The configuration in case of single-voice output. + +`multiSpeakerVoiceConfig` +`object (MultiSpeakerVoiceConfig)` +`MultiSpeakerVoiceConfig` + +Optional. The configuration for the multi-speaker setup. It is mutually exclusive with the voiceConfig field. + +`languageCode` +`string` + +Optional. The IETF [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) language code that the user configured the app to use. Used for speech recognition and synthesis. + +Valid values are: `de-DE`, `en-AU`, `en-GB`, `en-IN`, `en-US`, `es-US`, `fr-FR`, `hi-IN`, `pt-BR`, `ar-XA`, `es-ES`, `fr-CA`, `id-ID`, `it-IT`, `ja-JP`, `tr-TR`, `vi-VN`, `bn-IN`, `gu-IN`, `kn-IN`, `ml-IN`, `mr-IN`, `ta-IN`, `te-IN`, `nl-NL`, `ko-KR`, `cmn-CN`, `pl-PL`, `ru-RU`, and `th-TH`. + +`de-DE` +`en-AU` +`en-GB` +`en-IN` +`en-US` +`es-US` +`fr-FR` +`hi-IN` +`pt-BR` +`ar-XA` +`es-ES` +`fr-CA` +`id-ID` +`it-IT` +`ja-JP` +`tr-TR` +`vi-VN` +`bn-IN` +`gu-IN` +`kn-IN` +`ml-IN` +`mr-IN` +`ta-IN` +`te-IN` +`nl-NL` +`ko-KR` +`cmn-CN` +`pl-PL` +`ru-RU` +`th-TH` + +| JSON representation | +| --- | +| ``` { "voiceConfig": { object (VoiceConfig) }, "multiSpeakerVoiceConfig": { object (MultiSpeakerVoiceConfig) }, "languageCode": string } ``` | + +`VoiceConfig` +`MultiSpeakerVoiceConfig` + +## VoiceConfig + +The configuration for the voice to use. + +`voice_config` +`Union type` +`voice_config` +`prebuiltVoiceConfig` +`object (PrebuiltVoiceConfig)` +`PrebuiltVoiceConfig` + +The configuration for the prebuilt voice to use. + +| JSON representation | +| --- | +| ``` { // voice_config "prebuiltVoiceConfig": { object (PrebuiltVoiceConfig) } // Union type } ``` | + +`PrebuiltVoiceConfig` + +## PrebuiltVoiceConfig + +The configuration for the prebuilt speaker to use. + +`voiceName` +`string` + +The name of the preset voice to use. + +| JSON representation | +| --- | +| ``` { "voiceName": string } ``` | + +## MultiSpeakerVoiceConfig + +The configuration for the multi-speaker setup. + +`speakerVoiceConfigs[]` +`object (SpeakerVoiceConfig)` +`SpeakerVoiceConfig` + +Required. All the enabled speaker voices. + +| JSON representation | +| --- | +| ``` { "speakerVoiceConfigs": [ { object (SpeakerVoiceConfig) } ] } ``` | + +`SpeakerVoiceConfig` + +## SpeakerVoiceConfig + +The configuration for a single speaker in a multi speaker setup. + +`speaker` +`string` + +Required. The name of the speaker to use. Should be the same as in the prompt. + +`voiceConfig` +`object (VoiceConfig)` +`VoiceConfig` + +Required. The configuration for the voice to use. + +| JSON representation | +| --- | +| ``` { "speaker": string, "voiceConfig": { object (VoiceConfig) } } ``` | + +`VoiceConfig` + +## ThinkingConfig + +Config for thinking features. + +`includeThoughts` +`boolean` + +Indicates whether to include thoughts in the response. If true, thoughts are returned only when available. + +`thinkingBudget` +`integer` + +The number of thoughts tokens that the model should generate. + +`thinkingLevel` +`enum (ThinkingLevel)` +`ThinkingLevel` + +Optional. Controls the maximum depth of the model's internal reasoning process before it produces a response. The default value is model-dependent. Refer to the [Thinking levels guide](https://ai.google.dev/gemini-api/docs/thinking#thinking-levels) for more details. Recommended for Gemini 3 or later models. Use with earlier models results in an error. + +| JSON representation | +| --- | +| ``` { "includeThoughts": boolean, "thinkingBudget": integer, "thinkingLevel": enum (ThinkingLevel) } ``` | + +`ThinkingLevel` + +## ThinkingLevel + +Allow user to specify how much to think using enum instead of integer budget. + +| Enums | | +| --- | --- | +| `THINKING_LEVEL_UNSPECIFIED` | Default value. | +| `MINIMAL` | Little to no thinking. | +| `LOW` | Low thinking level. | +| `MEDIUM` | Medium thinking level. | +| `HIGH` | High thinking level. | + +`THINKING_LEVEL_UNSPECIFIED` +`MINIMAL` +`LOW` +`MEDIUM` +`HIGH` + +## ImageConfig + +Config for image generation features. + +`aspectRatio` +`string` + +Optional. The aspect ratio of the image to generate. Supported aspect ratios: `1:1`, `1:4`, `4:1`, `1:8`, `8:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, or `21:9`. + +`1:1` +`1:4` +`4:1` +`1:8` +`8:1` +`2:3` +`3:2` +`3:4` +`4:3` +`4:5` +`5:4` +`9:16` +`16:9` +`21:9` + +If not specified, the model will choose a default aspect ratio based on any reference images provided. + +`imageSize` +`string` + +Optional. Specifies the size of generated images. Supported values are `512`, `1K`, `2K`, `4K`. If not specified, the model will use default value `1K`. + +`512` +`1K` +`2K` +`4K` +`1K` + +| JSON representation | +| --- | +| ``` { "aspectRatio": string, "imageSize": string } ``` | + +## MediaResolution + +Media resolution for the input media. + +| Enums | | +| --- | --- | +| `MEDIA_RESOLUTION_UNSPECIFIED` | Media resolution has not been set. | +| `MEDIA_RESOLUTION_LOW` | Media resolution set to low (64 tokens). | +| `MEDIA_RESOLUTION_MEDIUM` | Media resolution set to medium (256 tokens). | +| `MEDIA_RESOLUTION_HIGH` | Media resolution set to high (zoomed reframing with 256 tokens). | + +`MEDIA_RESOLUTION_UNSPECIFIED` +`MEDIA_RESOLUTION_LOW` +`MEDIA_RESOLUTION_MEDIUM` +`MEDIA_RESOLUTION_HIGH` + +## ResponseFormatConfig + +Configuration for the response output format. This is a flat object where each optional sub-field configures a specific output modality. + +`text` +`object (TextResponseFormat)` +`TextResponseFormat` + +Optional. Text output format configuration. + +`audio` +`object (AudioResponseFormat)` +`AudioResponseFormat` + +Optional. Audio output format configuration. + +`image` +`object (ImageResponseFormat)` +`ImageResponseFormat` + +Optional. Image output format configuration. + +| JSON representation | +| --- | +| ``` { "text": { object (TextResponseFormat) }, "audio": { object (AudioResponseFormat) }, "image": { object (ImageResponseFormat) } } ``` | + +`TextResponseFormat` +`AudioResponseFormat` +`ImageResponseFormat` + +## TextResponseFormat + +Configuration for text output format. + +`mimeType` +`enum (MimeType)` +`MimeType` + +Optional. The MIME type of the text output. + +`schema` +`value (Value format)` +`Value` + +Optional. The JSON schema that the output should conform to. Only applicable when mimeType is APPLICATION\_JSON. + +| JSON representation | +| --- | +| ``` { "mimeType": enum (MimeType), "schema": value } ``` | + +`MimeType` + +## MimeType + +Supported MIME types for text output. + +| Enums | | +| --- | --- | +| `MIME_TYPE_UNSPECIFIED` | Default value. This value is unused. | +| `APPLICATION_JSON` | JSON output format. | +| `TEXT_PLAIN` | Plain text output format. | + +`MIME_TYPE_UNSPECIFIED` +`APPLICATION_JSON` +`TEXT_PLAIN` + +## AudioResponseFormat + +Configuration for audio output format. + +`mimeType` +`enum (MimeType)` +`MimeType` + +Optional. The MIME type of the audio output. + +`delivery` +`enum (Delivery)` +`Delivery` + +Optional. The delivery mode for the audio output. + +`sampleRate` +`integer` + +Optional. Sample rate in Hz. + +`bitRate` +`integer` + +Optional. Bit rate in bits per second (bps). Only applicable for compressed formats (MP3, Opus). + +| JSON representation | +| --- | +| ``` { "mimeType": enum (MimeType), "delivery": enum (Delivery), "sampleRate": integer, "bitRate": integer } ``` | + +`MimeType` +`Delivery` + +## MimeType + +Supported MIME types for audio output. + +| Enums | | +| --- | --- | +| `MIME_TYPE_UNSPECIFIED` | Default value. This value is unused. | +| `AUDIO_MP3` | MP3 audio format. | +| `AUDIO_OGG_OPUS` | OGG Opus audio format. | +| `AUDIO_L16` | Raw PCM (L16) audio format. | +| `AUDIO_WAV` | WAV audio format. | +| `AUDIO_ALAW` | A-law audio format. | +| `AUDIO_MULAW` | Mu-law audio format. | + +`MIME_TYPE_UNSPECIFIED` +`AUDIO_MP3` +`AUDIO_OGG_OPUS` +`AUDIO_L16` +`AUDIO_WAV` +`AUDIO_ALAW` +`AUDIO_MULAW` + +## Delivery + +Delivery mode for audio output. + +| Enums | | +| --- | --- | +| `DELIVERY_UNSPECIFIED` | Default value. This value is unused. | +| `INLINE` | Audio data is returned inline in the response. | +| `URI` | Audio data is returned as a URI. | + +`DELIVERY_UNSPECIFIED` +`INLINE` +`URI` + +## ImageResponseFormat + +Configuration for image output format. + +`mimeType` +`enum (MimeType)` +`MimeType` + +Optional. The MIME type of the image output. + +`delivery` +`enum (Delivery)` +`Delivery` + +Optional. The delivery mode for the image output. + +`aspectRatio` +`enum (AspectRatio)` +`AspectRatio` + +Optional. The aspect ratio for the image output. + +`imageSize` +`enum (ImageSize)` +`ImageSize` + +Optional. The size of the image output. + +| JSON representation | +| --- | +| ``` { "mimeType": enum (MimeType), "delivery": enum (Delivery), "aspectRatio": enum (AspectRatio), "imageSize": enum (ImageSize) } ``` | + +`MimeType` +`Delivery` +`AspectRatio` +`ImageSize` + +## MimeType + +Supported MIME types for image output. + +| Enums | | +| --- | --- | +| `MIME_TYPE_UNSPECIFIED` | Default value. This value is unused. | +| `IMAGE_JPEG` | JPEG image format. | + +`MIME_TYPE_UNSPECIFIED` +`IMAGE_JPEG` + +## Delivery + +Delivery mode for image output. + +| Enums | | +| --- | --- | +| `DELIVERY_UNSPECIFIED` | Default value. This value is unused. | +| `INLINE` | Image data is returned inline in the response. | +| `URI` | Image data is returned as a URI. | + +`DELIVERY_UNSPECIFIED` +`INLINE` +`URI` + +## AspectRatio + +Supported aspect ratios for image output. + +| Enums | | +| --- | --- | +| `ASPECT_RATIO_UNSPECIFIED` | Default value. This value is unused. | +| `ASPECT_RATIO_ONE_BY_ONE` | 1:1 aspect ratio. | +| `ASPECT_RATIO_TWO_BY_THREE` | 2:3 aspect ratio. | +| `ASPECT_RATIO_THREE_BY_TWO` | 3:2 aspect ratio. | +| `ASPECT_RATIO_THREE_BY_FOUR` | 3:4 aspect ratio. | +| `ASPECT_RATIO_FOUR_BY_THREE` | 4:3 aspect ratio. | +| `ASPECT_RATIO_FOUR_BY_FIVE` | 4:5 aspect ratio. | +| `ASPECT_RATIO_FIVE_BY_FOUR` | 5:4 aspect ratio. | +| `ASPECT_RATIO_NINE_BY_SIXTEEN` | 9:16 aspect ratio. | +| `ASPECT_RATIO_SIXTEEN_BY_NINE` | 16:9 aspect ratio. | +| `ASPECT_RATIO_TWENTY_ONE_BY_NINE` | 21:9 aspect ratio. | +| `ASPECT_RATIO_ONE_BY_EIGHT` | 1:8 aspect ratio. | +| `ASPECT_RATIO_EIGHT_BY_ONE` | 8:1 aspect ratio. | +| `ASPECT_RATIO_ONE_BY_FOUR` | 1:4 aspect ratio. | +| `ASPECT_RATIO_FOUR_BY_ONE` | 4:1 aspect ratio. | + +`ASPECT_RATIO_UNSPECIFIED` +`ASPECT_RATIO_ONE_BY_ONE` +`ASPECT_RATIO_TWO_BY_THREE` +`ASPECT_RATIO_THREE_BY_TWO` +`ASPECT_RATIO_THREE_BY_FOUR` +`ASPECT_RATIO_FOUR_BY_THREE` +`ASPECT_RATIO_FOUR_BY_FIVE` +`ASPECT_RATIO_FIVE_BY_FOUR` +`ASPECT_RATIO_NINE_BY_SIXTEEN` +`ASPECT_RATIO_SIXTEEN_BY_NINE` +`ASPECT_RATIO_TWENTY_ONE_BY_NINE` +`ASPECT_RATIO_ONE_BY_EIGHT` +`ASPECT_RATIO_EIGHT_BY_ONE` +`ASPECT_RATIO_ONE_BY_FOUR` +`ASPECT_RATIO_FOUR_BY_ONE` + +## ImageSize + +Supported image sizes for image output. + +| Enums | | +| --- | --- | +| `IMAGE_SIZE_UNSPECIFIED` | Default value. This value is unused. | +| `IMAGE_SIZE_FIVE_TWELVE` | 512px image size. | +| `IMAGE_SIZE_ONE_K` | 1K image size. | +| `IMAGE_SIZE_TWO_K` | 2K image size. | +| `IMAGE_SIZE_FOUR_K` | 4K image size. | + +`IMAGE_SIZE_UNSPECIFIED` +`IMAGE_SIZE_FIVE_TWELVE` +`IMAGE_SIZE_ONE_K` +`IMAGE_SIZE_TWO_K` +`IMAGE_SIZE_FOUR_K` + +## TranslationConfig + +Config for translation features. + +`targetLanguageCode` +`string` + +Required. The target language for translation. Supported values are BCP-47 language codes (e.g. "en", "es", "fr"). + +`echoTargetLanguage` +`boolean` + +Optional. If true, the model will generate audio when the target language is spoken, essentially it will parrot the input. If false, we will not produce audio for the target language. + +| JSON representation | +| --- | +| ``` { "targetLanguageCode": string, "echoTargetLanguage": boolean } ``` | + +## AudioTranscriptionConfig + +The audio transcription configuration. + +`languageCodes[]` +`string` + +Optional. BCP-47 language codes providing hints about the languages present in the audio. If omitted or empty, defaults to automatic language detection. + +`adaptationPhrases[] +(deprecated)` +`string` + +This item is deprecated! + +Optional. A list of phrases used for speech adaptation, which biases the ASR model to improve recognition of these specific terms. + +`customVocabulary[]` +`string` + +Optional. A list of custom vocabulary phrases to bias the speech recognition model toward recognizing specific terms (product names, proper nouns, jargon). + +`wordTimestamp` +`boolean` + +Optional. Configures word-level timestamp generation. + +`diarization` +`boolean` + +Optional. Configures speaker diarization. + +`language_config` +`Union type` +`language_codes` +`language_config` +`languageAuto +(deprecated)` +`object (LanguageAuto)` +`LanguageAuto` + +This item is deprecated! + +Optional. The model will detect the language automatically. + +`languageHints +(deprecated)` +`object (LanguageHints)` +`LanguageHints` + +This item is deprecated! + +Optional. Specifies one or more languages in the audio. + +| JSON representation | +| --- | +| ``` { "languageCodes": [ string ], "adaptationPhrases": [ string ], "customVocabulary": [ string ], "wordTimestamp": boolean, "diarization": boolean, // language_config "languageAuto": { object (LanguageAuto) }, "languageHints": { object (LanguageHints) } // Union type } ``` | + +`LanguageAuto` +`LanguageHints` + +## LanguageAuto + +This type has no fields. + +This item is deprecated! + +Indicates the language of the audio should be automatically detected. + +## LanguageHints + +This item is deprecated! + +Provides hints to the model about possible languages present in the audio. + +`languageCodes[] +(deprecated)` +`string` + +This item is deprecated! + +Required. BCP-47 language codes. + +| JSON representation | +| --- | +| ``` { "languageCodes": [ string ] } ``` | + +## RealtimeInputConfig + +Configures the realtime input behavior in `BidiGenerateContent`. + +`BidiGenerateContent` +`automaticActivityDetection` +`object (AutomaticActivityDetection)` +`AutomaticActivityDetection` + +Optional. If not set, automatic activity detection is enabled by default. If automatic voice detection is disabled, the client must send activity signals. + +`activityHandling` +`enum (ActivityHandling)` +`ActivityHandling` + +Optional. Defines what effect activity has. + +`turnCoverage` +`enum (TurnCoverage)` +`TurnCoverage` + +Optional. Defines which input is included in the user's turn. + +| JSON representation | +| --- | +| ``` { "automaticActivityDetection": { object (AutomaticActivityDetection) }, "activityHandling": enum (ActivityHandling), "turnCoverage": enum (TurnCoverage) } ``` | + +`AutomaticActivityDetection` +`ActivityHandling` +`TurnCoverage` + +## AutomaticActivityDetection + +Configures automatic detection of activity. + +`disabled` +`boolean` + +Optional. If enabled (the default), detected voice and text input count as activity. If disabled, the client must send activity signals. + +`startOfSpeechSensitivity` +`enum (StartSensitivity)` +`StartSensitivity` + +Optional. Determines how likely speech is to be detected. + +`prefixPaddingMs` +`integer` + +Optional. The required duration of detected speech before start-of-speech is committed. The lower this value, the more sensitive the start-of-speech detection is and shorter speech can be recognized. However, this also increases the probability of false positives. + +`endOfSpeechSensitivity` +`enum (EndSensitivity)` +`EndSensitivity` + +Optional. Determines how likely detected speech is ended. + +`silenceDurationMs` +`integer` + +Optional. The required duration of detected non-speech (e.g. silence) before end-of-speech is committed. The larger this value, the longer speech gaps can be without interrupting the user's activity but this will increase the model's latency. + +| JSON representation | +| --- | +| ``` { "disabled": boolean, "startOfSpeechSensitivity": enum (StartSensitivity), "prefixPaddingMs": integer, "endOfSpeechSensitivity": enum (EndSensitivity), "silenceDurationMs": integer } ``` | + +`StartSensitivity` +`EndSensitivity` + +## StartSensitivity + +Determines how start of speech is detected. + +| Enums | | +| --- | --- | +| `START_SENSITIVITY_UNSPECIFIED` | The default is START\_SENSITIVITY\_HIGH. | +| `START_SENSITIVITY_HIGH` | Automatic detection will detect the start of speech more often. | +| `START_SENSITIVITY_LOW` | Automatic detection will detect the start of speech less often. | + +`START_SENSITIVITY_UNSPECIFIED` +`START_SENSITIVITY_HIGH` +`START_SENSITIVITY_LOW` + +## EndSensitivity + +Determines how end of speech is detected. + +| Enums | | +| --- | --- | +| `END_SENSITIVITY_UNSPECIFIED` | The default is END\_SENSITIVITY\_HIGH. | +| `END_SENSITIVITY_HIGH` | Automatic detection ends speech more often. | +| `END_SENSITIVITY_LOW` | Automatic detection ends speech less often. | + +`END_SENSITIVITY_UNSPECIFIED` +`END_SENSITIVITY_HIGH` +`END_SENSITIVITY_LOW` + +## ActivityHandling + +The different ways of handling user activity. + +| Enums | | +| --- | --- | +| `ACTIVITY_HANDLING_UNSPECIFIED` | If unspecified, the default behavior is `START_OF_ACTIVITY_INTERRUPTS`. | +| `START_OF_ACTIVITY_INTERRUPTS` | If true, start of activity will interrupt the model's response (also called "barge in"). The model's current response will be cut-off in the moment of the interruption. This is the default behavior. | +| `NO_INTERRUPTION` | The model's response will not be interrupted. | + +`ACTIVITY_HANDLING_UNSPECIFIED` +`START_OF_ACTIVITY_INTERRUPTS` +`START_OF_ACTIVITY_INTERRUPTS` +`NO_INTERRUPTION` + +## TurnCoverage + +Options about which input is included in the user's turn. + +| Enums | | +| --- | --- | +| `TURN_COVERAGE_UNSPECIFIED` | If unspecified, a default behavior is selected based on the model. E.g., for Gemini 2.5, the default is `TURN_INCLUDES_ONLY_ACTIVITY`, while for Gemini 3.1 and onwards, it's `TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO`. | +| `TURN_INCLUDES_ONLY_ACTIVITY` | Includes activity since the last turn, excluding inactivity (e.g. silence on the audio stream). | +| `TURN_INCLUDES_ALL_INPUT` | Includes all realtime input since the last turn, including inactivity (e.g. silence on the audio stream). | +| `TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO` | Includes audio activity and all video since the last turn. With automatic activity detection, audio activity means speech and excludes silence. | + +`TURN_COVERAGE_UNSPECIFIED` +`TURN_INCLUDES_ONLY_ACTIVITY` +`TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO` +`TURN_INCLUDES_ONLY_ACTIVITY` +`TURN_INCLUDES_ALL_INPUT` +`TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO` + +## SessionResumptionConfig + +Session resumption configuration. + +This message is included in the session configuration as `BidiGenerateContentSetup.session_resumption`. If configured, the server will send `SessionResumptionUpdate` messages. + +`BidiGenerateContentSetup.session_resumption` +`SessionResumptionUpdate` +`handle` +`string` + +The handle of a previous session. If not present then a new session is created. + +Session handles come from `SessionResumptionUpdate.token` values in previous connections. + +`SessionResumptionUpdate.token` + +| JSON representation | +| --- | +| ``` { "handle": string } ``` | + +## ContextWindowCompressionConfig + +Enables context window compression — a mechanism for managing the model's context window so that it does not exceed a given length. + +`compression_mechanism` +`Union type` +`compression_mechanism` +`slidingWindow` +`object (SlidingWindow)` +`SlidingWindow` + +A sliding-window mechanism. + +`triggerTokens` +`string (int64 format)` + +The number of tokens (before running a turn) required to trigger a context window compression. + +This can be used to balance quality against latency as shorter context windows may result in faster model responses. However, any compression operation will cause a temporary latency increase, so they should not be triggered frequently. + +If not set, the default is 80% of the model's context window limit. This leaves 20% for the next user request/model response. + +| JSON representation | +| --- | +| ``` { // compression_mechanism "slidingWindow": { object (SlidingWindow) } // Union type "triggerTokens": string } ``` | + +`SlidingWindow` + +## SlidingWindow + +The SlidingWindow method operates by discarding content at the beginning of the context window. The resulting context will always begin at the start of a USER role turn. System instructions and any `BidiGenerateContentSetup.prefix_turns` will always remain at the beginning of the result. + +`BidiGenerateContentSetup.prefix_turns` +`targetTokens` +`string (int64 format)` + +The target number of tokens to keep. The default value is triggerTokens/2. + +Discarding parts of the context window causes a temporary latency increase so this value should be calibrated to avoid frequent compression operations. + +| JSON representation | +| --- | +| ``` { "targetTokens": string } ``` | + +## HistoryConfig + +History configuration. + +This message is included in the session configuration as `BidiGenerateContentSetup.history_config`. Configures the exchange of history messages. + +`BidiGenerateContentSetup.history_config` +`initialHistoryInClientContent` +`boolean` + +Optional. If true, after sending `setupComplete`, the server will wait and at first process `clientContent` messages until `turnComplete` is `true`. This initial history will not trigger a model call and may end with role `MODEL`. After `turnComplete` is `true`, the client can start the realtime conversation via `realtimeInput`. + +`setupComplete` +`clientContent` +`turnComplete` +`true` +`MODEL` +`turnComplete` +`true` +`realtimeInput` + +| JSON representation | +| --- | +| ``` { "initialHistoryInClientContent": boolean } ``` | + +## Method: auth\_tokens.create + +Creates a token that can be used to constrain the behavior of a BidiGenerateContent session. + +### Endpoint + +`https://generativelanguage.googleapis.com/v1beta/auth_tokens` + +### Request body + +The request body contains an instance of `AuthToken`. + +`AuthToken` +`expireTime` +`string (Timestamp format)` +`Timestamp` + +Optional. Input only. Immutable. An optional time after which, when using the resulting token, messages in BidiGenerateContent sessions will be rejected. (Gemini may preemptively close the session after this time.) + +If not set then this defaults to 30 minutes in the future. If set, this value must be less than 20 hours in the future. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` +`newSessionExpireTime` +`string (Timestamp format)` +`Timestamp` + +Optional. Input only. Immutable. The time after which new Live API sessions using the token resulting from this request will be rejected. + +If not set this defaults to 60 seconds in the future. If set, this value must be less than 20 hours in the future. + +Uses RFC 3339, where generated output will always be Z-normalized and use 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. Examples: `"2014-10-02T15:01:23Z"`, `"2014-10-02T15:01:23.045123456Z"` or `"2014-10-02T15:01:23+05:30"`. + +`"2014-10-02T15:01:23Z"` +`"2014-10-02T15:01:23.045123456Z"` +`"2014-10-02T15:01:23+05:30"` +`fieldMask` +`string (FieldMask format)` +`FieldMask` + +Optional. Input only. Immutable. If fieldMask is empty, and `bidiGenerateContentSetup` is not present, then the effective `BidiGenerateContentSetup` message is taken from the Live API connection. + +`bidiGenerateContentSetup` +`BidiGenerateContentSetup` + +If fieldMask is empty, and `bidiGenerateContentSetup` *is* present, then the effective `BidiGenerateContentSetup` message is taken entirely from `bidiGenerateContentSetup` in this request. The setup message from the Live API connection is ignored. + +`bidiGenerateContentSetup` +`BidiGenerateContentSetup` +`bidiGenerateContentSetup` + +If fieldMask is not empty, then the corresponding fields from `bidiGenerateContentSetup` will overwrite the fields from the setup message in the Live API connection. + +`bidiGenerateContentSetup` + +This is a comma-separated list of fully qualified names of fields. Example: `"user.displayName,photo"`. + +`"user.displayName,photo"` +`config` +`Union type` +`config` +`bidiGenerateContentSetup` +`object (BidiGenerateContentSetup)` +`BidiGenerateContentSetup` + +Optional. Input only. Immutable. Configuration specific to `BidiGenerateContent`. + +`BidiGenerateContent` +`uses` +`integer` + +Optional. Input only. Immutable. The number of times the token can be used. If this value is zero then no limit is applied. Resuming a Live API session does not count as a use. If unspecified, the default is 1. + +### Response body + +If successful, the response body contains a newly created instance of `AuthToken`. + +`AuthToken` + +Except as otherwise noted, the content of this page is licensed under the [Creative Commons Attribution 4.0 License](https://creativecommons.org/licenses/by/4.0/), and code samples are licensed under the [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0). For details, see the [Google Developers Site Policies](https://developers.google.com/site-policies). Java is a registered trademark of Oracle and/or its affiliates. + +Last updated 2026-08-17 UTC. \ No newline at end of file diff --git a/cache_research/official_overseas_v2/report.md b/cache_research/official_overseas_v2/report.md new file mode 100644 index 00000000..60e7c791 --- /dev/null +++ b/cache_research/official_overseas_v2/report.md @@ -0,0 +1,422 @@ +# 海外官方 LLM API「缓存命中 token 字段」调研报告(v2 重试版) + +> 调研时间:2026-08-29 +> 调研人:子智能体 #4 +> 范围:海外**官方 API**(OpenAI / Anthropic / Google Gemini / xAI Grok / Mistral AI;AWS Bedrock 与 Azure OpenAI 仅简述透传方式) +> 方法:以官方文档为准(platform.openai.com / docs.anthropic.com / platform.claude.com / ai.google.dev / docs.x.ai / docs.mistral.ai / learn.microsoft.com / aws.amazon.com 官方博客),社区与第三方内容仅作辅助并标注来源等级。 +> 说明:官方文档随时间变化(2026 年的文档已覆盖 GPT-5.x、Claude Opus/Sonnet 5 等新模型),本报告同时保留「历史经典行为」(如 OpenAI 1024 阈值、Anthropic 1024/2048)与「文档当前状态」,供实验对照。 + +--- + +## 1. 总览对照表 + +| 提供商 | 缓存类型 / 启用方式 | 命中字段 JSON 路径(非流式) | 写入(创建)字段 | 自动 / 显式 | 最低门槛 | 官方文档链接 | +|---|---|---|---|---|---|---| +| **OpenAI** Chat Completions | prompt caching(KV cache) | `usage.prompt_tokens_details.cached_tokens` | `usage.prompt_tokens_details.cache_write_tokens`(GPT-5.6+ 上报;老模型无写入字段) | 自动(implicit);GPT-5.6+ 可选显式 breakpoint | 历史 1024 tokens(128 递增);当前文档:GPT-5.6+ = 1024,更早模型 = 2048 | https://platform.openai.com/docs/guides/prompt-caching | +| **OpenAI** Responses API | prompt caching | `usage.input_tokens_details.cached_tokens` | `usage.input_tokens_details.cache_write_tokens` | 自动 / 显式(`prompt_cache_options.mode` + `prompt_cache_breakpoint`) | 同上 | https://platform.openai.com/docs/guides/prompt-caching | +| **Anthropic** Claude Messages API | prompt caching(前缀缓存) | `usage.cache_read_input_tokens` | `usage.cache_creation_input_tokens`(另有细分对象 `usage.cache_creation.ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens`) | 显式:块级 `cache_control`(`{"type":"ephemeral"}`);也提供顶层 `cache_control` 自动断点 | 因模型而异:512 / 1024 / 2048 / 4096 均有(详见 §3 表) | https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching(即 platform.claude.com/docs/en/build-with-claude/prompt-caching) | +| **Google Gemini** GLM(Generative Language API) | 隐式缓存 + 显式 Context Caching(`cachedContents` 资源) | `usageMetadata.cachedContentTokenCount`(SDK snake_case:`usage_metadata.cached_content_token_count`;另有带 modality 细分 `cacheTokensDetails`) | 无「usage 内写入字段」;显式缓存通过 `cachedContents.create` 创建资源(TTL 计费) | 隐式:2.5 及更新模型自动;显式:须先创建 CachedContent 再传 `cachedContent` | 隐式:Gemini 2.5 = 2048 tokens,Gemini 3.x = 4096 tokens(历史:2.5 Flash 曾 1024 / 2.5 Pro 曾 2048);显式:缓存资源 ≥1 分钟 TTL,按 token·时长计费 | https://ai.google.dev/gemini-api/docs/generate-content/caching | +| **xAI** Grok | prompt caching(messages 前缀缓存) | Chat Completions:`usage.prompt_tokens_details.cached_tokens`;Responses API:`usage.input_tokens_details.cached_tokens` | 无独立字段(官方仅暴露 `cached_tokens`) | 自动;建议设 `x-grok-conv-id` / `prompt_cache_key` 提升命中率 | 官方文档未公布固定 token 门槛(按消息前缀整段匹配) | https://docs.x.ai/developers/advanced-api-usage/prompt-caching | +| **Mistral AI** | prompt caching(前缀缓存,OpenAI 兼容格式) | `usage.prompt_tokens_details.cached_tokens` | 无独立字段(未命中时该字段为 0 或省略) | 显式:须在请求中传 `prompt_cache_key` 提高命中;命中与否由服务端决定 | 缓存块 = 64 tokens;`cached_tokens` 恒为 64 的倍数;<64 token 无命中 | https://docs.mistral.ai/studio/conversations/advanced/prompt-caching | +| **AWS Bedrock** | 透传:Claude 系用 `cachePoint`(system/tools 内);Amazon Nova 自动缓存 | `usage`(原生透传 Anthropic 的 `cacheReadInputTokens`/`cacheCreationInputTokens`;converse 返回 `usage.cacheReadInputTokens` 等;SDK 中为 `usage_metadata`) | `cacheCreationInputTokens` | 显式(cachePoint)/ Nova 自动 | Claude 按模型(同 Anthropic);Nova 最高 20K tokens | https://aws.amazon.com/blogs/machine-learning/effectively-use-prompt-caching-on-amazon-bedrock | +| **Azure OpenAI** | 透传:与 OpenAI 字段一致(prompt caching) | Chat Completions:`usage.prompt_tokens_details.cached_tokens`;Responses API:`usage.input_tokens_details.cached_tokens` | `usage.prompt_tokens_details.cache_write_tokens`(GPT-5.6+) | 自动;GPT-5.6+ 支持 breakpoint / `prompt_cache_key` | 最低 1024 tokens,前 1024 必须完全一致 | https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/prompt-caching | + +**一句话结论**:五家海外官方 API 中,OpenAI / xAI / Mistral / Gemini 用「自动或半自动 + `cached_tokens` 类字段」,Anthropic 用「显式 cache_control + read/write 双字段」。唯一同时提供「命中 + 写入」双向拆分的官方原生字段是 **Anthropic**(`cache_read_input_tokens` / `cache_creation_input_tokens`)与 **OpenAI GPT-5.6+ / Responses API**(`cached_tokens` / `cache_write_tokens`)。 + +--- + +## 2. 各家详细信息 + +### 2.1 OpenAI(Chat Completions API + Responses API) + +**官方文档**:https://platform.openai.com/docs/guides/prompt-caching(2026-08 抓取,官方文档原文;历史公告 https://openai.com/index/api-prompt-caching 作辅助) + +**1) 是否支持 / 自动或显式** +- 支持,**默认自动启用**(implicit caching),无需改代码。 +- GPT-5.6 及更新模型支持**显式缓存断点**(`prompt_cache_options.mode: "explicit"` + 块上 `prompt_cache_breakpoint: {"mode":"explicit"}`)与 `prompt_cache_key`(影响路由、帮助同前缀请求命中同一台机器)。 +- 更早模型仅有隐式缓存,断点由 OpenAI 按模型间隔自动放置。 + +**2) 命中字段完整 JSON 路径** +- Chat Completions API:`usage.prompt_tokens_details.cached_tokens` +- Responses API:`usage.input_tokens_details.cached_tokens` +- 官方文档定价示例(Responses API 用法,摘自官方 docs 原文): + +```json +// Responses API(官方文档 "Request 1 · Response usage") +{ + "usage": { + "input_tokens": 12000, + "input_tokens_details": { + "cached_tokens": 0, + "cache_write_tokens": 12000 + } + } +} +// 第二次请求命中: +{ + "usage": { + "input_tokens": 15000, + "input_tokens_details": { + "cached_tokens": 12000, + "cache_write_tokens": 3000 + } + } +} +``` + +- Chat Completions(经典格式,官方社区/公告示例):`usage.prompt_tokens_details.cached_tokens`: + +```json +{ + "usage": { + "prompt_tokens": 1253, + "completion_tokens": 72, + "total_tokens": 1325, + "prompt_tokens_details": { + "cached_tokens": 1024 + }, + "completion_tokens_details": { + "reasoning_tokens": 0 + } + } +} +``` + +**3) 缓存写入/创建字段** +- 有:Responses API `usage.input_tokens_details.cache_write_tokens`;Chat Completions `usage.prompt_tokens_details.cache_write_tokens`(GPT-5.6+ 上报;更早模型不计写入费、也不上报该字段)。 +- 注:官方 docs 的成本计算示例同时读取 `cached_tokens` 与 `cache_write_tokens` 计算输入成本。 + +**4) 流式响应(stream=true)** +- Chat Completions:usage(含 cached_tokens)只在**最后一个 chunk** 返回,且必须设置 `stream_options: {"include_usage": true}`,否则流式响应不含 usage。 +- Responses API:流式下 usage 在 `response.completed` 事件中携带,字段路径不变。 +- 字段路径在流式与批式下**完全一致**。 + +**5) 最低门槛** +- 历史(2024-10 公告,GPT-4o/o1 时代):**≥1024 tokens** 自动缓存,命中按 **128 tokens 递增**(1024/1152/1280/1408…),缓存通常 5–10 分钟无活动后清除、最长 1 小时。 +- 当前官方文档(2026-08):GPT-5.6 及以后 = **1024 visible tokens**;GPT-5.5 及更早 = **2048 visible tokens**(个别老模型可更短);GPT-5.6 不再按 128 取整(精确到缓存断点),旧模型上报时向下取整到 128 倍数。 + +**6) 计费折扣** +- 历史模型:命中 token 打 5 折(50% off)。 +- 当前:GPT-5.6+ 缓存读 0.1×、缓存写 1.25×(写一次 + 读一次 = 1.35× vs 不缓存 2×);更早模型读价为模型相关折扣、写入不额外计费。 + +**7) 注意事项** +- 缓存匹配的是「完整渲染前缀」:model、tools、parallel_tool_calls、格式参数等任何相关设置变化都可能破坏前缀。 +- 命中不保证 100%(路由溢出、机器未持有缓存)。官方建议用 `prompt_cache_key` 提高路由一致性。 +- 实验时优先用 Responses API 的 `input_tokens_details`,或 Chat Completions 的 `prompt_tokens_details`;两处都要注意老模型字段可能为 null/缺省(`cached_tokens` 为 0 也算明确返回)。 + +### 2.2 Anthropic Claude(Messages API) + +**官方文档**:https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching(与 platform.claude.com/docs/en/build-with-claude/prompt-caching 同源,2026-08 抓取) + +**1) 是否支持 / 自动或显式** +- 支持,**必须显式标记**: + - 显式块级:在 `system` / `tools` / `messages.content` 块上加 `"cache_control": {"type": "ephemeral"}`(可加 `"ttl": "1h"` 延长到 1 小时)。 + - 自动断点:在请求**顶层**加一个 `cache_control`,系统自动把断点放到最后一个可缓存块,并随对话增长前移(2026 新增的 automatic caching 模式)。 +- 断点最多 4 个;缓存前缀顺序 tools → system → messages。 + +**2) 命中字段完整 JSON 路径** +- `usage.cache_read_input_tokens`(本次请求从缓存读取的 token 数) +- `usage.input_tokens`(未命中、实际处理的 token 数) +- 官方文档示例(1 小时 TTL 输出): + +```json +{ + "usage": { + "input_tokens": 2048, + "cache_read_input_tokens": 1800, + "cache_creation_input_tokens": 248, + "output_tokens": 503, + "cache_creation": { + "ephemeral_5m_input_tokens": 148, + "ephemeral_1h_input_tokens": 100 + } + } +} +``` + +**3) 缓存写入/创建字段** +- 有:`usage.cache_creation_input_tokens`(写入缓存的新 token 数);1 小时 TTL 时另有细分对象 `usage.cache_creation.ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens`(二者之和 = cache_creation_input_tokens)。 + +**4) 流式响应(stream=true)** +- usage 在 **`message_start` 事件**的 `message.usage` 中(含 input_tokens / cache_creation_input_tokens / cache_read_input_tokens);`output_tokens` 增量在 `message_delta` 事件。字段路径与批式一致。 +- 官方原文:"Monitor cache performance using these API response fields, within `usage` in the response (or `message_start` event if streaming)。" + +**5) 最低门槛**(当前文档,按模型) +| 模型 | 最低可缓存 token 数 | +|---|---| +| Claude Opus 5 / Fable 5 / Mythos 5 | 512 | +| Claude Mythos Preview / Opus 4.7 | 2,048 | +| Claude Opus 4.6 / 4.5 | 4,096 | +| Claude Opus 4.8 / Sonnet 5 / Sonnet 4.6 / 4.5 / Opus 4.1 / Opus 4 / Sonnet 4 | 1,024 | +| Claude Haiku 4.5 | 4,096 | +| Claude Haiku 3.5 | 2,048 | + +- 历史经典值(claude-3/3.5 时代):Sonnet/Opus = 1024、Haiku = 2048。低于门槛即使打了 cache_control 也不会缓存、不报错,只会在 usage 里两个缓存字段都为 0。 + +**6) 计费折扣** +- 缓存写(5m TTL):1.25× 基础输入价;缓存写(1h TTL):2× 基础输入价;**缓存读/刷新:0.1× 基础输入价**(约 90% 折扣)。 + +**7) 注意事项** +- 命中要求前缀 100% 一致(一个字符差异即 miss);缓存生命周期 5 分钟(1h 可选),从请求开始计时。 +- 思考块(thinking)不能单独打 cache_control,但可作为助手轮内容被缓存;enabling/disabling web search、citations、effort 等设置会失效部分缓存。 +- 可用 `max_tokens: 0` 预热缓存(不产生输出)。 +- 官方提供 cache diagnostics 接口用于排查前缀差异。 + +### 2.3 Google Gemini(Generative Language API / Vertex AI) + +**官方文档**:https://ai.google.dev/gemini-api/docs/generate-content/caching 与 REST 参考 https://ai.google.dev/api/generate-content(2026-08 抓取) + +**1) 是否支持 / 自动或显式** +- 支持两种: + - **隐式缓存**:Gemini 2.5 及更新模型默认自动启用,请求里什么都不用加。 + - **显式 Context Caching**:用 `cachedContents.create` 创建 CachedContent 资源(含 model/contents/systemInstruction/ttl),然后在 generateContent 请求传 `cachedContent: ""` 引用。可用 OpenAI 兼容库时在 `extra_body` 传 `cached_content`。 +- Vertex AI 同样支持(context caching),字段名一致。 + +**2) 命中字段完整 JSON 路径** +- REST:`GenerateContentResponse.usageMetadata.cachedContentTokenCount` +- SDK(snake_case,Python/Node):`response.usage_metadata.cached_content_token_count` +- 细分字段:`usageMetadata.cacheTokensDetails[]`(按 modality 的命中 token 明细)。 +- 官方 REST 参考中 UsageMetadata JSON(节选): + +```json +{ + "promptTokenCount": integer, + "cachedContentTokenCount": integer, + "candidatesTokenCount": integer, + "toolUsePromptTokenCount": integer, + "thoughtsTokenCount": integer, + "totalTokenCount": integer, + "promptTokensDetails": [ { "modality": "...", "tokenCount": integer } ], + "cacheTokensDetails": [ { "modality": "...", "tokenCount": integer } ], + "candidatesTokensDetails": [ { "modality": "...", "tokenCount": integer } ] +} +``` + +- 社区实测示例(Gemini 2.5,来源:discuss.ai.google.dev,等级=辅助):显式缓存命中时 `cached_content_token_count=4115`、`cache_tokens_details=[{modality:'TEXT', token_count:4115}]`。 + +**3) 缓存写入/创建字段** +- usage 内**没有**缓存写入字段;「写入」体现在显式 CachedContent 资源的计费(按 token 数 × 存储时长 TTL 计费),资源元数据中有 `usageMetadata.totalTokenCount`。隐式缓存的写入由 Google 内部处理,不暴露字段。 + +**4) 流式响应(stream=true / streamGenerateContent)** +- `usageMetadata` 在**最后一个 chunk** 返回(REST `streamGenerateContent` 的末帧;SDK 中亦在流结束的响应对象上)。当前官方文档没有为缓存命中另设流式事件,字段路径不变。 +- 社区有多起「流式末尾 usageMetadata 里 cachedContentTokenCount 缺失」的报告(discuss.ai.google.dev,等级=辅助,未 100% 确认为官方 bug),实验时建议同时打印非流式结果对照。 + +**5) 最低门槛** +- 隐式(当前文档表):Gemini 2.5 Flash / 2.5 Pro = **2,048 tokens**;Gemini 3.x(3.1 Pro Preview / 3.5 / 3.6 / 3.7 Flash)= **4,096 tokens**。 +- 历史(Google 官方博客 2025-05):2.5 Flash 曾 1,024、2.5 Pro 曾 2,048——阈值随版本调整,以文档当前值为准。 +- 显式缓存:无 token 下限但资源有 TTL(默认 1h,可 300s 起),按 token×时间计费;≥1 分钟 TTL。 + +**6) 计费折扣** +- 隐式缓存命中:按缓存价计费(Gemini 2.5 起缓存输入约为基础价 10% 档;具体以官方定价页为准)。 +- 显式缓存:命中 token 折扣 90%(2.5+ 模型)/ 75%(2.0 模型),外加缓存存储费($/token·hour)。 + +**7) 注意事项** +- 隐式缓存命中率不受控制、非保证;显式缓存保证计费折扣但要多维护资源生命周期(create/list/update/delete API)。 +- `cachedContentTokenCount` 只统计命中的 token,`promptTokenCount` 仍含全部输入;计算未命中部分 = promptTokenCount − cachedContentTokenCount。 +- 实验时注意 SDK 属性名 snake_case(`cached_content_token_count`)与 REST camelCase(`cachedContentTokenCount`)的差异。 + +### 2.4 xAI Grok + +**官方文档**:https://docs.x.ai/developers/advanced-api-usage/prompt-caching(How it works / Usage & Pricing / Best Practices & FAQ,2026-08 抓取) + +**1) 是否支持 / 自动或显式** +- 支持,**完全自动**(按 messages 数组起始匹配前缀);建议设置 HTTP 头 `x-grok-conv-id`(或 Responses API 的 `prompt_cache_key`)提升命中率。 +- 无 cache_control 之类显式标记。 + +**2) 命中字段完整 JSON 路径** +- Chat Completions API:`usage.prompt_tokens_details.cached_tokens` +- Responses API:`usage.input_tokens_details.cached_tokens` +- 官方示例(Chat Completions): + +```json +{ + "usage": { + "prompt_tokens": 125, + "completion_tokens": 48, + "total_tokens": 173, + "prompt_tokens_details": { + "text_tokens": 125, + "audio_tokens": 0, + "image_tokens": 0, + "cached_tokens": 98 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + } +} +``` + +- 官方示例(Responses API):`usage.input_tokens_details.cached_tokens`(路径同 OpenAI Responses API)。 + +**3) 缓存写入/创建字段** +- 无。官方只暴露 `cached_tokens`(≤0 表示 miss;等于 prompt_tokens 表示整段命中)。 + +**4) 流式响应(stream=true)** +- 官方 FAQ:**流式与非流式均支持缓存**;流式下第一个空 token 对应缓存查找与 prefill 阶段。usage 聚合到最后一个 chunk(沿用 OpenAI 风格,需 `stream_options.include_usage`)。字段路径不变。 + +**5) 最低门槛** +- 官方文档未公布固定 token 门槛;机制按「消息前缀精确匹配」整段生效(示例中 3 条消息被整体缓存)。实验时建议让共享前缀 ≥ 数百 token 并保持多轮对话以观察命中增长。 + +**6) 计费折扣** +- 缓存命中 token 按「cached prompt token 价」计费(低于常规输入价;具体比率见各模型定价页)。 + +**7) 注意事项** +- 命中无保证(内存压力可驱逐缓存、请求可能路由到别的机器);换 `x-grok-conv-id` 可强制 miss,便于对照实验。 +- 典型多轮:turn1 cached=0(建缓存)→ turn2 cached=前 50 → turn3 cached=前 120(官方示例)。 + +### 2.5 Mistral AI + +**官方文档**:https://docs.mistral.ai/studio/conversations/advanced/prompt-caching 与 API 参考 https://docs.mistral.ai/api/endpoint/chat(2026-08 抓取) + +**1) 是否支持 / 自动或显式** +- 支持 prompt caching;**需在请求中显式传 `prompt_cache_key`**(会话/工作流 ID)来提升命中,且请求体必须保留共享前缀(多轮重发完整历史)。命中与否由服务端决定,key 不保证命中。 +- 接口格式 OpenAI 兼容(/v1/chat/completions)。 + +**2) 命中字段完整 JSON 路径** +- `usage.prompt_tokens_details.cached_tokens` +- 官方示例: + +```json +{ + "id": "a4db7c530548494f8ff9986bcd2a7737", + "created": 1773840064, + "model": "mistral-large-latest", + "usage": { + "prompt_tokens": 1013, + "total_tokens": 1043, + "completion_tokens": 30, + "prompt_tokens_details": { + "cached_tokens": 1008 + } + }, + "object": "chat.completion" +} +``` + +- 未命中时 `cached_tokens` 为 0 或字段被省略。 + +**3) 缓存写入/创建字段** +- 无独立写入字段;计费侧「可收费未缓存输入 = prompt_tokens − cached_tokens」。 + +**4) 流式响应(stream=true)** +- 官方文档未单列流式差异;API 与 OpenAI 兼容,流式下 usage(含 cached_tokens)在最后一个 chunk(需 stream_options.include_usage 等开关)。实验时建议以「最后一个 chunk 的 usage」为准核对,并用非流式对照(等级:基于兼容性推断,官方未明示)。 + +**5) 最低门槛** +- **缓存块 = 64 tokens**:`cached_tokens` 恒为 64 的倍数;prompt < 64 tokens 不会命中;共享前缀越长可复用越多。 + +**6) 计费折扣** +- 缓存命中 token 按标准输入价 **10%** 计费(官方原文)。 + +**7) 注意事项** +- `prompt_cache_key` 不应包含密钥/敏感数据;变更 prompt 开头部分会导致 miss;命中率可在 Admin Panel › Usage 按模型查看。 + +### 2.6 AWS Bedrock / Azure OpenAI(透传简述) + +**AWS Bedrock** +- 透传方式:Claude 系模型在 `converse` / `invoke_model` 请求的 system/tools 内放 `{"cachePoint": {"type": "default"}}` 标记缓存点(Claude 平台另有 cache_control 等效写法);Amazon Nova 模型则自动缓存(文本 prompt,最多 20K tokens)。 +- 响应中透传 Anthropic 原样字段:`usage.cacheReadInputTokens` / `usage.cacheCreationInputTokens`(REST)/ SDK `usage_metadata` 内 `cache_read_input_tokens` / `cache_creation_input_tokens`;AWS 官方博客示例: + +```json +"usage": { + "input_tokens": 10, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 37209, + "output_tokens": 324 +} +``` + +- 门槛/折扣与 Anthropic 一致(Claude);Claude 之外模型(截至 2026-06 官方/社区口径)多数尚未支持缓存(AWS re:Post 有用户确认 Nova 支持、Mistral 在 Bedrock 上不支持)。 +- 官方:https://aws.amazon.com/blogs/machine-learning/effectively-use-prompt-caching-on-amazon-bedrock + +**Azure OpenAI** +- 透传方式:与 OpenAI 同名请求结构与字段,模型名改为 Azure 部署名。 +- 命中字段:Chat Completions `usage.prompt_tokens_details.cached_tokens`;Responses API `usage.input_tokens_details.cached_tokens`;GPT-5.6+ 另有 `usage.prompt_tokens_details.cache_write_tokens`。 +- 门槛:**≥1024 tokens** 且前 1024 tokens 完全一致;GPT-5.5 及更早按 128 递增取整,GPT-5.6+ 不取整;默认自动启用,GPT-5.6+ 支持 `prompt_cache_key` / `prompt_cache_options.mode` / `prompt_cache_breakpoint`、`prompt_cache_options.ttl="30m"`。 +- 官方示例: + +```json +{ + "usage": { + "prompt_tokens": 1566, + "completion_tokens": 1518, + "total_tokens": 3084, + "prompt_tokens_details": { + "audio_tokens": null, + "cached_tokens": 1408, + "cache_write_tokens": 0 + }, + "completion_tokens_details": { "audio_tokens": null, "reasoning_tokens": 576 } + } +} +``` + +- 官方:https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/prompt-caching + +--- + +## 3. 实验建议(按供应商) + +通用思路:**连续发两条「共享固定前缀 + 不同后缀」的请求**,记录第 1 次(写入/未命中)与第 2 次(命中)的 usage 字段;前缀长度务必超出各家门槛;两次请求间隔须在缓存 TTL 内。 + +### OpenAI +- 构造:固定 system/developer 指令(≥1024 tokens,建议 2000+)放最前 + 每次变化的 user 后缀;两次请求只改后缀。 +- 读取字段:Chat Completions `usage.prompt_tokens_details.cached_tokens`;Responses API `usage.input_tokens_details.cached_tokens`(GPT-5.6+ 可同时看 `cache_write_tokens`/`cache_read` 0.1× 计费)。 +- 断言:第 2 次请求 `cached_tokens > 0`(且应为前缀长度附近,老模型按 128 取整)。 +- 流式:必须 `stream_options: {"include_usage": true}`,取最后一个 chunk 的 usage。 +- 建议同时打印完整 `usage` 对象,防止 SDK 解包时字段为 None。 + +### Anthropic Claude +- 构造:system 数组放长文本(≥对应模型门槛,如 Sonnet 4.5/5 = 1024,Haiku 4.5 = 4096),块尾加 `"cache_control": {"type": "ephemeral"}`;连续发两条相同前缀请求。 +- 读取字段:`usage.cache_read_input_tokens`(命中)与 `usage.cache_creation_input_tokens`(首次写入);`input_tokens` 为未命中部分。 +- 断言:第 1 次 `cache_creation_input_tokens ≈ 前缀长`、第 2 次 `cache_read_input_tokens ≈ 前缀长`;两次都在 5 分钟(默认 TTL)内。 +- 流式:读 `message_start` 事件的 `message.usage`。 +- 额外可试:`max_tokens: 0` 预热 + 顶层 `cache_control`(automatic caching)两种模式各跑一轮。 + +### Google Gemini +- 构造: + - 隐式:长 system_instruction / 长首条 user 消息(≥2048 tokens,Gemini 2.5;≥4096 若用 3.x),连续请求、保持前缀不变。 + - 显式:先 `cachedContents.create`(model + contents + ttl,如 300s~1h),再 generateContent 传 `cachedContent`,用于对照验证。 +- 读取字段:`response.usage_metadata.cached_content_token_count`(REST 为 `usageMetadata.cachedContentTokenCount`);明细看 `cache_tokens_details[]`。 +- 断言:显式缓存第 1 次 cached=0(或写资源)、之后 cached ≈ 缓存内容 token 数;隐式缓存命中需 ≥ 门槛且不保证,多试几轮或调高峰时段。 +- 流式:取流末尾 chunk 的 usageMetadata 核对;若缺失可与非流式对照。 +- 注意:promptTokenCount 含缓存 token,未命中部分 = promptTokenCount − cachedContentTokenCount。 + +### xAI Grok +- 构造:固定 system + 固定历史轮次 + 变化的最新 user 消息;设置 `x-grok-conv-id`(Chat Completions)或 `prompt_cache_key`(Responses API)保持一致。 +- 读取字段:Chat Completions `usage.prompt_tokens_details.cached_tokens`;Responses API `usage.input_tokens_details.cached_tokens`。 +- 断言:多轮递增 —— turn1 cached=0 → turn2 cached=前几轮总 token → turn3 更大;若一直为 0,改用不同/省略 conv-id 强制 miss 对照已验证机制。 +- 流式:可观察第一个空 token(prefill),usage 取最后 chunk。 + +### Mistral AI +- 构造:固定 system + 历史 + 变化的最后 user 消息,**每条请求都传相同 `prompt_cache_key`(如会话 ID)并完整重发前缀**;前缀建议 ≥128 tokens(至少 2 个 64 块)以便观察倍数。 +- 读取字段:`usage.prompt_tokens_details.cached_tokens`。 +- 断言:第 2 次 `cached_tokens > 0` 且为 64 的倍数;未命中时为 0 或字段缺失。计费未缓存输入 = prompt_tokens − cached_tokens(10% 折扣)。 +- 流式:取最后一个 chunk 的 usage;必要时非流式对照。 + +### AWS Bedrock / Azure OpenAI(顺带) +- Bedrock(Claude):system 里加 `{"cachePoint": {"type": "default"}}`,看 `usage.cacheReadInputTokens` / `cacheCreationInputTokens` 增减。 +- Azure OpenAI:与 OpenAI 实验相同(≥1024 tokens、前缀不动),看 `usage.prompt_tokens_details.cached_tokens` 与 GPT-5.6+ 的 `cache_write_tokens`。 + +--- + +## 附:信息来源与等级 + +| 内容 | 来源 | 等级 | +|---|---|---| +| OpenAI 全部字段/门槛/计费 | platform.openai.com/docs/guides/prompt-caching(官方,2026-08 抓取) | 官方文档 | +| OpenAI 历史 1024/128 递增/5 折 | openai.com/index/api-prompt-caching(官方公告) | 官方发布 | +| Anthropic 全部字段/门槛/计费/流式 | docs.anthropic.com(= platform.claude.com)prompt-caching(官方,2026-08 抓取) | 官方文档 | +| Gemini 隐式/显式/门槛 | ai.google.dev/gemini-api/docs/generate-content/caching、ai.google.dev/api/generate-content(官方) | 官方文档 | +| Gemini usageMetadata JSON | ai.google.dev/api/generate-content(官方 REST 参考) | 官方文档 | +| Gemini 2.5 隐式缓存历史阈值 | developers.googleblog.com(Google 官方博客) | 官方发布(辅助) | +| Gemini 流式/字段缺失现象 | discuss.ai.google.dev 社区帖 | 第三方(辅助) | +| xAI 全部字段/机制/流式 FAQ | docs.x.ai/developers/advanced-api-usage/prompt-caching(官方,2026-08 抓取) | 官方文档 | +| Mistral 全部字段/64 块/10% 计费 | docs.mistral.ai/studio/conversations/advanced/prompt-caching、docs.mistral.ai/api/endpoint/chat(官方) | 官方文档 | +| Bedrock cachePoint/usage 透传 | aws.amazon.com 官方博客 + AWS re:Post + Portkey 文档 | 官方博客/第三方辅助 | +| Azure OpenAI 字段/门槛 | learn.microsoft.com(微软官方) | 官方文档 | + +> 提示:以上信息抓取于 2026-08-29,部分字段/门槛随时间迭代(如 Gemini 阈值、Anthropic 门槛、OpenAI GPT-5.6 行为改动)。实验前建议按本报告给出的官方链接复核最新值;凡第三方转述均已在文中标注「等级=辅助」。 \ No newline at end of file diff --git a/server/deep_compression.py b/server/deep_compression.py index 27a9c25e..f3574247 100644 --- a/server/deep_compression.py +++ b/server/deep_compression.py @@ -584,6 +584,8 @@ async def run_deep_compression( # 关键:重置 current_context_tokens,避免自动压缩续接后阈值判断仍读到压缩前的大值而陷入死循环。 # 真实上下文长度会在下一次 API 响应后被重新写入。 + # 同时置位 cache_cold_start_pending:压缩重写了上下文前缀,缓存可能已失效; + # 下一次真实调用若未命中缓存,其输入会被计入冷启动豁免值(cache_exempt_input_tokens)。 try: target_manager.update_token_statistics( conversation_id, @@ -591,6 +593,7 @@ async def run_deep_compression( output_tokens=0, total_tokens=0, current_context_tokens=0, + cache_cold_start_pending=True, ) except Exception as exc: _emit(sender, "system_message", {"content": tr("deep_compression.stats_reset_failed", error=exc)}) diff --git a/static/src/app/methods/taskPolling/sync.ts b/static/src/app/methods/taskPolling/sync.ts index e20a2048..205a36e2 100644 --- a/static/src/app/methods/taskPolling/sync.ts +++ b/static/src/app/methods/taskPolling/sync.ts @@ -72,6 +72,8 @@ export const syncMethods = { this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; + this.currentConversationTokens.cumulative_cached_input_tokens = data.cumulative_cached_input_tokens || 0; + this.currentConversationTokens.cache_exempt_input_tokens = data.cache_exempt_input_tokens || 0; if (typeof data.current_context_tokens === 'number') { this.resourceSetCurrentContextTokens(data.current_context_tokens); diff --git a/static/src/components/token/TokenDrawer.vue b/static/src/components/token/TokenDrawer.vue index 5f31fcbe..25f60c74 100644 --- a/static/src/components/token/TokenDrawer.vue +++ b/static/src/components/token/TokenDrawer.vue @@ -33,6 +33,16 @@ {{ formatTokenCount(currentConversationTokens.cumulative_output_tokens || 0) }} +
+
{{ $t('sidebar.cumulativeCachedInput') }}
+
+ {{ formatTokenCount(currentConversationTokens.cumulative_cached_input_tokens || 0) }} +
+
+
+
{{ $t('sidebar.cacheHitRate') }}
+
{{ cacheHitRateText }}
+
@@ -128,6 +138,8 @@ const props = defineProps<{ currentConversationTokens: { cumulative_input_tokens?: number; cumulative_output_tokens?: number; + cumulative_cached_input_tokens?: number; + cache_exempt_input_tokens?: number; }; currentContextTokens: number; containerStatus: any; @@ -152,6 +164,19 @@ const quotaTiers = computed(() => [ { key: 'search', label: 'sidebar.quotaTierSearch', value: props.usageQuota.search } ]); +// 缓存命中率 = 累积缓存命中输入 / (累计总输入 - 冷启动豁免输入); +// 豁免值由后端累计:首轮及压缩后首轮若未命中缓存,其输入属于建立缓存的成本,不计入分母 +const cacheHitRateText = computed(() => { + const totalInput = props.currentConversationTokens.cumulative_input_tokens || 0; + const exemptInput = props.currentConversationTokens.cache_exempt_input_tokens || 0; + const effectiveInput = totalInput - exemptInput; + if (effectiveInput <= 0) { + return '--'; + } + const cached = props.currentConversationTokens.cumulative_cached_input_tokens || 0; + return `${((cached / effectiveInput) * 100).toFixed(1)}%`; +}); + const hasContainerStats = computed(() => { const status = props.containerStatus; if (!status || !status.stats) { diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts index 5dcf650b..0e2e46a0 100644 --- a/static/src/composables/useLegacySocket.ts +++ b/static/src/composables/useLegacySocket.ts @@ -676,6 +676,8 @@ export async function initializeLegacySocket(ctx: any) { ctx.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; ctx.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; ctx.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; + ctx.currentConversationTokens.cumulative_cached_input_tokens = data.cumulative_cached_input_tokens || 0; + ctx.currentConversationTokens.cache_exempt_input_tokens = data.cache_exempt_input_tokens || 0; socketLog( `Cumulative token stats updated: input=${data.cumulative_input_tokens}, output=${data.cumulative_output_tokens}, total=${data.cumulative_total_tokens}` diff --git a/static/src/locales/en-US/sidebar.ts b/static/src/locales/en-US/sidebar.ts index df64acee..9be13587 100644 --- a/static/src/locales/en-US/sidebar.ts +++ b/static/src/locales/en-US/sidebar.ts @@ -55,6 +55,8 @@ export default { currentContext: 'Current context', cumulativeInput: 'Total input', cumulativeOutput: 'Total output', + cumulativeCachedInput: 'Cached input', + cacheHitRate: 'Cache hit rate', performanceStats: 'Performance', memory: 'Memory', containerMetricsPending: 'Container is running, waiting for metrics...', diff --git a/static/src/locales/zh-CN/sidebar.ts b/static/src/locales/zh-CN/sidebar.ts index d3b28b10..0bc092e7 100644 --- a/static/src/locales/zh-CN/sidebar.ts +++ b/static/src/locales/zh-CN/sidebar.ts @@ -59,6 +59,8 @@ export default { currentContext: '当前上下文', cumulativeInput: '累计输入', cumulativeOutput: '累计输出', + cumulativeCachedInput: '累积缓存输入', + cacheHitRate: '缓存命中率', performanceStats: '性能统计', memory: '内存', containerMetricsPending: '容器已运行,等待采集指标...', diff --git a/static/src/stores/resource.ts b/static/src/stores/resource.ts index 4b4aa8c2..cd7097ad 100644 --- a/static/src/stores/resource.ts +++ b/static/src/stores/resource.ts @@ -5,6 +5,8 @@ interface ConversationTokens { cumulative_input_tokens: number; cumulative_output_tokens: number; cumulative_total_tokens: number; + cumulative_cached_input_tokens: number; + cache_exempt_input_tokens: number; } interface ProjectStorage { @@ -65,7 +67,9 @@ export const useResourceStore = defineStore('resource', { currentConversationTokens: { cumulative_input_tokens: 0, cumulative_output_tokens: 0, - cumulative_total_tokens: 0 + cumulative_total_tokens: 0, + cumulative_cached_input_tokens: 0, + cache_exempt_input_tokens: 0 } as ConversationTokens, projectStorage: { used_bytes: 0, @@ -97,7 +101,9 @@ export const useResourceStore = defineStore('resource', { this.currentConversationTokens = { cumulative_input_tokens: 0, cumulative_output_tokens: 0, - cumulative_total_tokens: 0 + cumulative_total_tokens: 0, + cumulative_cached_input_tokens: 0, + cache_exempt_input_tokens: 0 }; }, setCurrentContextTokens(value: number) { @@ -143,6 +149,10 @@ export const useResourceStore = defineStore('resource', { this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0; this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0; + this.currentConversationTokens.cumulative_cached_input_tokens = + data.data.total_cached_input_tokens || 0; + this.currentConversationTokens.cache_exempt_input_tokens = + data.data.cache_exempt_input_tokens || 0; if (typeof data.data.current_context_tokens === 'number') { this.currentContextTokens = data.data.current_context_tokens; } diff --git a/static/src/styles/components/panels/_resource-panel.scss b/static/src/styles/components/panels/_resource-panel.scss index 75e6b657..0bf7bfcc 100644 --- a/static/src/styles/components/panels/_resource-panel.scss +++ b/static/src/styles/components/panels/_resource-panel.scss @@ -132,6 +132,14 @@ font-size: 20px; } +// 深色主题下 --accent 为灰色(#606060),作为大字号统计数字辨识度不足, +// 「当前上下文」数字改用 --text-primary(dark 下为白色)保证可读性。 +body[data-theme='dark'] { + .stat-value--accent { + color: var(--text-primary); + } +} + .stat-value--success { color: var(--state-success); } diff --git a/utils/context_manager/token_mixin.py b/utils/context_manager/token_mixin.py index c0091aa1..a4a00da8 100644 --- a/utils/context_manager/token_mixin.py +++ b/utils/context_manager/token_mixin.py @@ -100,6 +100,7 @@ class TokenMixin: "input_tokens": int(payload.get("input_tokens") or payload.get("total_input_tokens") or 0), "output_tokens": int(payload.get("output_tokens") or payload.get("total_output_tokens") or 0), "total_tokens": int(payload.get("total_tokens") or 0), + "cached_input_tokens": int(payload.get("cached_input_tokens") or payload.get("total_cached_input_tokens") or 0), "updated_at": payload.get("updated_at"), } except (OSError, json.JSONDecodeError, ValueError) as exc: @@ -117,13 +118,14 @@ class TokenMixin: with open(path, 'w', encoding='utf-8') as fh: json.dump(data, fh, ensure_ascii=False, indent=2) - def _increment_workspace_token_totals(self, input_tokens: int, output_tokens: int, total_tokens: int): + def _increment_workspace_token_totals(self, input_tokens: int, output_tokens: int, total_tokens: int, cached_input_tokens: int = 0): if input_tokens <= 0 and output_tokens <= 0 and total_tokens <= 0: return snapshot = self._load_token_totals() snapshot["input_tokens"] = snapshot.get("input_tokens", 0) + max(0, int(input_tokens)) snapshot["output_tokens"] = snapshot.get("output_tokens", 0) + max(0, int(output_tokens)) snapshot["total_tokens"] = snapshot.get("total_tokens", 0) + max(0, int(total_tokens)) + snapshot["cached_input_tokens"] = snapshot.get("cached_input_tokens", 0) + max(0, int(cached_input_tokens)) snapshot["updated_at"] = datetime.now().isoformat() self._save_token_totals(snapshot) @@ -137,9 +139,11 @@ class TokenMixin: total_tokens = int(normalized_usage.get("total_tokens") or (prompt_tokens + completion_tokens)) # 当前上下文长度优先取专用字段;缺失时回退到 prompt_tokens current_context_tokens = int(normalized_usage.get("current_context_tokens") or prompt_tokens) + # 本次请求命中缓存的输入 token(全厂商字段已在 normalize 中归一化) + cached_input_tokens = int(normalized_usage.get("cached_input_tokens") or 0) try: - self._increment_workspace_token_totals(prompt_tokens, completion_tokens, total_tokens) + self._increment_workspace_token_totals(prompt_tokens, completion_tokens, total_tokens, cached_input_tokens) except Exception as exc: print(f"[TokenStats] 无法写入累计Token: {exc}") @@ -160,6 +164,7 @@ class TokenMixin: completion_tokens, total_tokens, current_context_tokens=current_context_tokens, + cached_input_tokens=cached_input_tokens, ) if success: @@ -221,6 +226,8 @@ class TokenMixin: 'cumulative_input_tokens': cumulative_stats.get("total_input_tokens", 0) if cumulative_stats else 0, 'cumulative_output_tokens': cumulative_stats.get("total_output_tokens", 0) if cumulative_stats else 0, 'cumulative_total_tokens': cumulative_stats.get("total_tokens", 0) if cumulative_stats else 0, + 'cumulative_cached_input_tokens': cumulative_stats.get("total_cached_input_tokens", 0) if cumulative_stats else 0, + 'cache_exempt_input_tokens': cumulative_stats.get("cache_exempt_input_tokens", 0) if cumulative_stats else 0, 'current_context_tokens': cumulative_stats.get("current_context_tokens", 0) if cumulative_stats else 0, 'updated_at': datetime.now().isoformat() } diff --git a/utils/conversation_manager/metadata_mixin.py b/utils/conversation_manager/metadata_mixin.py index d1271856..32a8f478 100644 --- a/utils/conversation_manager/metadata_mixin.py +++ b/utils/conversation_manager/metadata_mixin.py @@ -147,6 +147,11 @@ class MetadataMixin: "total_input_tokens": 0, "total_output_tokens": 0, "total_tokens": 0, + "total_cached_input_tokens": 0, + # 豁免出命中率分母的冷启动输入累计(首轮未命中 + 压缩后首轮未命中的输入) + "cache_exempt_input_tokens": 0, + # 深度压缩后待判定标记:下一次真实调用若无缓存命中,其输入累加进豁免值 + "cache_cold_start_pending": False, "current_context_tokens": 0, "updated_at": now } @@ -161,12 +166,15 @@ class MetadataMixin: if key not in token_stats: token_stats[key] = default_value - # 确保数值类型正确 + # 确保数值类型正确(cache_cold_start_pending 为布尔,不在此转换) try: token_stats["total_input_tokens"] = int(token_stats.get("total_input_tokens", 0)) token_stats["total_output_tokens"] = int(token_stats.get("total_output_tokens", 0)) token_stats["total_tokens"] = int(token_stats.get("total_tokens", 0)) + token_stats["total_cached_input_tokens"] = int(token_stats.get("total_cached_input_tokens", 0)) + token_stats["cache_exempt_input_tokens"] = int(token_stats.get("cache_exempt_input_tokens", 0)) token_stats["current_context_tokens"] = int(token_stats.get("current_context_tokens", 0)) + token_stats["cache_cold_start_pending"] = bool(token_stats.get("cache_cold_start_pending", False)) except (ValueError, TypeError): print("⚠️ Token统计数据损坏,重置为0") token_stats = defaults diff --git a/utils/conversation_manager/token_mixin.py b/utils/conversation_manager/token_mixin.py index fa7c6692..f0dec2a0 100644 --- a/utils/conversation_manager/token_mixin.py +++ b/utils/conversation_manager/token_mixin.py @@ -48,6 +48,8 @@ class TokenMixin: output_tokens: int, total_tokens: int, current_context_tokens: Optional[int] = None, + cached_input_tokens: int = 0, + cache_cold_start_pending: bool = False, ) -> bool: """ 更新对话的Token统计 @@ -58,6 +60,8 @@ class TokenMixin: output_tokens: 输出Token数量 total_tokens: 本次请求的总Token数量(prompt+completion) current_context_tokens: 当前上下文长度(用于压缩阈值判断) + cached_input_tokens: 本次请求命中缓存的输入Token数量 + cache_cold_start_pending: 置位「压缩后待判定」标记(深度压缩后调用时传入) Returns: bool: 更新是否成功 @@ -74,9 +78,28 @@ class TokenMixin: # 更新统计数据 token_stats = conversation_data["token_statistics"] + + # ── 冷启动豁免:首轮/压缩后首轮若未命中缓存,其输入属于“建立缓存”成本, + # 豁免出命中率分母(cache_exempt_input_tokens);若命中则说明缓存延续,正常处理。 + # 判断需在累加之前进行(首轮判定依赖累加前的 total_input_tokens 为 0)。 + if input_tokens > 0: + is_first_call = token_stats.get("total_input_tokens", 0) == 0 + cold_start_pending = bool(token_stats.get("cache_cold_start_pending", False)) + if is_first_call or cold_start_pending: + if cached_input_tokens <= 0: + token_stats["cache_exempt_input_tokens"] = ( + token_stats.get("cache_exempt_input_tokens", 0) + int(input_tokens) + ) + # 压缩后首轮消费标记(首轮不涉及该标记,置 False 无副作用) + token_stats["cache_cold_start_pending"] = False + token_stats["total_input_tokens"] = token_stats.get("total_input_tokens", 0) + input_tokens token_stats["total_output_tokens"] = token_stats.get("total_output_tokens", 0) + output_tokens token_stats["total_tokens"] = token_stats.get("total_tokens", 0) + total_tokens + token_stats["total_cached_input_tokens"] = token_stats.get("total_cached_input_tokens", 0) + max(0, int(cached_input_tokens or 0)) + # 置位压缩后待判定标记(深度压缩重置统计时传入) + if cache_cold_start_pending: + token_stats["cache_cold_start_pending"] = True if current_context_tokens is None: # 兼容旧调用:未显式传入时,默认以输入 token 作为当前上下文长度 current_context_tokens = input_tokens @@ -116,6 +139,8 @@ class TokenMixin: "total_input_tokens": token_stats.get("total_input_tokens", 0), "total_output_tokens": token_stats.get("total_output_tokens", 0), "total_tokens": token_stats.get("total_tokens", 0), + "total_cached_input_tokens": token_stats.get("total_cached_input_tokens", 0), + "cache_exempt_input_tokens": token_stats.get("cache_exempt_input_tokens", 0), "current_context_tokens": token_stats.get("current_context_tokens", 0), "updated_at": token_stats.get("updated_at"), "conversation_id": conversation_id diff --git a/utils/token_usage.py b/utils/token_usage.py index 13647259..770a9a5f 100644 --- a/utils/token_usage.py +++ b/utils/token_usage.py @@ -16,6 +16,7 @@ INPUT_TOKEN_KEYS = ( "inputTokens", "promptTokens", "prefill_tokens", + "promptTokenCount", ) OUTPUT_TOKEN_KEYS = ( "completion_tokens", @@ -24,6 +25,7 @@ OUTPUT_TOKEN_KEYS = ( "completionTokens", "generated_tokens", "generatedTokens", + "candidatesTokenCount", ) TOTAL_TOKEN_KEYS = ( "total_tokens", @@ -31,6 +33,45 @@ TOTAL_TOKEN_KEYS = ( "total_token_count", "totalTokenCount", ) +# 缓存命中 token 数的所有已知字段位置(2026-08 调研,见 cache_research/SUMMARY.md): +# - OpenAI 系/Qwen/GLM/MiniMax/xAI/Mistral/千帆/OpenRouter: usage.prompt_tokens_details.cached_tokens +# (Responses API 为 usage.input_tokens_details.cached_tokens) +# - DeepSeek: usage.prompt_cache_hit_tokens(顶层) +# - Kimi / 阶跃Step / 部分 DashScope 地域: usage.cached_tokens(顶层) +# - Anthropic / Bedrock / MiniMax-Anthropic 模式/中转站: usage.cache_read_input_tokens(顶层) +# - Gemini: usageMetadata.cachedContentTokenCount +CACHED_INPUT_TOKEN_KEYS = ( + "cached_input_tokens", # normalize 输出自身的字段名(保证二次归一化幂等) + "cached_tokens", + "cachedTokens", + "prompt_cache_hit_tokens", + "promptCacheHitTokens", + "cache_read_input_tokens", + "cacheReadInputTokens", + "cached_content_token_count", + "cachedContentTokenCount", +) +CACHE_WRITE_TOKEN_KEYS = ( + "cache_creation_input_tokens", + "cacheCreationInputTokens", + "cache_write_tokens", + "cacheWriteTokens", +) +# 缓存详情可能出现的嵌套容器(OpenAI 风格 details 对象) +PROMPT_DETAILS_KEYS = ( + "prompt_tokens_details", + "input_tokens_details", + "promptTokensDetails", + "inputTokensDetails", +) +# Anthropic 语义的输入键与顶层缓存字段组合:仅当【输入命中 input_tokens 类键】 +# 且【顶层存在 cache_read_input_tokens / cache_creation_input_tokens】时才判定为 +# Anthropic 语义(input_tokens 不含缓存部分),需要把缓存部分加回总输入。 +# 注意:OpenAI Responses API 也用 input_tokens 键但其缓存字段在 input_tokens_details 里 +# (input_tokens 本身含缓存),因此不能用键名单独判断,必须同时要求顶层 Anthropic 字段存在。 +ANTHROPIC_STYLE_INPUT_KEYS = {"input_tokens", "inputTokens"} +ANTHROPIC_CACHE_READ_KEYS = ("cache_read_input_tokens", "cacheReadInputTokens") +ANTHROPIC_CACHE_WRITE_KEYS = ("cache_creation_input_tokens", "cacheCreationInputTokens") CURRENT_CONTEXT_KEYS = ( "current_context_tokens", "currentContextTokens", @@ -60,28 +101,49 @@ def _to_int(value: Any) -> Optional[int]: def _first_int(payload: Dict[str, Any], keys: Iterable[str]) -> Optional[int]: + _, value = _first_int_with_key(payload, keys) + return value + + +def _first_int_with_key(payload: Dict[str, Any], keys: Iterable[str]) -> tuple: + """返回 (命中键名, 值);未命中返回 (None, None)。""" for key in keys: if key in payload: value = _to_int(payload.get(key)) if value is not None: - return value - return None + return key, value + return None, None def normalize_usage_payload(raw: Any) -> Optional[Dict[str, int]]: if not isinstance(raw, dict): return None - prompt_tokens = _first_int(raw, INPUT_TOKEN_KEYS) + prompt_key, prompt_tokens = _first_int_with_key(raw, INPUT_TOKEN_KEYS) completion_tokens = _first_int(raw, OUTPUT_TOKEN_KEYS) total_tokens = _first_int(raw, TOTAL_TOKEN_KEYS) current_context_tokens = _first_int(raw, CURRENT_CONTEXT_KEYS) - prompt_details = raw.get("prompt_tokens_details") or raw.get("input_tokens_details") - if isinstance(prompt_details, dict): - cached = _first_int(prompt_details, ("cached_tokens", "cachedTokens")) - # cached tokens are still part of prompt tokens in most APIs. Keep the - # detail accessible for callers that need it, but do not add it again. + # 缓存命中:先查顶层字段(DeepSeek/Kimi/Step/Anthropic/Gemini),再查 details 容器(OpenAI 系) + cached_input_tokens = _first_int(raw, CACHED_INPUT_TOKEN_KEYS) + cache_write_tokens = _first_int(raw, CACHE_WRITE_TOKEN_KEYS) + for details_key in PROMPT_DETAILS_KEYS: + prompt_details = raw.get(details_key) + if not isinstance(prompt_details, dict): + continue + if cached_input_tokens is None: + cached_input_tokens = _first_int(prompt_details, CACHED_INPUT_TOKEN_KEYS) + if cache_write_tokens is None: + cache_write_tokens = _first_int(prompt_details, CACHE_WRITE_TOKEN_KEYS) + + # Anthropic 语义校准:顶层出现 cache_read/cache_creation 字段且输入键为 input_tokens 时, + # input_tokens 不含缓存读取/写入部分,加回以统一“总输入”口径; + # OpenAI 系(prompt_tokens 或 details 内 cached_tokens)本身含缓存部分,不校准。 + anthropic_read = _first_int(raw, ANTHROPIC_CACHE_READ_KEYS) + anthropic_write = _first_int(raw, ANTHROPIC_CACHE_WRITE_KEYS) + if prompt_key in ANTHROPIC_STYLE_INPUT_KEYS and (anthropic_read or anthropic_write): + prompt_tokens = (prompt_tokens or 0) + (anthropic_read or 0) + (anthropic_write or 0) + completion_details = raw.get("completion_tokens_details") or raw.get("output_tokens_details") if isinstance(completion_details, dict): reasoning = _first_int(completion_details, ("reasoning_tokens", "reasoningTokens")) @@ -105,6 +167,7 @@ def normalize_usage_payload(raw: Any) -> Optional[Dict[str, int]]: "completion_tokens": int(completion_tokens), "total_tokens": int(total_tokens), "current_context_tokens": int(current_context_tokens), + "cached_input_tokens": int(cached_input_tokens or 0), }