尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI模型token费用对比:从价格归一化到比价网站实战

AI模型token费用对比:从价格归一化到比价网站实战 之前一直在用 Codex 做 AI 编程最头疼的就是 token 消耗太快。明明只是自动补全、跑个测试一天下来账单数字就让人肉疼。更麻烦的是OpenAI、Anthropic、DeepSeek、Gemini 这些模型家族的定价口径完全不一样有的按百万 token 算有的按千 token 算输入输出价格差异又大还有缓存命中、上下文缓存这些细碎计费项。每次估算项目成本都要翻好几个官网换算半天。后来我花了不少时间把主流 AI 模型的公开定价、计费口径和常见中转方式整理到一起做了一个免费的 AI 充值比价网站。核心功能很简单把不同平台的 token 单价拉到同一个口径下用户输入一个场景直接估算大概消耗多少钱并对比各家方案。这篇文章把整个项目从数据采集、价格归一化、费用计算引擎到前后端部署的完整过程拆开讲适合想做 AI 工具、或者自己开发中需要做成本测算的开发者。即便你不做网站里面的价格计算模型和接口设计思路也可以直接用到自己的项目里。1. 为什么需要 AI 价格比价1.1 痛点来源Codex 与 token 计费Codex 是 OpenAI 推出的 AI 编程代理它和普通聊天机器人不太一样会自主完成代码编写、命令执行、测试反馈等一系列操作。正因为它是“代理式”工作一次任务可能触发很多轮模型调用token 消耗会成倍增长。比如一个很小的代码重构聊天式工具可能只消耗 5000 token但 Codex 从解析代码、生成补丁、执行命令到分析报错整体消耗可能达到几万甚至十几万 token。这带来一个很现实的问题token 用量很难预估。你在开工之前根本不知道这个任务会花多少钱。而不同平台的计费差异非常大某款模型的输入价格可能是另一款的十几倍。如果只是无脑选贵的模型项目成本会迅速失控。1.2 定价口径不统一我做比价网站最核心的障碍不是数据难找而是“单位不统一”。有的平台按“每 1000 token”计费比如 0.03 美元/1K tokens。有的平台按“每 100 万 token”计费比如 3 美元/1M tokens。输入价格和输出价格通常不一样。缓存命中cached input价格往往远低于常规输入价格。还有的模型支持提示词缓存写入缓存也要计费。如果不把这些口径全部转换成同一个基准比如统一为“美元 / 每 100 万 token”比较起来完全没有意义。1.3 网站要解决什么问题这个网站的目标非常具体把主流模型的价格统一到同一计费口径支持快速查询。输入“任务类型 预估 token 量”自动估算成本。对比不同供应商在相同场景下的成本差异。提供免费的查询和估算能力供开发者日常使用。这篇文章记录的就是从零实现这个比价网站的过程重点讲数据模型设计、价格归一化逻辑、费用计算引擎的代码实现以及部署上线时遇到的典型问题。2. 整体架构与功能拆解2.1 项目范围既然是个人开发维护的免费工具架构不能搞得太重。最终采用前后端分离的轻量方案后端Python FastAPI负责价格数据查询和费用估算接口。前端原生 HTML JavaScript也可以替换成 Vue但纯静态放在服务器上更省成本。数据存储SQLite保存价格数据和更新记录。定时任务APScheduler 或系统 cron定期更新价格快照。这个方案的好处是服务器资源占用非常小一台低配云主机就能跑起来方便长期免费提供给大家使用。2.2 核心功能模块整个网站拆成下面几个模块模块职责关键点数据采集模块收集官方公开价格数据优先使用公开页面/JSON人工校验价格归一化模块将不同单位统一为 USD/1M tokens核心逻辑不能出错费用估算引擎根据 token 量估算调用成本区分输入/输出/缓存API 层对外提供查询接口支持列表查询、价格估算前端展示模型价格表和成本计算器简单清晰移动端可用价格快照记录历史价格为后续价格趋势提供数据2.3 目录结构项目源码目录规划如下后面所有代码都围绕这个结构展开ai-price-compare/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── database.py # SQLite 连接与建表 │ ├── models.py # Pydantic 数据模型 │ ├── price_service.py # 价格查询服务 │ ├── estimate_service.py # 费用估算引擎 │ └── scheduler.py # 定时更新任务 ├── data/ │ ├── prices.json # 原始价格数据 │ └── price_history.db # SQLite 数据库 ├── frontend/ │ ├── index.html # 首页 │ ├── app.js # 前端逻辑 │ └── style.css # 样式 ├── scripts/ │ └── update_prices.py # 手动更新价格脚本 └── requirements.txt接下来我重点讲三个核心部分数据模型、价格归一化、费用估算引擎。3. 数据模型与存储设计3.1 价格表结构价格记录需要支持不同模型、不同供应商、不同计费类型。一张表如果只存“model、price”两个字段完全不够用。我设计了下面这个表结构CREATE TABLE IF NOT EXISTS model_prices ( id INTEGER PRIMARY KEY AUTOINCREMENT, provider TEXT NOT NULL, -- 供应商例如 openai model_name TEXT NOT NULL, -- 模型名称例如 gpt-4o price_type TEXT NOT NULL, -- input / output / cached_input / cache_write price_per_1m REAL NOT NULL, -- 每 100 万 token 价格单位美元 currency TEXT DEFAULT USD, -- 币种 effective_date TEXT NOT NULL, -- 生效日期 source_url TEXT, -- 来源地址 updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_model ON model_prices(model_name);这里有几个字段需要特别说明price_type用来区分计费类型。同一种模型输入、输出、缓存命中的价格差异非常大必须分开存。effective_date表示该价格从什么时候开始生效。AI 模型降价特别频繁保留生效日期后续才能做价格趋势。source_url是价格来源链接方便审计和人工核对。3.2 Python 数据模型在应用层我用 Pydantic 定义模型方便做接口参数校验# 文件路径app/models.py from typing import Literal from pydantic import BaseModel class PriceRecord(BaseModel): provider: str model_name: str price_type: Literal[input, output, cached_input, cache_write] price_per_1m: float currency: str USD effective_date: str source_url: str class EstimateRequest(BaseModel): model_name: str input_tokens: int 1000 output_tokens: int 500 cached_input_tokens: int 0 cache_write_tokens: int 0 class EstimateResult(BaseModel): model_name: str total_cost_usd: float input_cost_usd: float output_cost_usd: float cached_input_cost_usd: float cache_write_cost_usd: float detail: str PriceRecord用于价格数据写入和查询EstimateRequest是费用估算接口的请求体EstimateResult是返回给前端的估算结果。3.3 为什么必须区分 cached_input 和 cache_write很多开发者做 token 成本计算时只考虑“输入 输出”忽略了缓存逻辑。但实际调用中如果用了 prompt caching缓存命中的输入价格可能只有常规输入价格的 25% 甚至 10%而写入缓存的费用是单独计算的。举个例子普通输入3 美元/1M tokens缓存命中输入0.3 美元/1M tokens缓存写入3.75 美元/1M tokens如果在计算时不区分这三者你估算的成本可能比实际成本高出好几倍。这在需要频繁调用同一批系统指令、上下文模板的 AI 编程场景中特别明显。4. 价格归一化逻辑4.1 为什么要归一化不同平台公布价格时单位差异很大某平台写$0.00003 / 1K tokens另一平台写$3.00 / 1M tokens还有的平台写¥0.015 / 1K tokens如果直接把价格乘进代码里很容易算错数量级。所以我写了一个normalize_price函数统一转换为“美元 / 每 100 万 token”也就是USD per 1M tokens。# 文件路径app/price_service.py from typing import Tuple def normalize_price( price_value: float, unit_tokens: int 1000, currency: str USD, usd_rate: float 1.0 ) - float: 将任意平台公布的价格归一化为 USD / 1M tokens。 :param price_value: 原始价格数字 :param unit_tokens: 该价格对应的 token 数量默认 1000 :param currency: 原始币种 :param usd_rate: 该币种兑美元汇率默认 1.0 :return: 归一化后的 USD / 1M tokens 价格 # 先换算成每 1 个 token 的美元价格 price_per_token price_value * usd_rate / unit_tokens # 再换算成每 1M token 的价格 return round(price_per_token * 1_000_000, 6) def normalize_cny_to_usd(price_cny_per_1k: float, usd_rate: float) - float: 示例如果是人民币价格先按汇率换算。 这个函数只是示例思路实际汇率需要从接口或配置获取。 price_usd_per_1k price_cny_per_1k / usd_rate return normalize_price(price_usd_per_1k, unit_tokens1000, currencyUSD)写进数据库前我还会做一次边界检查防止负数和明显异常的大数污染数据def validate_price(value: float) - bool: if value 0: return False if value 1000: # 每 1M token 超过 1000 美元的项目需要人工复核 return False return True4.2 构建模型价格字典处理完归一化之后为了方便查询我在内存里维护一份模型价格字典结构如下{ gpt-4o: { provider: openai, input: 2.50, output: 10.00, cached_input: 0.30, cache_write: 3.75 }, claude-3-5-sonnet: { provider: anthropic, input: 3.00, output: 15.00, cached_input: 0.30, cache_write: 3.75 }, deepseek-chat: { provider: deepseek, input: 0.27, output: 1.10, cached_input: 0.07, cache_write: 1.10 } }注意真实价格随时间变化很快上文这些价格只用于演示数据结构实际部署一定要以官方最新公布的价格为准。千万不要把网上某一个时间点的价格写死进代码。4.3 数据更新策略价格数据更新的核心原则是“官方公开半自动维护”。全自动爬取官方定价页面风险较高因为很多网站有反爬策略页面结构频繁变化而且模型价格调整需要人工确认。我的做法是定期从官方公开的 pricing 页面或 JSON 文件抓取价格数据。抓取完之后先经过自动校验再通过一个管理后台或者手动脚本审核入库。如果抓取失败保留上一次成功的数据不中断服务。手动更新脚本示意如下# 文件路径scripts/update_prices.py import json import sqlite3 # 这里只是示意实际需要从官方源拉取 RAW_PRICES [ { provider: openai, model_name: gpt-4o, input: 2.50, output: 10.00, cached_input: 0.30, cache_write: 3.75 } ] def update_prices(conn: sqlite3.Connection, prices: list[dict]) - None: for item in prices: for price_type in [input, output, cached_input, cache_write]: if price_type not in item: continue conn.execute( INSERT INTO model_prices (provider, model_name, price_type, price_per_1m, effective_date) VALUES (?, ?, ?, ?, date(now)) , (item[provider], item[model_name], price_type, item[price_type]) ) conn.commit() if __name__ __main__: conn sqlite3.connect(data/price_history.db) update_prices(conn, RAW_PRICES) conn.close()5. 费用估算引擎5.1 计费公式费用估算的逻辑并不复杂但很容易算漏。核心公式是总费用 输入 token 数 × 输入单价 缓存命中 token 数 × 缓存命中单价 缓存写入 token 数 × 缓存写入单价 输出 token 数 × 输出单价这里有一个容易忽略的地方如果请求命中了 prompt 缓存那一部分 token 不应该再按普通输入价格计费。也就是说普通输入和缓存命中输入要分开计算不能把input_tokens和cached_input_tokens混在一起。5.2 估算引擎实现# 文件路径app/estimate_service.py from app.models import EstimateRequest, EstimateResult from app.price_service import get_price_map def estimate_cost(req: EstimateRequest) - EstimateResult: 根据模型名称和 token 用量估算费用。 price_map get_price_map() model price_map.get(req.model_name) if not model: return EstimateResult( model_namereq.model_name, total_cost_usd0, input_cost_usd0, output_cost_usd0, cached_input_cost_usd0, cache_write_cost_usd0, detailf未找到模型 {req.model_name} 的价格请检查模型名称。 ) input_cost req.input_tokens / 1_000_000 * model.get(input, 0) output_cost req.output_tokens / 1_000_000 * model.get(output, 0) cached_input_cost req.cached_input_tokens / 1_000_000 * model.get(cached_input, 0) cache_write_cost req.cache_write_tokens / 1_000_000 * model.get(cache_write, 0) total_cost input_cost output_cost cached_input_cost cache_write_cost return EstimateResult( model_namereq.model_name, total_cost_usdround(total_cost, 6), input_cost_usdround(input_cost, 6), output_cost_usdround(output_cost, 6), cached_input_cost_usdround(cached_input_cost, 6), cache_write_cost_usdround(cache_write_cost, 6), detail估算结果仅供参考实际费用以供应商账单为准。 )这里的关键是统一用/ 1_000_000把 token 数量换算成“百万 token”再乘以price_per_1m。这个口径一旦统一整个系统就不会出现数量级错误。5.3 场景示例假设我们要对比在 Codex 中接入不同模型时重构一个模块的成本。场景参数如下输入上下文200,000 tokens其中缓存命中150,000 tokens缓存写入20,000 tokens输出5,000 tokens调用estimate_cost之后前端会展示一张对比表。不同模型的成本差异会非常直观。比如某些模型常规输入价格高但缓存命中价格很低适合高频复用上下文的任务有些模型输出价格便宜适合大量生成代码的场景。6. API 与前端部署实战6.1 FastAPI 接口API 层只需要两个接口模型价格列表查询、费用估算。# 文件路径app/main.py from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.models import EstimateRequest, EstimateResult from app.price_service import list_prices from app.estimate_service import estimate_cost app FastAPI(titleAI Price Compare API, version1.0.0) app.add_middleware( CORSMiddleware, allow_origins[*], # 生产环境建议收紧 allow_methods[GET, POST], allow_headers[*], ) app.get(/api/price/list) def get_price_list(): return list_prices() app.post(/api/price/estimate, response_modelEstimateResult) def get_cost_estimate(req: EstimateRequest): return estimate_cost(req)要注意allow_origins[*]只适用于开发环境正式上线建议改成自己的域名避免被任意页面跨域调用。6.2 前端页面前端我采用了最朴素的方式一个输入区域 一个结果表格。用户选择模型、输入 token 参数点击估算前端把请求发到后端接口并渲染结果。核心 JS 逻辑如下// 文件路径frontend/app.js async function loadPrices() { const res await fetch(/api/price/list); const data await res.json(); const select document.getElementById(modelSelect); data.forEach(item { const option document.createElement(option); option.value item.model_name; option.textContent ${item.model_name} (${item.provider}); select.appendChild(option); }); } async function estimate() { const modelName document.getElementById(modelSelect).value; const inputTokens parseInt(document.getElementById(inputTokens).value) || 0; const outputTokens parseInt(document.getElementById(outputTokens).value) || 0; const cachedInputTokens parseInt(document.getElementById(cachedInputTokens).value) || 0; const cacheWriteTokens parseInt(document.getElementById(cacheWriteTokens).value) || 0; const res await fetch(/api/price/estimate, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ model_name: modelName, input_tokens: inputTokens, output_tokens: outputTokens, cached_input_tokens: cachedInputTokens, cache_write_tokens: cacheWriteTokens }) }); const result await res.json(); renderResult(result); }前端页面不需要做太复杂的交互重点是清晰展示模型名称输入成本输出成本缓存命中成本总成本我还在页面底部加了免责声明提醒用户估算结果仅供参考实际价格以各家官网为准。6.3 本地运行在项目根目录安装依赖并启动pip install -r requirements.txt uvicorn app.main:app --host 0.0.0.0 --port 8000启动后打开http://localhost:8000就能访问前端页面。接口文档会自动生成在http://localhost:8000/docs。7. 开发 Codex 相关功能的踩坑记录做这个网站期间我大量使用了 Codex 来辅助开发也遇到了几个很典型的报错这里集中记录一下排查思路。7.1 Codex CLI 无法启动报错信息类似unable to locate the codex cli binary. set codex cli path or ensure the executable is in your PATH这个问题的原因是系统找不到 Codex 的可执行文件。排查顺序确认是否安装了 Codex CLI安装命令一般是npm install -g openai/codex或通过官方安装脚本。确认 Codex 可执行文件所在目录是否在PATH环境变量中。如果 IDE 插件仍然报错可以在插件设置里手动指定 Codex 路径常见环境变量是CODEX_CLI_PATH。# 查看 codex 路径 which codex # 临时加入 PATHmacOS/Linux export PATH$PATH:/usr/local/bin7.2 登录 token 交换失败报错信息常见的有sign-in could not be completed token exchange failed token endpoint returned status 403 forbidden这个报错通常是认证流程中本地客户端拿临时授权码去换取访问令牌时网络出口 IP 或地区不被目标服务支持。也可能是系统时间不准导致 JWT 校验失败。排查步骤检查本机系统时间误差过大会导致 token 校验失败。检查网络环境确认出口 IP 是否在服务允许范围内。清空本地缓存配置后重新登录。如果使用团队网关或代理确认请求头中的认证信息没有被篡改。7.3 模型不支持报错报错信息类似the gpt-5.6-sol model is not supported when using codex with a ...这个原因是模型名写错或者当前 Codex 版本还不支持该模型。解决方案是检查 Codex 配置文件和启动命令中的模型名。升级 Codex CLI 到最新版本。如果已经通过第三方接口接入其他模型确认模型名与供应商平台完全一致。我在写比价网站时就遇到过一次模型名大小写出错导致请求直接失败。后来所有模型名统一小写存储并在查询时做大小写归一化才彻底解决。7.4 token 用量统计不准确做网站之前我习惯直接用 Codex 的上下文显示去估成本后来发现完全不准。正确做法是累计读取接口返回的prompt_tokens、completion_tokens和cached_tokens字段再做聚合。def accumulate_usage(usage: dict, total: dict) - None: total[prompt_tokens] total.get(prompt_tokens, 0) usage.get(prompt_tokens, 0) total[completion_tokens] total.get(completion_tokens, 0) usage.get(completion_tokens, 0) total[cached_tokens] total.get(cached_tokens, 0) usage.get(prompt_tokens_details, {}).get(cached_tokens, 0)8. 常见问题排查表问题现象常见原因解决思路接口返回模型价格为零数据表未初始化或价格缺失检查 SQLite 中 model_prices 表数据前端跨域请求失败CORS 配置未生效检查 FastAPI CORSMiddleware 配置估算结果数量级不对输入 token 单位混淆确认是否统一按百万 token 计算token 用量和账单对不上忽略缓存 token 计费仔细阅读供应商计费文档定时任务更新价格失败数据源反爬或接口变更降级为保留旧数据并人工告警Codex 登录报 403网络出口限制检查地区限制、系统时间和代理设置Codex 模型不支持模型名写错或版本过旧升级 CLI核对模型名9. 最佳实践与工程建议9.1 价格数据必须可配置化我见过很多工具把价格直接写成常量硬编码在 Python 文件里。这在一两周内没问题但 AI 模型降价、调价非常频繁硬编码意味着每次调价都要改代码、发版。更合理的做法是价格放在数据库或 JSON 配置文件中。启动时加载到内存缓存。提供手动更新入口和定时更新任务。9.2 接口鉴权与频率限制既然是免费公开服务接口很容易被人写脚本循环请求造成资源浪费。建议加一个简单的频率限制比如同一个 IP 每分钟最多请求 30 次。FastAPI 生态里可以直接用slowapi实现。# 示意代码限制请求频率 from slowapi import Limiter from slowapi.util import get_remote_address limiter Limiter(key_funcget_remote_address) # app.get(/api/price/list) # limiter.limit(30/minute)9.3 不要暴露 API Key任何比价工具如果接入了真实供应商 API 获取实时价格都必须把 Key 放在服务端不能让浏览器直接请求供应商接口。我目前的方案全部基于公开价格数据因此不涉及用户 Key。如果后续做“真实充值渠道比价”难度和合规要求会完全不同这里只是提醒敏感凭证永远不要进前端代码。9.4 日志与观测上线之后我加了两条最基本的日志每次估算请求记录模型名、token 参数、估算结果。定时更新任务记录成功/失败状态、更新条数。日志不只是排查问题用更重要的是能观察用户都在查哪些模型。比如发现某个模型被反复查询但价格一直显示旧数据就需要及时更新。9.5 合规与免责声明网站上一定要声明“价格信息仅供参考实际以官方为准”。因为 AI 模型价格调整频繁而且不同渠道的结算方式可能包含折扣、赠送额度等任何第三方比价工具都不能保证 100% 准确。我在页面底部添加了免责声明同时在 API 返回结果里也加了一个detail字段提醒用户。10. 后续规划目前这个比价网站已经可以正常使用核心能力是模型价格统一查询输入场景化费用估算缓存计费细节计算常见模型横向对比后续可以继续扩展的方向包括价格历史趋势图方便观察模型降价节奏。把估算工具做成浏览器插件在 ChatGPT、Codex 网页版中直接显示当前对话的预估成本。增加按任务类型推荐模型的逻辑比如“代码重构优先推荐 X 模型长文总结推荐 Y 模型”。如果你也在做 AI 成本控制相关工具建议先从最小的价格归一化模块入手把它做成一个函数库。这样无论后续是接命令行、做网页还是做浏览器插件都可以复用同一套计算逻辑。代码里的所有模型价格数据也一定记得从官方及时核对。
返回列表