Repository navigation
Open
Description
Agent
- Tool: Claude Code
- Tool version: 2.1.325
- Model (full id): claude-opus-5-5
- Host (CLI / IDE / GitHub coding agent / other): CLI
- Date (UTC): 2026-11-14
User request
Claude 渠道处理 Gemini 格式请求时,new-api 返回的 usageMetadata.cachedContentTokenCount 恒为 0,但缓存读已经计入 promptTokenCount,和同一响应里 billing_usage 记录的缓存读不一致。按 Gemini 标准字段计费的下游会因此把缓存读按普通输入计费。需要修复这个转换,让标准字段如实反映缓存读;不能改变 new-api 自身的结算结果,也不能引入其他副作用。先提 issue,再提 PR。
- Later constraints or corrections from the user (quote, or none): 已并入上面的整理描述
Out of scope — refuse
- Matched: no
- If yes, what was told to the user (stop here; do not file): 不适用
File gate — do not file unless all are satisfied
- Out of scope (including pass-through): no
- Usage / configuration / integration (answered instead of filing): no
- Required facts present without invention (actual behavior, impact, frequency, evidence in new-api, applicable type fields): yes
- Body is short and factual; no unfiltered AI-generated text: yes
- File: yes
- If no, what was told to the user (stop here): 不适用
Kind
- Bug
- Feature
- Investigation
- Other:
Usage / configuration / integration check
- https://docs.newapi.ai/ — 搜索 cachedContentTokenCount、billing_usage、Gemini 格式转换。结论:只有「缓存计费」一节,讲的是命中缓存按 Prompt Cache Ratio 计费,没有讲转换出来的 Gemini 响应里 usage 怎么填。
- https://deepwiki.com/QuantumNous/new-api — 2026-11-14 查询了三次,都返回 HTTP 429,没能看到内容。
- README / repo docs: README* 和 docs/ 里都没有 cachedContentTokenCount 或 billing_usage 的相关说明。
- Relevant code paths and conclusion:
relaykit/relayconvert/internal/oai_chat/to_gemini_chat_resp.go里的ResponseOpenAI2Gemini、StreamResponseOpenAI2Gemini、newGeminiStreamResponse用 chat usage 填promptTokenCount(已经包含缓存读),但没有设置cachedContentTokenCount。反方向的gemini_chat/to_oai_chat_resp.go会把cachedContentTokenCount映射成cached_tokens。 - Can the current version already do this? (required for feature requests): 不适用(bug)
- Verdict: product bug
Environment
- new-api version / commit / image tag: main
b388140ac(源码构建) - Deploy source: main source
- Database: sqlite
Problem facts
- Actual behavior: Gemini 格式请求由 Claude 渠道处理时,返回的
usageMetadata.promptTokenCount=15035(input 2054 + cache_read 12981),cachedContentTokenCount=0;同一个对象里的billing_usage.claude_usage.cache_read_input_tokens=12981。流式和非流式都是这样。 - Impact: 同一个响应里,标准字段和
billing_usage描述的缓存用量对不上。只按 Gemini 标准字段计费的客户端或网关,会把 15035 全部按普通输入计;下游 new-api 读billing_usage,按 2054 全价 + 12981 缓存价计。本实例自己的结算正确,消费日志见下文。 - Frequency: 这条路由上只要有缓存读,每次必现。
- Evidence that the problem is in new-api rather than the client or upstream: 上游返回的
cache_read_input_tokens=12981,经 new-api 转成 Gemini 格式后,标准字段里没有体现;改动 new-api 的转换代码后,同样的上游返回能得到cachedContentTokenCount=12981。实验见下文。
Type-specific details
Relay / API
- Request endpoint and method:
POST /v1beta/models/claude-sonnet-4-5-20250929:generateContent和POST /v1beta/models/claude-sonnet-4-5-20250929:streamGenerateContent?alt=sse - Channel type: Anthropic Claude(type 14)
- Model: claude-sonnet-4-5-20250929
- Conversion format: 请求 Gemini → Claude;响应 Claude → OpenAI chat → Gemini(
claude_messages_to_gemini_chat_resp) - Pass-through enabled: 否
- Evidence of upstream native support: Anthropic Messages 的 usage 带
cache_read_input_tokens;Gemini 的usageMetadata.promptTokenCount包含cachedContentTokenCount。 - Equivalent redacted request sent directly upstream: status, body, server logs: 上游是本地模拟的 Anthropic Messages 服务,固定返回 200,usage 为
{"input_tokens":2054,"cache_creation_input_tokens":0,"cache_read_input_tokens":12981,"output_tokens":42};流式的message_delta带同样的累计 usage。 - Same request through new-api: status, body, server logs: 200。非流式和流式最后一个 chunk 的
usageMetadata都是"promptTokenCount":15035,"candidatesTokenCount":42,"totalTokenCount":15077,"cachedContentTokenCount":0,"billing_usage":{"source":"claude_messages","semantic":"anthropic","claude_usage":{"input_tokens":2054,"cache_creation_input_tokens":0,"cache_read_input_tokens":12981,"output_tokens":42,...}}
Billing
- Request endpoint and model: 同上
- Response
usage: 同上 - Relevant ratio or pricing configuration: 内置价格,未改配置;消费日志里 cache_ratio 为 0.1
- Consumption log: 流式和非流式都是
prompt_tokens=2054, completion_tokens=42, cache_tokens=12981, cache_ratio=0.1, quota=5343, usage_billing_path=billing-usage-anthropic - Expected charge and calculation basis: 本实例的扣费正确,不需要改。需要改的是响应:
cachedContentTokenCount应为 12981,让标准字段和billing_usage描述同一份用量。
Frontend
- not applicable
Deployment / upgrade
- not applicable
Reproduction and expected result
- Steps to reproduce:
- 用 main
b388140ac源码构建,sqlite,初始化时开启自用模式。 - 起一个模拟的 Anthropic Messages 上游,返回上面的 usage。
- 新建 Anthropic 渠道:base_url 指向模拟上游,模型 claude-sonnet-4-5-20250929;再新建一个令牌。
- 用该令牌(
x-goog-api-key)请求:generateContent和:streamGenerateContent?alt=sse,请求体{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}。
- 用 main
- Expected result:
usageMetadata.cachedContentTokenCount=12981,promptTokenCount仍为 15035。 - Related screenshots (optional): 无
Feature (feature requests only)
- Feature description: 不适用
- Use case: 不适用
Duplicate check
- Search queries (issues, PRs, discussions):
cachedContentTokenCount、billing_usage gemini、gemini cache usage、OpenAI2Gemini、claude to gemini、gemini cached tokens、gemini 缓存 计费 - Closest existing threads: fix(gemini): support Gemini 3.5/regional models and preserve thoughts usage #7641(已关闭,未合并)、Claude 上游返回 billing_usage 时缓存写入 Token 漏计,缓存写入完全未计费 #6353、Claude 用量日志的 prompt_tokens 只记录未命中缓存的 fresh token,导致日志显示与 TPM/token 统计漏掉全部缓存输入 #7290、Anthropic API 的 cacheRead 应该计入数据看板的总 token 数 #5440
- Why this is not a duplicate: fix(gemini): support Gemini 3.5/regional models and preserve thoughts usage #7641 里包含相同的字段映射,但混在 Gemini 3.5 推理识别等不相关改动里,2026-10-10 被关闭,没有合并;main 仍未修复。Claude 上游返回 billing_usage 时缓存写入 Token 漏计,缓存写入完全未计费 #6353 讲的是 Claude
billing_usage的缓存写入计费,Claude 用量日志的 prompt_tokens 只记录未命中缓存的 fresh token,导致日志显示与 TPM/token 统计漏掉全部缓存输入 #7290 / Anthropic API 的 cacheRead 应该计入数据看板的总 token 数 #5440 讲的是日志和看板里的 token 统计,都不是这个问题。
Research
Docs
- https://docs.newapi.ai/ : 缓存计费页(basic-concepts/features-introduction)只讲按 Prompt Cache Ratio 计费,没讲转换后的用量字段
- https://deepwiki.com/QuantumNous/new-api : HTTP 429,没能看到内容
- README / other repo docs: 没有相关内容
- Conclusions: 文档里没有说明这个行为,需要看代码确认
Code
relaykit/relayconvert/text_converter_registry.go— Claude → Gemini 的响应走ConverterClaudeMessagesToOpenAIChat→ConverterOpenAIChatToGeminiContent两步relaykit/relayconvert/internal/claude_messages/to_oai_chat_resp.gobuildOpenAIStyleUsageFromClaudeUsage— chat usage:prompt_tokens= input + cache_read + cache_creation,prompt_tokens_details.cached_tokens= cache_readrelaykit/relayconvert/internal/oai_chat/to_gemini_chat_resp.goResponseOpenAI2Gemini/StreamResponseOpenAI2Gemini/newGeminiStreamResponse— 只填 prompt / candidates / total,cachedContentTokenCount恒为 0relaykit/relayconvert/internal/gemini_chat/to_oai_chat_resp.goUsageFromGeminiMetadata— 反方向会把cachedContentTokenCount映射成 cached_tokens
Experiments
- Command or redacted request: 见复现步骤
- Direct upstream result: usage 中
cache_read_input_tokens=12981 - Result through new-api:
promptTokenCount=15035, cachedContentTokenCount=0 - Conclusion: 缓存读计入了
promptTokenCount,但没有体现在cachedContentTokenCount里
Working theory
- What is broken or missing: chat → Gemini 的响应转换没有把 chat usage 里的缓存读写进
cachedContentTokenCount - Why: 上述三个函数只复制 prompt / candidates / total
- What would falsify this: 在未修改的 main 上,Claude → Gemini 的响应里出现非 0 的
cachedContentTokenCount
Scope
- In scope for a later PR: chat → Gemini 的三处 usageMetadata 构造补上
cachedContentTokenCount - Out of scope / not this repo: 缓存写入(Gemini 没有对应字段);流式下 DeepSeek 风格的
prompt_cache_hit_tokens;上游message_delta只带 output_tokens 时,Gemini 最后一个 chunk 的promptTokenCount为 0(另一个问题) - Large or directional feature? 否
Proposed direction
由 chat usage 生成的 Gemini 响应(流式和非流式)里,cachedContentTokenCount 等于 promptTokenCount 中包含的缓存读 token 数;本实例的结算结果不变。
Not verified
- 没有用真实的 Anthropic API 验证,上游是模拟服务
- 只用 sqlite 验证(不涉及数据库改动)
- OpenAI 渠道带
cached_tokens的情况只用单元测试覆盖,没有实际起服务验证
Related
Metadata
Metadata
Assignees
Labels
No labels
Activity