ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大模型API成本核算实战:按官网原样建模的比价目录设计

大模型API成本核算实战:按官网原样建模的比价目录设计 做 API 成本核算这件事我前前后后折腾了快两个月。起因很简单团队要选一个主力模型接入生产环境候选名单拉出来一看十几家厂商、几十个模型版本、每家的计费口径还都不一样——有的按 token 分段计价有的把输入输出拆开算有的搞阶梯折扣还有的订阅套餐里塞了一堆每月赠送额度但实际限制条件写在文档最底部。我拿着计算器对着各家官网算了一下午算到第三家就乱了因为上一家的价格规则我已经记混了。后来我干脆做了个比价目录核心思路只有一条价格规则按官网原样建模不做任何简化和归一化。这个决定听起来很笨但恰恰是它让整个目录变得可信、可维护、可复现。下面我把整个建模过程、踩过的坑、以及为什么某些看似聪明的做法反而会害了你完整讲一遍。1. 为什么按官网原样建模是唯一正确的起点1.1 归一化是个陷阱我最初想做的统一单位错在哪我一开始的想法很自然把所有厂商的价格统一换算成每百万 token 多少元然后拉个表格排序谁便宜谁贵一目了然。这个思路在写第一版代码的时候就被我自己否掉了原因有三个。第一计费单位根本不统一。有的厂商按每千 token报价有的按每百万 token有的按每次调用还有的按字符数对真的有。你强行换算成同一个单位表面上整齐了但换算过程中引入的舍入误差和口径偏差会让最终数字失去参考价值。比如某家按千 token 报价 0.001 元换算成百万 token 是 1 元看起来没问题但另一家按百万 token 报价 0.8 元同时有个单次调用最低计费 100 token的规则这个规则在归一化之后完全消失了。第二输入和输出的价格差异巨大。很多模型输入价格和输出价格能差 3 到 5 倍你把它们合并成一个综合单价等于把最重要的成本结构信息抹掉了。实际使用中一个摘要类应用的输入输出比可能是 10:1一个对话类应用可能是 1:1一个代码生成应用可能是 1:5。用综合单价去选型选出来的结果大概率是错的。第三阶梯定价和套餐折扣无法用单一数字表达。某家厂商月用量超过 1 亿 token 后单价打七折另一家预充值 5000 元送 20% 额度。这些规则你用每百万 token X 元根本表达不出来但它们对实际成本的影响可能超过 30%。所以我的结论是比价目录的第一性原理不是给出一个价格数字而是忠实记录价格规则。用户要的不是一个排序结果而是一个能自己代入用量、自己算账的工具。归一化看起来省事实际上是把复杂度从系统转移到了用户的脑子里而且转移过程中还丢了信息。1.2 价格规则的四种基本形态在建模之前我先把市面上能见到的计费方式梳理了一遍归纳成四种基本形态。这个分类是整个数据模型的基础后面所有的表结构设计都围绕它展开。形态典型表现建模难点线性单价每百万 token 固定价格单位换算、输入输出分离分段阶梯0-100万一个价100万-1000万另一个价区间边界、累进还是全额订阅套餐月付固定费用含一定额度额度是否结转、超额单价附加规则最低计费、缓存折扣、批量折扣规则的触发条件和优先级这里要特别说一个容易搞混的点阶梯定价分累进和全额两种。累进是指超过 100 万的那部分按新价格算前 100 万还是老价格全额是指一旦超过 100 万全部用量都按新价格算。这两种算法在临界点附近的成本差异可能达到百分之十几而很多厂商的文档里根本没写清楚是哪种。我的做法是在数据里加一个字段tier_mode取值progressive或flat遇到文档没写明的标注为unknown并在展示时给出两种算法的结果。1.3 数据模型设计三张表撑起整个目录最终我用了三张核心表来建模结构不复杂但足够表达上面四种形态。第一张是providers表存厂商基本信息厂商 ID、名称、官网地址、文档地址、最后核对日期。最后核对日期这个字段非常重要因为价格是会变的用户需要知道这个数据是什么时候的。第二张是models表存模型信息模型 ID、所属厂商、模型名称、上下文长度、是否支持多模态、模型状态在售/下线/预览。模型状态这个字段是我后来加的因为有些模型官网还在但已经停止新用户接入了如果不标注用户照着目录去申请会发现根本开不了。第三张是pricing_rules表这是核心。每条记录代表一条计费规则字段包括规则 ID、关联模型、计费类型线性/阶梯/套餐/附加、方向输入/输出/缓存、单位千 token/百万 token/次、价格、阶梯区间下限、阶梯区间上限、阶梯模式、生效条件、备注。CREATE TABLE pricing_rules ( rule_id INTEGER PRIMARY KEY, model_id INTEGER NOT NULL, billing_type TEXT NOT NULL, -- linear / tiered / subscription / surcharge direction TEXT NOT NULL, -- input / output / cache unit TEXT NOT NULL, -- per_1k / per_1m / per_call price REAL NOT NULL, tier_min INTEGER, -- 阶梯下限单位 token tier_max INTEGER, -- 阶梯上限NULL 表示无上限 tier_mode TEXT, -- progressive / flat / unknown condition TEXT, -- 生效条件描述 note TEXT, verified_at TEXT NOT NULL );这个设计的妙处在于任何一条官网上的价格描述都能拆成一条或多条pricing_rules记录不需要为每家厂商写特殊逻辑。比如输入每百万 token 2 元输出每百万 token 8 元月用量超 1 亿后输入输出均打八折就是三条记录两条线性规则加一条带condition的折扣规则。2. 把官网价格翻译成结构化数据的实操细节2.1 从官网文档到数据录入的完整流程录入一条价格数据我固定走五步一步都不能省。第一步定位官方定价页。注意是定价页不是产品页很多厂商的产品页上写的价格是营销话术比如低至 0.001 元/千 token这个低至背后往往有前提条件。真正的价格规则在定价页或者计费文档里。第二步截图存档。我会把定价页完整截图按厂商名_日期命名存到本地。这个习惯救过我好几次——有一次某厂商悄悄改了价格用户质疑我的数据不准我把两周前的截图翻出来证明我录入时官网确实是那个价是厂商后来改的。第三步逐条拆解规则。把页面上的每一句话拆成独立的计费规则。比如新用户注册赠送 100 万 token有效期 30 天是一条规则超出赠送额度后按标准价格计费是另一条规则。拆的时候要问自己这条规则的触发条件是什么它和别的规则是叠加还是互斥第四步标注不确定项。凡是文档里没写清楚的一律标unknown绝不自己猜。比如前面说的阶梯模式文档没写累进还是全额我就标unknown。宁可展示时多给一个说明也不自己脑补一个可能错误的答案。第五步交叉验证。如果厂商同时提供了价格计算器我会用几个典型用量去跑一遍计算器看结果和我按规则手算的是否一致。不一致的地方就是规则理解有偏差的地方重点排查。2.2 单位换算的坑千 token、百万 token 和字符单位换算看起来是最简单的实际上坑最多。我遇到过的单位有每千 token、每百万 token、每千字符、每百万字符、每次调用、每秒钟。其中字符和 token 的换算最麻烦因为不同模型的分词器不一样中文一个字可能是 1 到 2 个 token英文一个单词可能是 1 到 3 个 token。我的处理方式是数据层保留官网原始单位展示层做换算。也就是说pricing_rules表里存的是官网写的单位比如某家写每千字符 0.002 元我就存unit per_1k_char价格 0.002。展示的时候如果用户想看每百万 token的价格我再按一个可配置的换算系数去算并且明确标注此换算基于 1 token ≈ 1.5 字符的估算实际以官网为准。这样做的好处是数据永远忠实于官网换算只是展示层的一个视图。如果哪天换算系数需要调整改一个配置就行不用动数据。提示字符和 token 的换算系数不要写死。我一开始写死了 1.5后来发现对某些模型偏差很大改成了按模型可配置默认 1.5特殊模型单独设置。2.3 套餐类规则的建模额度、有效期和结转订阅套餐是最难建模的部分因为它涉及三个维度的组合额度大小、有效期、是否结转。额度大小好办就是一个数字。有效期也好办就是天数。麻烦的是结转——这个月没用完的额度下个月还能不能用我见过三种情况不结转月底清零、全额结转永久累积、部分结转最多结转一个月。还有一个隐藏维度套餐额度是只覆盖输入还是输入输出都覆盖。有的套餐说每月 1000 万 token实际是输入输出合计有的说每月 1000 万输入 token输出另算。这个差异对成本影响巨大因为输出通常比输入贵。我的建模方式是在pricing_rules里用billing_type subscription的记录存套餐基本信息然后用condition字段描述结转规则和覆盖范围。展示时套餐类模型会额外显示一个套餐外单价也就是超出额度后的计费标准。套餐维度可能的取值对成本的影响额度范围仅输入 / 仅输出 / 输入输出合计高输出贵时差异大结转规则不结转 / 全额结转 / 部分结转中取决于用量波动超额单价标准价 / 折扣价 / 阶梯价高超额部分往往是大头有效期自然月 / 30天 / 按购买日低但影响对账2.4 附加规则的优先级谁先算谁后算附加规则最容易被忽略但它决定了最终账单。常见的附加规则有最低计费单次调用不足 X token 按 X 算、缓存命中折扣命中缓存的输入按折扣价、批量折扣单次请求超过一定量打折、新用户优惠首月折扣。这些规则之间是有优先级的。比如一个请求既命中了缓存又满足了批量折扣条件是按缓存折扣算还是按批量折扣算还是两个叠加大部分厂商文档不会写这么细我的做法是在condition字段里用自然语言描述规则并在展示时把所有可能适用的规则都列出来让用户自己判断。如果厂商提供了计算器以计算器结果为准。这里有个实操心得附加规则一定要在备注里写清楚此规则未在官网明确说明优先级实际以账单为准。我吃过这个亏早期版本没写这句话有用户按我的目录算出来是 50 元实际账单 65 元跑来质疑数据不准。后来加了这句免责说明类似的争议就没了。3. 比价目录的展示逻辑让用户自己算账3.1 为什么我不做一键排序 cheapest first这是我在产品设计上最重要的一个决定。绝大多数比价工具都会做一个按价格排序的功能用户输入用量工具输出一个从便宜到贵的排名。我一开始也做了但很快就发现这个功能在误导用户。原因在于排名依赖的假设太多了。用户输入每月 1000 万 token这个 1000 万是输入还是输出输入输出比例是多少有没有命中缓存用量是均匀分布还是集中在某几天这些假设每一个都会显著影响排名结果。工具替用户做了假设用户看到排名就以为那是事实实际上那只是在某个特定假设下的结果。所以我改成了参数化展示用户输入自己的用量画像输入 token 数、输出 token 数、缓存命中率、月用量分布目录实时计算每个模型在这个画像下的成本并且把计算过程展开给用户看。用户能看到你的输入成本是 X输出成本是 Y缓存折扣省了 Z最终合计 W。这样用户不仅知道结果还知道结果是怎么来的可以自己调整假设看变化。3.2 成本计算器的实现逻辑计算器的核心是一个纯函数输入是用量画像和模型 ID输出是成本明细。逻辑不复杂但边界条件要处理干净。def calc_cost(model_id, profile): rules load_rules(model_id) cost {input: 0.0, output: 0.0, cache: 0.0, subscription: 0.0} detail [] # 先算基础用量成本 for rule in rules: if rule.billing_type linear: if rule.direction input: amount profile.input_tokens * rule.price / unit_divisor(rule.unit) cost[input] amount detail.append(f输入: {profile.input_tokens} token × {rule.price}/{rule.unit}) elif rule.direction output: amount profile.output_tokens * rule.price / unit_divisor(rule.unit) cost[output] amount detail.append(f输出: {profile.output_tokens} token × {rule.price}/{rule.unit}) # 再算阶梯调整 for rule in rules: if rule.billing_type tiered: # 根据 tier_mode 决定累进还是全额 ... # 最后算附加规则 for rule in rules: if rule.billing_type surcharge: # 检查 condition 是否满足 ... return cost, detail这里的关键是计算顺序先算基础线性成本再应用阶梯调整最后应用附加规则。这个顺序不是随便定的它对应了大多数厂商账单的实际计算流程。如果你先算附加规则再算阶梯结果可能不一样。3.3 展示层的透明度设计我在展示上坚持一个原则任何一个数字用户都能点开看到它是怎么来的。具体做法是每个成本数字旁边有个展开按钮点开显示计算明细包括用了哪条规则、代入的数值是多少、中间结果是什么。这个设计增加了开发量但极大提升了可信度。用户看到输入成本 12.5 元的时候如果不知道这 12.5 是怎么来的他会怀疑但如果他能看到1000 万 token × 1.25 元/百万 token 12.5 元他就信了。另外所有unknown的字段在展示时都会用醒目的方式标注比如阶梯模式未知的会显示此阶梯的计费模式官网未明确以下按累进和全额两种方式分别计算累进 X 元全额 Y 元。用户看到这个就知道这里有个不确定性会自己去官网确认。4. 维护一个会过期的目录数据更新机制4.1 价格变动是常态不是例外做这个目录之前我以为价格是稳定的做了之后才发现大模型 API 的价格变动频率远超我的预期。我统计过自己维护的这几十个模型平均每个月有 3 到 5 个模型会调整价格有的是降价有的是调整阶梯区间有的是改套餐规则。所以目录的核心挑战不是第一次录入而是持续维护。如果维护跟不上目录很快就会变成错误信息的集合比没有还糟糕。我的维护机制是这样的每个模型记录一个verified_at字段表示最后一次核对日期。目录首页会显示数据最后更新于 X 月 X 日并且对超过 30 天未核对的模型打上待核对标记。我自己每周花一个小时随机抽查几个模型看看官网有没有变。如果变了更新数据并刷新verified_at。4.2 用户反馈是最好的更新触发器我一个人不可能盯住所有厂商的所有变动所以用户反馈非常重要。我在目录里加了个报告价格变动的入口用户发现哪个模型价格不对可以提交反馈。反馈会带上用户看到的官网截图和当前目录数据的对比我核对后更新。这个机制运行下来效果不错很多价格变动都是用户先发现的。为了鼓励反馈我在目录里公示了最近由用户反馈更新的模型列表算是一种认可。4.3 历史价格的价值不只是当前价维护过程中我发现历史价格数据其实很有价值。用户选型的时候不仅关心现在多少钱还关心这个价格稳定吗过去半年降过几次价。一个频繁降价的厂商可能意味着它的成本在下降未来还有降价空间一个价格一直不动的厂商可能意味着它的定价策略比较稳定。所以我在数据模型里加了价格历史表每次更新价格时不覆盖旧数据而是新增一条记录并标记生效日期。展示时可以画一个简单的价格走势让用户看到这个模型过去几个月的价格变化。CREATE TABLE price_history ( history_id INTEGER PRIMARY KEY, rule_id INTEGER NOT NULL, old_price REAL, new_price REAL NOT NULL, changed_at TEXT NOT NULL, source TEXT, -- official / user_report note TEXT );这个表的数据量不大但给目录增加了一个很有价值的维度。有用户跟我说他就是看了价格走势才决定选某家的因为那家过去半年降了三次价说明成本控制得好未来大概率还会降。5. 踩过的坑和几条硬核经验5.1 免费额度是最容易出错的地方免费额度看起来简单实际上是最容易录错的地方。我踩过的坑包括把新用户一次性赠送录成了每月赠送把仅限特定模型的免费额度录成了全模型通用把有效期 30 天漏掉了导致用户以为永久有效。现在的做法是免费额度一律用condition字段详细描述包括发放方式一次性/每月/活动、适用范围全模型/指定模型、有效期天数/自然月/永久、是否可叠加。展示时这些条件全部显示出来不做任何省略。5.2 别信低至和起官网上的低至 X 元和X 元起这两个词背后一定有前提条件。我见过低至 0.0005 元/千 token实际是一次性购买 10 亿 token 的批量价也见过0.001 元起实际是仅限特定轻量模型且不含输出。我的处理方式是凡是带低至起的价格一律不作为标准价格录入而是作为附加规则录入并在备注里写清楚触发条件。标准价格只录官网明确写出的、无前提条件的价格。5.3 币种和税费容易被忽略的成本项大部分国内厂商用人民币报价但也有用美元的。用美元的厂商实际支付时还有汇率和可能的跨境手续费。另外报价是否含税也是个问题有的厂商报价不含税实际开票要加 6% 或 13%。这些在目录里都要标注。我的做法是加一个currency字段和tax_included字段展示时如果是不含税价格会额外显示此价格不含税实际支付需加 X%。5.4 上下文长度对价格的影响有些模型对不同上下文长度的请求收费不同比如 32K 以内一个价32K 到 128K 另一个价。这个规则很容易被忽略因为它在定价页上往往写得很小。我的处理方式是在pricing_rules里用condition字段描述上下文长度限制展示时如果用户输入的上下文长度超过了某个阈值会提示你的请求上下文长度超过 X适用更高的单价。常见坑表现我的处理方式免费额度条件一次性 vs 每月适用范围condition 字段详细描述低至价格有批量或模型限制作为附加规则不录为标准价币种税费美元报价、不含税currency tax_included 字段上下文长度长上下文单价更高condition 描述 展示提示阶梯模式累进 vs 全额未写明标 unknown两种都算5.5 一个反直觉的经验数据准确性比功能丰富更重要做这个目录的过程中我一度想加很多功能价格预测、用量优化建议、多模型成本对比报告等等。但后来我砍掉了大部分只保留了最核心的比价和计算功能。原因是我发现用户对比价工具的核心诉求是准不是全。一个功能简单但数据准确的工具比一个功能丰富但数据有错的工具价值高得多。我见过太多比价工具因为数据不准被用户抛弃功能再多也没用。所以我现在把 80% 的精力放在数据核对上20% 放在功能上。每次加新功能之前先问自己这个功能会不会让用户对数据的准确性产生怀疑如果会就不加。6. 如果你也想做一个类似的目录6.1 从最小可用版本开始不要一上来就想着覆盖所有厂商所有模型。我的建议是先选 5 到 10 个你最熟悉的模型把数据模型跑通把展示逻辑跑通把更新机制跑通。这个过程会暴露很多你在设计阶段想不到的问题比如单位换算的边界、套餐规则的复杂度、展示层的性能。等这 10 个模型的数据你能稳定维护一个月不出错再考虑扩展。扩展的时候数据模型基本不用改只是加数据而已。6.2 数据模型要留扩展位我在设计pricing_rules表的时候特意留了几个现在没用上的字段比如region地域有些厂商不同地域价格不同、commitment承诺用量有些厂商要求承诺最低消费才给折扣。这些字段现在都是空的但将来遇到对应规则时可以直接用不用改表结构。留扩展位的原则是凡是你能预见到可能出现的规则维度都留一个字段。字段空着不占成本但需要的时候没有改表结构就很麻烦。6.3 展示层要诚实最后说一个价值观层面的经验。做比价目录最容易犯的错误是为了让结果好看而简化规则。比如把复杂的阶梯定价简化成一个平均价把有条件的免费额度当成无条件的把不含税价格当成含税价格。这些简化会让目录看起来更干净但会误导用户。我的原则是宁可展示得复杂一点也要保证信息完整。用户看到复杂的信息会自己去判断用户看到被简化的信息会以为那就是全部真相。前者是帮助用户后者是误导用户。这个原则贯穿了整个目录的设计不做归一化、不做默认假设、不隐藏条件、不省略不确定性。看起来很笨但正是这种笨让目录变得可信。我在实际维护中最大的体会是做这类工具技术难度其实不高难的是持续投入的耐心和对准确性的坚持。价格会变规则会变厂商会变唯一不变的是用户对准确信息的需求。谁能持续提供准确的信息谁的工具就有价值。
返回列表