实验设置
四个 Amazon Bedrock 上的模型,各自管理 $50,000 虚拟本金,每个交易日做一次收盘决策,按历史收盘价撮合,区间为 2026-01-02 至 2026-07-10,共 130 个交易日。相同的中性提示词、相同的 16 支股票池、相同的工具(查行情、看持仓、买入、卖出、写日志)。网络搜索关闭,确保没有模型能读到未来的新闻。
我们想回答的,是所有做 Agent 的团队迟早会问的问题:为更强的模型多付钱,能换来更好的决策吗?
我们差点发布的那个排行榜
| 模型 | 厂商 | 期末净值 | 收益 | 成交 | 每次决策成本 |
|---|---|---|---|---|---|
| llama-8b | Meta | $86,289 | +72.6% | 3 笔 | $0.00036 |
| nova-lite | Amazon | $56,562 | +13.1% | 7 笔 | $0.00041 |
| claude-haiku | Anthropic | $53,855 | +7.7% | 29 笔 | $0.00706 |
| sonnet-4-6 | Anthropic | $50,767 | +1.5% | 46 笔 | $0.04402 |
一个 8B 模型,只交易了 3 次,把交易了 46 次的前沿模型甩开 71 个百分点。交易频率与收益几乎完全负相关。这是一个绝佳的头条,而且它长得很像一个真正的洞见。
我们不相信它。采样温度是 0.7,这里没有任何东西是确定的。而 llama-8b 跑完整的 130 天只要 $0.04。没有任何理由不去验证。
于是我们把每个模型都重跑了五次
同样的 130 天,同样的提示词。额外 18 次完整回测。
| 模型 | 头条那次 | 五次重跑 | 重跑均值 | 自身波动幅度 |
|---|---|---|---|---|
| llama-8b | +72.6% | +10.8 / +8.0 / +18.9 / +16.6 / +5.9 | +12.0% | 13.0 pp |
| nova-lite | +13.1% | +3.7 / +9.3 / +1.8 / +13.4 / −0.1 | +5.6% | 13.5 pp |
| claude-haiku | +7.7% | +9.4 / +14.8 / +1.0 / +11.3 / +8.8 | +9.0% | 13.8 pp |
| sonnet-4-6 | +1.5% | −3.0 / +15.6 / +2.2 (n=3) | +5.0% | 18.6 pp |
请把最后两列放在一起读,因为这就是整篇文章的全部:
- 四个模型的均值全部落在 +5.0% 到 +12.0% 之间——彼此相差 7 个百分点。
- 而每个模型自己重跑一遍的波动幅度,是 13 到 19 个百分点。
一个模型和它自己重跑的差距,约等于模型之间差距的两倍。 在收益这个维度上,这四个模型根本无法区分。我们排行榜上那个 71 个百分点的鸿沟是噪声,而我们距离把它当作研究发现推出去,只差一次部署。
llama-8b 的五次重跑,没有任何一次接近 +72.6%,最高也只有 +18.9%。它的典型水平是 +12.0%——与表格里其他所有模型统计意义上打平。
头条那一次,在每个维度上都不典型
有了分布之后我们才发现,我们恰好截图的那一次运行,不只是收益异常,行为也异常:
| 模型 | 头条那次的成交 | 重跑的成交 |
|---|---|---|
| llama-8b | 3 笔 | 2 / 3 / 3 / 4 / 3 |
| nova-lite | 7 笔 | 28 / 46 / 46 / 43 / 43 |
| claude-haiku | 29 笔 | 5 / 3 / 12 / 4 / 17 |
| sonnet-4-6 | 46 笔 | 47 / 34 / 29 |
nova-lite 平时交易 2846 次,头条那次只交易了 7 次。claude-haiku 平时交易 317 次,头条那次交易了 29 次。我们基于「交易频率与收益负相关」讲了一个故事,而那次运行里,四个模型有两个的行为完全不像它们平时的样子。
这不是运气不好。这就是「样本量为 1」的真实模样。
那么,什么活了下来
两件事。它们是这篇文章里我们唯一愿意为之辩护的结论。
1. 收益不可复现,但行为可复现。 llama-8b 在每一次运行中都只交易 2~4 笔。它稳定地在早期下几注,然后一动不动——这是模型真实、稳定的性格。但它买什么,以及因此赚多少,是抛硬币。(需要说清楚:极少交易不是缺陷。在这个窗口里,什么都不做恰恰是最好的策略。我们要说的是,它的收益告诉你的是 NVDA 的走势,而不是 Meta 的模型。)
2. 成本是确定的。 它完全不在乎行情往哪边走:
sonnet-4-6 每次决策的成本是 llama-8b 的 121 倍——$0.04402 对 $0.00036。
这是结果表里唯一一个「看起来是什么意思、就真的是那个意思」的数字。把它和上面的收益分布放在一起,就得到了我们真正敢于站在后面的那个结论:
你多付了 121 倍的钱,买到的是同一个分布里的另一次抽样。
这不是在反对前沿模型。这是在反对「假设溢价一定能在这个任务上换来东西」,并主张去测量,而不是去假设。换一个需要真正推理深度的任务,答案很可能反过来——但它不会免费反过来,而且你不去看就永远不会知道。
由此得到的一条规则
如果你用单次运行的结果去给 LLM Agent 排名——无论是我们的还是任何人的——你排的是运气。修复方法并不高深:重跑几次,看看离散度。 如果单个模型内部的波动,比模型之间的差距还大,那你手上就没有排名。你手上是噪声,只不过在上面画了一个排行榜。
两个早已停止工作的 Agent
这次实验里最昂贵的发现,不在收益表里。
一个编造了六个月交易记录的模型
更早一轮模拟里包含 mistral-small-2402。它从 1 月运行到 5 月,执行了零笔交易。净值:每一天都精确地停在 $50,000。现金:$50,000。持仓:无。
它看起来像一个极度保守的策略。但它根本不是策略。这个模型无法发出工具调用,而它的日志显示了三种层层递进的失败形态:
它用散文描述自己正在调用工具:
I begin by reviewing my current portfolio using the "get_portfolio" tool...
它把工具调用的 JSON 当作普通文本写了出来:
{"name": "read_journal", "arguments": {"entry": "Reviewed portfolio and quotes. Decided to hold positions."}}
它清楚地知道一个工具调用长什么样。它只是把它打字打了出来,而没有返回真正的 toolUse 结构——于是 stopReason 永远不会是 tool_use,我们的 runTool() 永远不会被触发,订单永远不会成交。
而且它编造了从未发生的交易,用的还是真实存在的股票:
Bought 100 shares of AMZN based on strong momentum...Found positive news on AMD, bought 100 shares at market price.
AMZN 和 AMD 都在我们的股票池里。只扫一眼日志,这些记录看起来完全合理。但成交记录是零笔,持仓为空,现金分毫未动。
这不是一个关于 Mistral 的故事。mistral-small-2402 是 2024 年 2 月的快照,早于可靠的 function calling 出现;Mistral 后续的模型是支持的。这是一个关于 Agent 可以死得多么安静、而你的监控面板对此又多么无话可说的故事。
是我们自己的代码在掩护它
Bedrock 封装层捕获了模型错误,然后把它当作正常输出返回:
} catch (e) {
return { finalText: `[${spec.id}] model error: ${e.message}`, ... };
}
一个根本调不通的模型,和一个今天决定不交易的模型,产出的数据完全一样:零成交、净值不变。没有任何可供告警的信号。于是模拟愉快地跑了几个月,一个早已死去的 Agent 的推理账单,每个月都和其他账单一起如期而至。
然后 fail-fast 又抓出了第二个
在我们把「调用失败」改成抛异常之后,一次重跑在第 17 天崩溃了:
ModelCallFailed: [sonnet-4-6] The content field in the Message object
at messages.8 is empty.
这是一个真实的 bug,出在我们的循环里,而不是模型身上:当 sonnet-4-6 在推理中途被 maxTokens 截断时,它会返回一条不含任何内容块的助手消息。我们把这条空消息原样塞回了对话历史,于是 Bedrock 在下一次调用时判定消息非法,直接 400。
而在旧的「错误吞噬」代码下,这个 400 会被捕获、当作文本写进日志。这意味着在此前的每一轮回测中,sonnet-4-6 几乎必然在某些天悄无声息地什么都没做——而我们永远不会知道。 与 mistral-small-2402 同一个病因,只是症状不同。
两次静默失败,同一个根因:我们在自己的数据里,把「调用失败」和「Agent 选择不行动」变成了同一个东西。
我们做了哪些修改
1. 花钱之前先体检。 开跑前用一次 1-token 调用探测每个模型,任何一个调不通就拒绝启动。几乎不花钱,而它本可以在第零天就拦下整起事故。
2. 失败必须立即中断并报错。 现在调用失败会抛出 ModelCallFailed 并终止运行。「调用失败」和「Agent 决定持有」,绝不能在数据里长成同一行。
3. 给工具循环加护栏。 绝不把空内容的消息塞回对话历史。
4. 按 Agent 分别记账。 我们的预算追踪器只记录了一个总数。我们能告诉你这次实验花了 $8.07,却答不出这笔钱是被哪个模型花掉的——而那恰恰是整个实验唯一要问的问题。更讽刺的是,doCheckin() 一直在计算分模型成本,只是被随手丢弃了。
请注意 preflight 拦不住什么:mistral-small-2402 能通过它。这个模型是调得通的,它只是不会行动。HTTP 200 不能证明 agentic 能力。 如果你需要模型使用工具,你的检查必须断言「返回了一个真正的工具调用结构」,而不仅仅是「API 有响应」。
这一切花了多少钱
我们认为这个价签很重要,因为对「评测一定要重跑」最常见的反驳就是:听起来很贵。
| 花费 | |
|---|---|
| 头条那一轮(4 模型 × 130 天) | $8.07 |
| 每次决策成本的校准跑 | $1.20 |
| 重跑 llama-8b × 5 | $0.19 |
| 重跑 nova-lite × 5 | $0.27 |
| 重跑 claude-haiku × 5 | $4.40 |
| 重跑 sonnet-4-6 × 3 | $17.75 |
| 失败与废弃的运行 | 约 $1.30 |
| Bedrock 总花费 | $33.14 |
把两个真正重要的数字并排放在一起:
产出那个错误的头条,花了 $8.07。推翻它,花了 $0.19。
不存在任何一种预算水平,能让「跳过重复实验」成为理性选择。
但请注意这里的不对称,因为这是一项没有任何厂商会写进价目表的、前沿模型的真实成本:推翻一个关于 llama-8b 的错误结论只要 1 毛 9;推翻一个关于 sonnet-4-6 的错误结论要 $17.75。 模型越贵,用同样的证据标准去检验它就越贵——于是「跑一次就发布」的诱惑也就越大。
(一处必须坦白的缺口:sonnet-4-6 是 n=3,不是 n=5。一次 AWS 会话过期加上预算断路器把运行截断了,而为剩下两次再花 $12 我们认为不值——它的波动幅度已经是四个里最大的。其余每个模型都是 n=5。)
可以带走的结论
- 绝不要用单次运行给 Agent 排名。 如果单个模型内部的离散度超过了模型之间的差距,那就没有排名可言。重跑几次——离散度本身就是结果。
- 不要靠价目表或排行榜来选 Agent 模型。 用一次真实的工具调用去验证它确实会行动。流畅和有能力是两回事,而这个差距从外面完全看不出来。
- 更贵的模型不会自动成为更好的 Agent。 在这个任务里,121 倍的单次决策成本买到的是更多的操作、并不更高的收益,以及更大的方差。请在你自己的任务上测量它,而不是假设它。
- 从第一天就按 Agent 分别记录成本。 一个总额,回答不了你真正会问的那个唯一的问题。
- 静默失败是 Agent 的默认失败方式。 服务崩溃会把你叫醒;而一个安静地什么都不做的 Agent,看起来和一个谨慎行事的 Agent 一模一样——而且无论哪种,它都照常向你收费。
这个模拟仍在运行,每个交易日决策一次。下方的排行榜会自动更新。现在,你知道它值多少了。