
后端网络/通信云原生【免费下载链接】bfeA modern layer 7 load balancer from baidu项目地址https://gitcode.com/gh_mirrors/bf/bfe点击查看免费下载导读本文基于 BFE 开源仓库中docs/zh_cn/modifications/2026-08-31-ai-token-auth-billing-fix/design-changes.md的设计文档系统剖析mod_ai_token_auth模块在 Anthropic / OpenAI 协议下暴露的三处计费异常对应 issues #1343、#1344、#1345的根因与最小改动修复方案并深入当前仓库源码bfe_modules/mod_ai_token_auth/、bfe_model_protocol/验证修复后的实际实现与测试覆盖。读者将掌握cache 命中 token 的正确计费语义、count_tokens非计费端点的豁免方式、请求级扣费幂等标记的设计思路以及 Anthropic 与 OpenAI 协议 usage 字段语义差异在解析层如何归一化。1. 问题背景线上对账发现的三类计费偏差mod_ai_token_auth是 BFE 中负责 AI 请求 Token 认证与配额扣费的核心模块。线上对账发现该模块在Anthropic / OpenAI 协议下存在三处计费异常分别表现为少收、多收和重复扣费Issue现象业务影响#1343cache 读取 token 数被错误截断到promptTokens高 cache 命中场景下严重少收#1344Anthropic/count_tokens端点被当作普通请求扣费产生不应有的多收#1345HandleRequestFinish回调被触发多次同一笔费用重复扣款设计文档给出的总体思路是对三处问题统一分析、给出最小改动修复避免引入分布式锁等重机制。2. 根因分析三处问题的根因都集中在bfe_modules/mod_ai_token_auth/mod_ai_token_auth.go涉及calcChatCost()、tokenRequestFinishHandler()两个核心函数。2.1 Issue #1343cache 读取 token 被截断导致少收根因位置calcChatCost()中对cacheReadTokens的截断逻辑if cacheReadTokens promptTokens { cacheReadTokens promptTokens }这段逻辑的潜在假设是「cache 命中数不可能超过总 prompt token 数」。但在Anthropic 协议下这个假设不成立Anthropic 的usage.PromptTokens来自 SSE 事件中的input_tokens该字段仅包含 cache missfresh的 token 数而非总 prompt token 数Anthropic 的usage.CacheReadTokens来自cache_read_input_tokens是实际 cache 命中数。当 cache 命中率很高时cacheReadTokens promptTokens截断后大量 cache token 被丢弃导致 cache 部分几乎不计费。对于OpenAI / DeepSeek 协议虽然prompt_tokens已包含 cache 部分、不存在上述问题但该截断逻辑对它们同样不必要——后续normalInput promptTokens - cacheReadTokens已经通过if normalInput 0 { normalInput 0 }做了非负保护截断反而会低估 cache 用量。2.2 Issue #1344count_tokens 端点被误扣费导致多收根因位置tokenRequestFinishHandler()。Anthropic 的POST /anthropic/v1/messages/count_tokens仅用于计算请求 token 数不应产生任何费用。但原实现对所有 200 OK 响应都会执行扣费逻辑未按 endpoint 路径做豁免。更隐蔽的是count_tokens是非流式请求ContentLength 0因此tokenReadResponseHandler会调用UpdateCtxByUsage(body)尝试解析 usage。但count_tokens的响应体格式不匹配任何解析模式tokenUsage未被正确更新随后tokenRequestFinishHandler使用GetPromptToken基于请求体字节数估算的 prompt tokens 计算费用造成按请求体字节数计费的多收。2.3 Issue #1345HandleRequestFinish 多次触发导致重复扣费根因位置tokenRequestFinishHandler()的扣费循环。原逻辑if tokenUsage.UsedCost 0 hasRMBPlan(ctx.Token.QuotaPlans) { tokenUsage.UsedCost m.calcCostUnits(req, ctx.serverConf, tokenUsage) } costUnits : tokenUsage.UsedCost // 扣费循环 for _, plan : range ctx.Token.QuotaPlans { ... plan.Deduct(m.redisClient, costUnits) }UsedCost 0的守卫只能防止重复计算UsedCost但无法防止重复扣费。当HandleRequestFinish被多次触发时第一次调用设置UsedCost X并执行扣费循环Redis 余额减少第二次调用发现UsedCost X 0跳过计算但costUnits X扣费循环再次执行由于 Redis 余额已变化deductRMB的 Lua 脚本math.min(current, amount)会产生不同扣费额形成随机的小额额外扣费。3. 修复方案3.1 修复 Issue #1343移除不必要的 cache 截断改动位置bfe_modules/mod_ai_token_auth/mod_ai_token_auth.go中calcChatCost()的截断分支。删除以下代码if cacheReadTokens promptTokens { cacheReadTokens promptTokens }保留normalInput的非负保护normalInput promptTokens - cacheReadTokens if normalInput 0 { normalInput 0 }理由Anthropic 协议下promptTokens不是总 prompt截断逻辑数学上错误OpenAI / DeepSeek 协议下promptTokens已含 cache截断会低估 cache 用量删除截断后normalInput的非负保护已足够防止负计费。当前仓库中的最终实现印证在calcChatCost()当前位于 mod_ai_token_auth.go中截断逻辑已不存在取而代之的是// cache-aware billing: split cache read/write from prompt. // PromptTokens is the total input (for Anthropic it is normalized to // input_tokens cache read cache write at parse time), so both cache // parts must be removed to get the normal (fresh) input tokens. if cacheReadPrice 0 || cacheWritePrice 0 { normalInput promptTokens - cacheReadTokens - cacheWriteTokens if normalInput 0 { normalInput 0 } }同时calcChatCost入口处对cacheReadTokens、cacheWriteTokens等子项做了 0清零的 sanitize 保护mod_ai_token_auth.go防止上游返回异常负数。3.2 修复 Issue #1344跳过 count_tokens 端点扣费改动位置tokenRequestFinishHandler开头增加 endpoint 白名单检查func (m *ModuleAITokenAuth) tokenRequestFinishHandler(req *bfe_basic.Request, res *bfe_http.Response) int { // 跳过非计费端点Anthropic count_tokens 仅用于 token 计数不应扣费 if strings.Contains(req.HttpRequest.RequestURI, /count_tokens) { return bfe_module.BfeHandlerGoOn } if res nil || res.StatusCode ! bfe_http.StatusOK { return bfe_module.BfeHandlerGoOn } // ... 原有逻辑 }依赖需要在文件头部引入strings包。当前仓库中的最终实现印证该豁免逻辑已落地在 mod_ai_token_auth.go且strings已在 import 区引入mod_ai_token_auth.go。3.3 修复 Issue #1345增加已扣费标记改动位置TokenAuthContext结构体 tokenRequestFinishHandler。在TokenAuthContext中增加deducted标记type TokenAuthContext struct { Token *Token aiBasicInfo *bfe_basic.AiBasicInfo serverConf bfe_basic.ServerDataConfInterface deducted bool // 标记本请求是否已执行过扣费 }在tokenRequestFinishHandler开头和扣费循环后使用该标记func (m *ModuleAITokenAuth) tokenRequestFinishHandler(req *bfe_basic.Request, res *bfe_http.Response) int { // ... 跳过 count_tokens ... if res nil || res.StatusCode ! bfe_http.StatusOK { return bfe_module.BfeHandlerGoOn } ctx : GetTokenAuthContext(req) if ctx nil { return bfe_module.BfeHandlerGoOn } // 已扣费则跳过防止 HandleRequestFinish 多次触发导致重复扣费 if ctx.deducted { return bfe_module.BfeHandlerGoOn } // ... 原有 UsedQuota / UsedCost 计算逻辑 ... if tokenUsage.UsedQuota 0 || costUnits 0 { for _, plan : range ctx.Token.QuotaPlans { // ... 原有扣费逻辑 ... } } ctx.deducted true return bfe_module.BfeHandlerGoOn }理由在请求上下文级别做幂等标记实现简单不引入分布式锁开销且与现有UsedCost守卫互补。当前仓库中的最终实现印证deducted字段定义在 mod_ai_token_auth.go在tokenRequestFinishHandler开头mod_ai_token_auth.go与扣费循环后mod_ai_token_auth.go分别读写。4. 代码变更汇总改动点文件说明删除 cache 截断mod_ai_token_auth.go避免 Anthropic 高 cache 命中场景少收增加strings导入mod_ai_token_auth.go用于count_tokens路径判断跳过count_tokensmod_ai_token_auth.go避免 Anthropic token 计数端点多收增加deducted字段mod_ai_token_auth.go请求级扣费幂等标记增加扣费守卫mod_ai_token_auth.go防止重复扣费5. 测试与验证设计文档给出的测试建议包括单元测试、集成测试与回归测试三层5.1 单元测试在mod_ai_token_auth_test.go中新增calcChatCost测试用例Anthropic 场景CacheReadTokens 1000PromptTokens 50验证 cache 按 1000 计费、normalInput 为 0OpenAI 场景CacheReadTokens 200PromptTokens 1000验证正常拆分计费新增tokenRequestFinishHandler测试模拟RequestURI包含/count_tokens验证不扣费模拟HandleRequestFinish被调用两次验证第二次不执行Deduct。5.2 集成测试使用 Anthropic 真实/模拟响应构造高 cache 命中请求核对计费金额与上游账单一致调用count_tokens端点确认 Redis 余额不变。5.3 回归测试运行make test确保现有 streaming / non-streaming 扣费逻辑不受影响。当前仓库中的测试印证这些测试建议已在仓库中落地为完整用例mod_ai_token_auth_test.goTestCalcCostUnits_AnthropicHighCacheHitL1233-L1258Anthropic 原始 usageinput_tokens320, cache_read8000, cache_write200归一化后PromptTokens8520normal_input8520-8000-200320最终费用320*452 8000*45 200*565 150*2262 956940固定点整数TestCalcCostUnits_CacheReadExceedsPromptL1209-L1231CacheReadTokens10000 PromptTokens8000明确注释 should NOT be truncated (Anthropic semantics)验证删除截断后 cache 按真实命中数计费TestTokenRequestFinishHandler_SkipCountTokensL763-L792构造RequestURI /anthropic/v1/messages/count_tokens与 RMB 配额计划验证处理后 Redis 中无任何扣费记录TestTokenRequestFinishHandler_NoDuplicateDeductionL794-L841模拟HandleRequestFinish触发两次验证第二次调用后 Redis 余额与第一次扣费后完全一致TestTokenRequestFinishHandler_RMB_Cache_NonStreaming/_StreamingL1033-L1129验证 streaming 与非 streaming 两条路径下 cache 拆分计费结果一致normal_input2000费用5087000。6. 兼容性说明计费上升是修正行为删除 cache 截断后Anthropic 高 cache 命中场景的计费会上升这是修正错误少收后的正确行为需要在运营侧提前告知用户历史多收需单独对账count_tokens端点此前多收的费用需在计费对账层单独处理deducted 标记作用域仅影响同一请求生命周期内的重复扣费不跨请求生效不影响正常流量。7. 后续修复issue #1343 的彻底解决Anthropic 协议语义归一化7.1 残留问题第一次修复删除cacheReadTokens promptTokens截断后高 cache 命中场景仍少收。根因是Anthropic 协议语义与 OpenAI 不一致Anthropicusage.input_tokens仅含 cache missfreshtoken不含cache_read_input_tokens与cache_creation_input_tokensOpenAI / DeepSeekusage.prompt_tokens已包含cache 命中部分。设计文档指出三处解析入口UpdateCtxByUsage、SSEEvent.GetQuotaUsage、RawEvent.GetQuotaUsage的 Claude fallback 直接把input_tokens赋给PromptTokens导致calcChatCost中normalInput promptTokens - cacheReadTokens在 cache 命中很高时为 0fresh token 被计费为 0。此外 100% cache 命中input_tokens 0时used 0usage 被误判为 guess 而丢弃。7.2 修复方案解析层归一化Claude fallback 分支中PromptTokens input_tokens cacheRead cacheWrite总输入 token 数与 OpenAIprompt_tokens语义一致UsedQuota PromptTokens CompletionTokens。100% cache 命中场景随之变为可识别不再被当成 guess。计费层拆分calcChatCost中normalInput max(promptTokens - cacheReadTokens - cacheWriteTokens, 0)cache write 从 normal input 中扣除避免 cache creation token 既按输入全价又按 cache 写入价重复计费audio input 的预截断基准同步扣除 cache write。7.3 当前仓库中的落地实现归一化逻辑已在bfe_model_protocol/utils/usage_parse.go的ParseAnthropicUsageFields中实现usage_parse.gofields.PromptTokens prompt.Int() fields.CompletionTokens completion.Int() fields.CacheReadTokens gjson.GetBytes(data, usage.cache_read_input_tokens).Int() ... fields.CacheWriteTokens gjson.GetBytes(data, usage.cache_creation_input_tokens).Int() ... fields.PromptTokens fields.CacheReadTokens fields.CacheWriteTokens fields.UsedQuota gjson.GetBytes(data, usage.total_tokens).Int() ... if fields.UsedQuota 0 { fields.UsedQuota fields.PromptTokens fields.CompletionTokens }该函数同时处理 streaming 场景下message.usage的嵌套结构message_start事件并支持 1h-TTL cache write 的独立字段cache_creation.ephemeral_1h_input_tokens/cache_creation_input_tokens_1husage_parse.go。UpdateCtxByUsage通过协议适配器调用该解析链mod_ai_token_auth.go并在单协议解析失败时回退到ParseUsageFieldsCrossProtocol跨协议组合链usage_parse.go以兼容 auth style 与响应格式不匹配的场景issue #1364。对应测试TestUpdateCtxByUsage_AnthropicCachemod_ai_token_auth_test.go验证input_tokens320, cache_read8000, cache_write200→PromptTokens85203208000200UsedQuota8670100% cache 命中input_tokens0, cache_read5000, output_tokens42→PromptTokens5000UsedQuota5042usage 不再被误判为 guess。7.4 影响Anthropic 高 cache 命中fresh token 恢复按输入全价计费cache read 按cache_read_input_token_cost计费cache write 按cache_creation_input_token_cost计费与上游账单一致OpenAI / DeepSeekcacheWriteTokens恒为 0计费行为不变未配置 cache 价格时回退公式promptTokens * inputCost因PromptTokens归一化为总输入而变得更准确此前对 Anthropic 只按 fresh 部分计费。附模块配置与数据文件速览理解上述修复后可结合以下配置文件快速上手模块主配置 conf/mod_ai_token_auth/mod_ai_token_auth.conf包含[basic]ProductRulePath、[redis]BNS 地址、连接/读写超时、连接池大小、[log]OpenDebug三节配置校验逻辑见 conf_mod_ai_token_auth.goToken 规则数据 conf/mod_ai_token_auth/token_rule.data定义Config条件规则动作固定为CHECK_TOKEN、Tokenskey/key_id/过期时间/额度计划与QuotaPlans完整字段定义见 token.go扣费原子性依赖 Redis Lua 脚本Token 额度用DECRBYmath.min截断token.goRMB 额度用固定点整数1e-8元并通过SETDECRBY原子扣减token.go计费价格来自集群配置的模型价格表cluster_conf.ModelPrice字段如PriceInputCostPerToken、PriceCacheReadInputTokenCost、PriceCacheCreationInputTokenCost等由calcCostUnits在请求结束时按 cluster/model/mode 查找mod_ai_token_auth.go。当价格表缺失时按设计计费 0 并通过PriceLookupMiss计数器暴露监控信号issue #1382避免按错误模式的价格静默计费。总结这三处计费问题的修复遵循最小改动原则——删除一个数学上错误的截断、增加一个 endpoint 豁免、增加一个请求级幂等标记并辅以解析层的协议语义归一化。当前仓库源码与测试用例已完整落地设计文档中的全部方案可作为 AI 网关类项目计费模块设计的参考范本。赞分享后端网络/通信云原生【免费下载链接】bfeA modern layer 7 load balancer from baidu项目地址https://gitcode.com/gh_mirrors/bf/bfe点击查看免费下载相关推荐draw.io 桌面版 Windows 安装指南三步装好十分钟画出第一张图draw.io 桌面版 Windows 安装指南三步装好十分钟画出第一张图 离线审计、内网隔离的场景下网页版画图行不通。draw.io 桌面版是官方用 E后端网络/通信云原生BFE mod_ai_token_auth 模块基础配置详解API Key 鉴权与配额扣减的 Redis 联动BFE mod_ai_token_auth 模块基础配置详解API Key 鉴权与配额扣减的 Redis 联动 导读 mod_ai_token_auth 是后端网络/通信云原生Select2 4.0 系列演进全解析从 4.0.0 到 4.0.13 的变更日志导读与 Dolibarr 集成实践Select2 4.0 系列演进全解析从 4.0.0 到 4.0.13 的变更日志导读与 Dolibarr 集成实践 Select2 是经典的 jQuery后端网络/通信云原生上一篇Windows驱动存储清理终极指南如何用DriverStoreExplorer释放宝贵磁盘空间下一篇OneMore插件如何将你的OneNote打造成终极笔记管理利器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考