From 0a2f12c04e7dc83379fdb663d6330062edd2ef59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=BF=E5=AE=89?= <1420970597@qq.com> Date: Sat, 20 Dec 2025 14:17:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Anthropic=20?= =?UTF-8?q?=E6=B8=A0=E9=81=93=E7=BC=93=E5=AD=98=E8=AE=A1=E8=B4=B9=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题描述 当使用 Anthropic 渠道通过 `/v1/chat/completions` 端点调用且启用缓存功能时, 计费逻辑错误地减去了缓存 tokens,导致严重的收入损失(94.5%)。 ## 根本原因 不同 API 的 `prompt_tokens` 定义不同: - **Anthropic API**: `input_tokens` 字段已经是纯输入 tokens(不包含缓存) - **OpenAI API**: `prompt_tokens` 字段包含所有 tokens(包含缓存) - **OpenRouter API**: `prompt_tokens` 字段包含所有 tokens(包含缓存) 当前 `postConsumeQuota` 函数对所有渠道都减去缓存 tokens,这对 Anthropic 渠道是错误的,因为其 `input_tokens` 已经不包含缓存。 ## 修复方案 在 `relay/compatible_handler.go` 的 `postConsumeQuota` 函数中,添加渠道类型判断: ```go if relayInfo.ChannelType != constant.ChannelTypeAnthropic { baseTokens = baseTokens.Sub(dCacheTokens) } ``` 只对非 Anthropic 渠道减去缓存 tokens。 ## 影响分析 ### ✅ 不受影响的场景 1. **无缓存调用**(所有渠道) - cache_tokens = 0 - 减去 0 = 不减去 - 结果:完全一致 2. **OpenAI/OpenRouter 渠道 + 缓存** - 继续减去缓存(因为 ChannelType != Anthropic) - 结果:完全一致 3. **Anthropic 渠道 + /v1/messages 端点** - 使用 PostClaudeConsumeQuota(不修改) - 结果:完全不受影响 ### ✅ 修复的场景 4. **Anthropic 渠道 + /v1/chat/completions + 缓存** - 修复前:错误地减去缓存,导致 94.5% 收入损失 - 修复后:不减去缓存,计费正确 ## 验证数据 以实际记录 143509 为例: | 项目 | 修复前 | 修复后 | 差异 | |------|--------|--------|------| | Quota | 10,489 | 191,330 | +180,841 | | 费用 | ¥0.020978 | ¥0.382660 | +¥0.361682 | | 收入恢复 | - | - | **+1724.1%** | ## 测试建议 1. 测试 Anthropic 渠道 + 缓存场景 2. 测试 OpenAI 渠道 + 缓存场景(确保不受影响) 3. 测试无缓存场景(确保不受影响) ## 相关 Issue 修复 Anthropic 渠道使用 prompt caching 时的计费错误。 --- relay/compatible_handler.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/relay/compatible_handler.go b/relay/compatible_handler.go index f46ff9de9..d92c990a7 100644 --- a/relay/compatible_handler.go +++ b/relay/compatible_handler.go @@ -300,14 +300,20 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage if !relayInfo.PriceData.UsePrice { baseTokens := dPromptTokens // 减去 cached tokens + // Anthropic API 的 input_tokens 已经不包含缓存 tokens,不需要减去 + // OpenAI/OpenRouter 等 API 的 prompt_tokens 包含缓存 tokens,需要减去 var cachedTokensWithRatio decimal.Decimal if !dCacheTokens.IsZero() { - baseTokens = baseTokens.Sub(dCacheTokens) + if relayInfo.ChannelType != constant.ChannelTypeAnthropic { + baseTokens = baseTokens.Sub(dCacheTokens) + } cachedTokensWithRatio = dCacheTokens.Mul(dCacheRatio) } var dCachedCreationTokensWithRatio decimal.Decimal if !dCachedCreationTokens.IsZero() { - baseTokens = baseTokens.Sub(dCachedCreationTokens) + if relayInfo.ChannelType != constant.ChannelTypeAnthropic { + baseTokens = baseTokens.Sub(dCachedCreationTokens) + } dCachedCreationTokensWithRatio = dCachedCreationTokens.Mul(dCachedCreationRatio) }