在 Amazon Bedrock 上比较 OpenAI 模型时,只看每百万 Token 的单价,很容易得到一个看似精确、实际不够有用的结论。生产系统真正承担的成本,通常还包括重试、人工复核、工具调用、上下文长度,以及模型是否一次就能交付正确结果。
更可靠的评估方式,是把价格放回业务结果中:每个正确答案花了多少钱?一次 Agent 任务完整轨迹消耗了多少?模型交付物的质量是否达到了可接受标准?这些指标比单纯比较 Token 单价更接近真实运营成本。
价格不是最终指标
设一次请求的输入 Token 数为 input_tokens,输出 Token 数为 output_tokens,对应价格分别为 input_price 和 output_price,那么基础调用成本可以写成:
call_cost = input_tokens / 1,000,000 * input_price
+ output_tokens / 1,000,000 * output_price
这个公式本身没有问题,但它只回答了“调用花了多少钱”,没有回答以下问题:
- 这次调用是否给出了正确答案?
- 如果答案错误,是否触发了重试或升级到更贵的模型?
- Agent 为完成一个任务调用了多少次模型和工具?
- 最终报告是否满足格式、事实、完整性和可执行性要求?
因此,模型 A 即使 Token 单价更低,也可能因为更高的失败率和重试次数,产生更高的任务总成本。模型 B 的单次调用价格可能更高,但如果它能减少工具调用、修正次数和人工复核,单位业务结果的成本反而更低。
用三个指标描述真实成本
每个正确答案的成本
对于有明确正确性判断的任务,可以计算:
cost_per_correct = total_cost / number_of_correct_answers
这里的 total_cost 应该包含测试期间的所有调用成本,而不是只统计成功请求。否则,失败和重试会被排除在外,结果会系统性偏乐观。
这个指标适合问答、分类、结构化抽取和代码生成等任务。需要提前定义“正确”的判定规则,例如精确匹配、集合匹配、单元测试通过,或由领域评审员按照明确标准打分。
Agent 轨迹成本
Agent 任务通常不是一次请求完成的。一次完整轨迹可能包含规划、工具选择、工具结果分析、纠错和最终回答。可以按任务聚合所有步骤:
trajectory_cost = sum(model_call_costs) + sum(tool_or_external_costs)
即使工具本身没有直接费用,也应该记录调用次数、延迟和失败情况。重复搜索、无效函数调用和过长的中间上下文,都会影响生产系统的吞吐与延迟。
Rubric 评分后的交付成本
对于报告、分析、代码修改建议等开放式任务,单一的正确/错误标签通常不够。可以定义一个评分量表,例如:
- 事实准确性:0 到 4 分
- 任务覆盖度:0 到 4 分
- 输出格式符合度:0 到 2 分
- 可执行性:0 到 2 分
然后计算质量分数与成本之间的关系:
cost_per_quality_point = total_cost / total_rubric_score
也可以只统计达到最低质量门槛的结果,例如“评分不低于 8 分的交付成本”。关键在于:量表、评审标准和最低门槛必须在比较模型之前固定下来,不能看完结果后再调整规则。
一个可改造的评测骨架
下面的 Python 示例不依赖外部库,演示如何从请求日志中计算成本、正确率、每个正确答案的成本和 Agent 轨迹成本。实际接入 Bedrock 时,只需要把模型调用层的 Token 用量、模型名、评分结果和轨迹编号写入相同的数据结构。
运行前可以直接保存为 benchmark.py 并执行 python benchmark.py。示例价格只是占位值,不能作为实际报价;请替换成你账户和模型对应的价格表。
from collections import defaultdict
PRICES_PER_MILLION = {
"model-a": {"input": 2.0, "output": 8.0},
"model-b": {"input": 5.0, "output": 15.0},
}
CALLS = [
{
"model": "model-a",
"task_id": "qa-1",
"trajectory_id": "qa-1",
"input_tokens": 900,
"output_tokens": 180,
"correct": True,
"rubric_score": 9,
},
{
"model": "model-a",
"task_id": "qa-2",
"trajectory_id": "agent-1",
"input_tokens": 1400,
"output_tokens": 420,
"correct": False,
"rubric_score": 5,
},
{
"model": "model-a",
"task_id": "agent-1-step-2",
"trajectory_id": "agent-1",
"input_tokens": 1100,
"output_tokens": 260,
"correct": True,
"rubric_score": 8,
},
{
"model": "model-b",
"task_id": "qa-1",
"trajectory_id": "qa-1",
"input_tokens": 850,
"output_tokens": 160,
"correct": True,
"rubric_score": 10,
},
]
def call_cost(call):
price = PRICES_PER_MILLION[call["model"]]
return (
call["input_tokens"] / 1_000_000 * price["input"]
+ call["output_tokens"] / 1_000_000 * price["output"]
)
def summarize(model):
calls = [call for call in CALLS if call["model"] == model]
total_cost = sum(call_cost(call) for call in calls)
correct = sum(call["correct"] for call in calls)
rubric_total = sum(call["rubric_score"] for call in calls)
trajectories = defaultdict(float)
for call in calls:
trajectories[call["trajectory_id"]] += call_cost(call)
return {
"model": model,
"calls": len(calls),
"total_cost_usd": round(total_cost, 8),
"correct_answers": correct,
"cost_per_correct_usd": round(total_cost / correct, 8) if correct else None,
"average_trajectory_cost_usd": round(
sum(trajectories.values()) / len(trajectories), 8
) if trajectories else None,
"average_rubric_score": round(rubric_total / len(calls), 2) if calls else None,
}
for model in PRICES_PER_MILLION:
print(summarize(model))
接入实际评测时,建议把每次模型调用记录为一行事件,并至少保存以下字段:run_id、model、task_id、trajectory_id、输入输出 Token 数、延迟、错误类型、最终正确性和 rubric 分数。这样可以从同一批原始记录重新计算指标,避免只保留汇总数字后无法追查异常。
在 Amazon Bedrock 的调用层,可以使用 boto3 访问 bedrock-runtime,但请求体和响应体会随模型与 API 模式变化。下面是一个最小的调用轮廓,实际使用前需要根据所选模型的 Bedrock 请求格式调整 body 和 Token 用量解析逻辑:
import json
import boto3
client = boto3.client("bedrock-runtime", region_name="us-east-1")
body = {
"messages": [{"role": "user", "content": "Return a JSON answer."}],
"max_tokens": 300,
}
response = client.invoke_model(
modelId="YOUR_BEDROCK_MODEL_ID",
body=json.dumps(body),
contentType="application/json",
accept="application/json",
)
payload = json.loads(response["body"].read())
print(json.dumps(payload, indent=2))
不要把响应中的 Token 字段名称写死在通用评测器里。更稳妥的方式是为每种响应格式实现一个适配器,统一输出 input_tokens、output_tokens、latency_ms 和 answer,然后让后续的计费与评分逻辑只处理统一事件格式。
让评测结果更接近生产
一组只有十几个问题的测试集,很容易被偶然样本影响。更实用的基准集应覆盖真实流量中的任务类型、输入长度、语言、工具使用路径和失败案例。对于 Agent,还应保留完整轨迹,而不是只比较最终文本。
评测过程中要固定以下条件:
- 使用相同的测试集和业务约束。
- 记录相同的 Token、延迟和错误字段。
- 固定温度、最大输出长度和工具定义等参数。
- 将模型调用成本与重试成本一起统计。
- 对开放式输出使用盲评或固定 rubric。
- 报告平均值之外的分位数,例如 P50、P95 成本和延迟。
还要区分开发集、验证集和最终验收集。若根据测试集结果反复修改提示词,再用同一批问题宣布模型胜出,测到的可能是对测试集的适应,而不是泛化能力。
选择模型时的落地清单
可以按下面的顺序做决策:
- 先定义业务结果:什么叫正确,什么质量算合格。
- 收集真实或脱敏后的代表性任务,并保留困难样本。
- 统一记录调用成本、Token、延迟、错误和轨迹。
- 分别计算每个正确答案的成本、平均轨迹成本和 rubric 质量。
- 检查质量门槛,避免用低质量结果换取便宜价格。
- 在接近的模型之间,再比较延迟、稳定性、上下文容量和运维复杂度。
- 上线后持续采样,观察输入分布变化和实际重试率。
最终目标不是找出“每百万 Token 最便宜”的模型,而是找到在你的任务分布和质量门槛下,单位有效结果成本最低、行为最稳定的模型。价格表可以帮助你估算上限,结果导向的基准测试才能帮助你做生产决策。