agent-Specialization/cache_research/SUMMARY.md
JOJO 3a4ea67e26 feat(stats): token 统计新增缓存命中追踪与命中率展示
- 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/ 各厂商缓存字段调研文档(代码注释引用)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 12:16:48 +08:00

85 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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+ 可显式断点) | 10245.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 | 隐式 20482.5/ 40963.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 透传路径有 bugPortkey 明确规范化CF 未文档化(推测透传) | LiteLLM `/v1/messages` 路径不映射 `cached_tokens`#27763 |
| **订阅制Copilot/Cursor/Windsurf/Augment** | 无公开 per-request usage APICursor/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 走完全独立的 usageMetadatacamelCase从响应体而非 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 chunkKimi 流式末 chunk 带 usageAnthropic 看 `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 |