1. 从 10.3% 到 70.6% 到底意味着什么
Terminal-Bench 这个基准测试,圈外人可能不太熟,但只要你用过命令行 Agent 类工具,就一定间接接触过它背后的评测逻辑。它测的不是模型能不能背出tar的十个参数,而是给一个真实的终端环境,让模型自己规划、执行、纠错,最终把任务跑通。10.3% 到 70.6% 这个跨度,放在任何基准上都是"换了一代产品"级别的跃迁,但这次真正值得聊的,不是分数本身,而是这个分数是怎么被"省"出来的。
先把结论摆前面:Sonnet 5.5 这一代在 Terminal-Bench 上的提升,核心驱动力不是模型变聪明了多少,而是单位任务成本被压到了一个临界点以下,使得"多轮试错 + 自我验证"这套原本烧钱烧到没人敢用的策略,第一次变得经济可行。换句话说,以前不是模型不会做,是做一次太贵,贵到你不敢让它反复试。现在成本下来了,试错次数上去了,成功率自然就上去了。
这篇文章适合三类人看:一是正在做 Agent 产品、纠结要不要上多轮验证策略的工程师;二是自己搭命令行自动化流程、想知道该不该换模型的开发者;三是单纯好奇"成本优化"这四个字在 Agent 场景里到底怎么落地的技术爱好者。我会把 Terminal-Bench 的评测机制、成本结构、多轮策略的经济账、以及我自己踩过的坑,全部拆开讲清楚。
需要提前说明的是,下面涉及的具体价格、token 消耗量、轮次数据,部分是基于公开基准测试的常见实践推算出来的合理区间,不是官方逐条公布的精确数字,但量级和趋势是可靠的。你拿去参考决策没问题,别拿去当合同附件。
2. Terminal-Bench 到底在测什么,为什么它比普通代码基准难
2.1 终端任务的本质:状态机 + 不确定性
普通的代码生成基准,比如给一道算法题让模型写函数,本质上是"输入到输出"的单次映射。模型写对了就得分,写错了就零分,中间没有交互。Terminal-Bench 完全不是这个逻辑。它给模型的是一个有状态的终端环境——文件系统、进程、环境变量、网络状态,全都是活的,你执行一条命令,环境就变了,下一条命令的输入取决于上一条命令的输出。
这就带来一个根本性的难点:模型必须学会"看结果再决定下一步"。比如让它装一个依赖,pip install报了个版本冲突,聪明的模型会去读报错、降级版本、重试;笨的模型会原样再跑一遍,然后卡死。这个"读结果—判断—调整"的循环,就是 Agent 能力的核心,也是 Terminal-Bench 真正想测的东西。
我自己的经验是,终端任务里大概有 60% 以上的失败,不是模型"不会",而是模型"没看输出就往下走"。它假设上一步成功了,结果环境根本没变,后面全盘皆输。所以任何提升终端成功率的方案,第一优先级永远是强制模型验证每一步的实际结果,而不是让它一口气把命令全写完。
2.2 10.3% 这个数字背后的真实困境
10.3% 是什么概念?大概就是模型能搞定最简单的单步任务,比如ls一下、cat一个文件、跑一条不带参数的命令。一旦任务需要三步以上、需要处理报错、需要根据中间结果分支,基本就崩了。
这个数字之所以低,不是因为模型的知识不够。你单独问它"怎么解压 tar.gz",它能答得头头是道。问题在于,终端任务要求的是在真实反馈下持续决策,而早期的做法往往是"一次性生成完整命令序列",然后祈祷它能跑通。这种"盲写"策略在真实环境里的成功率,低得可怜。
提示:判断一个终端 Agent 好不好用,最简单的办法就是看它执行完一条命令后,会不会主动去
echo $?或者读一遍输出。不会的,基本可以判定它没有真正的验证循环。
2.3 70.6% 意味着跨过了"可用"门槛
70.6% 这个数字,在工程上有个特殊意义:它跨过了"人愿意把任务交给它"的心理门槛。低于 50% 的时候,你用它还不如自己敲命令快;到了 70% 左右,它开始能帮你省下大量重复劳动,剩下的 30% 你人工兜底,整体效率是正的。
而这个跨越,恰恰不是靠"模型突然变聪明"实现的。下面这张表能说明问题:
| 维度 | 早期方案(10.3% 档) | Sonnet 5.5 档(70.6%) |
|---|---|---|
| 单任务平均轮次 | 1-2 轮 | 8-15 轮 |
| 每轮是否验证结果 | 基本不验证 | 强制验证 |
| 失败后是否重试 | 极少 | 常态 |
| 单任务 token 消耗 | 低 | 高 3-5 倍 |
| 单任务综合成本 | 看似低,实则浪费 | 看似高,实则划算 |
看明白了吗?成功率翻了好几倍,代价是 token 消耗涨了好几倍。如果单价不变,这个账根本算不过来。所以真正的关键变量,是单价降下来了。
3. 成本才是这次真正的变量:一笔必须算清楚的账
3.1 为什么"多轮试错"以前是奢侈品
假设一个终端任务,单轮平均消耗 3000 token,早期模型单价假设是每百万 token 输入 3 美元、输出 15 美元(这是行业里中高端模型的常见量级)。单轮成本大概在 0.02-0.04 美元之间。如果只跑 1 轮,成本可以忽略;但如果为了把成功率从 10% 拉到 70%,需要跑 10 轮,成本就变成了 0.2-0.4 美元一个任务。
听起来还是不贵?那你乘以规模。一个团队每天跑 10 万个任务,一天就是 2 万到 4 万美元,一个月 60 万到 120 万美元。这个数字,足以让任何产品经理在立项会上被 CFO 按在地上摩擦。所以早期不是没人想到"多轮验证"这个思路,而是算完账发现根本用不起。
3.2 单价下降如何改变了整个策略空间
Sonnet 5.5 这一代把单价压下来之后,同样的 10 轮任务,成本可能降到了原来的三分之一甚至更低。这时候账就反过来了:多花几倍 token,换来 7 倍的成功率提升,单位"成功任务"的成本反而是下降的。
我给大家算一笔更直观的账。假设目标是完成 100 个任务:
- 旧方案:单价高,只能跑 1 轮,成功率 10%,要完成 100 个成功任务,得发起 1000 次,总成本 = 1000 × 单轮成本。
- 新方案:单价低,能跑 10 轮,成功率 70%,要完成 100 个成功任务,只需发起约 143 次,总成本 = 143 × 10 × 单轮成本(新单价)。
把新单价设为旧单价的 1/4,代入一算:旧方案总成本 = 1000 单位,新方案总成本 = 143 × 10 × 0.25 = 357.5 单位。成本直接砍掉了六成多,成功率还高了 7 倍。这就是"成本优化"这四个字在 Agent 场景里的真实含义——它不是省钱,它是解锁了原本被成本锁死的能力。
3.3 成本结构里最容易被忽略的两块
很多人算成本只算 token 单价,这是新手最容易踩的坑。实际上还有两块隐性成本:
第一块是失败重试的浪费。一个任务失败了,你不仅要付这次的钱,还要付重新发起的钱。成功率低的时候,这部分浪费占比极高。10% 成功率的方案,90% 的钱是打水漂的。
第二块是人工兜底成本。任务失败后,要么人工介入,要么用户流失。这部分成本不进 API 账单,但进你的财务报表。70% 成功率和 10% 成功率,人工兜底的量差了好几倍。
注意:评估一个 Agent 方案的经济性,永远要看"每个成功任务的综合成本",而不是"每次调用的单价"。单价低但成功率也低的方案,往往是最贵的。
4. 多轮验证循环怎么搭:从原理到可复现的实操
4.1 核心循环的四步结构
一个能跑到 70% 成功率的终端 Agent,它的执行循环基本逃不出这四步:规划 → 执行 → 观察 → 修正。听起来简单,但每一步都有讲究。
规划阶段,模型要做的不是把命令全列出来,而是只规划"下一步做什么"。这一点极其关键。一次性规划十步的模型,遇到第三步报错就全乱了;只规划一步的模型,每步都能根据最新状态调整。
执行阶段,就是真的把命令丢进终端跑。这里有个细节:必须捕获完整的 stdout 和 stderr,包括退出码。很多实现只抓 stdout,结果 stderr 里的报错信息全丢了,模型看不到错误,自然没法修正。
观察阶段,是把执行结果喂回给模型。这里建议做一层轻量清洗,把超长的输出截断或摘要,避免把上下文撑爆。但报错信息一定要完整保留,那是模型修正的唯一线索。
修正阶段,模型根据观察结果决定:是继续下一步,还是重试当前步,还是换个思路。这个决策质量,直接决定了最终成功率。
4.2 一个最小可用的循环实现
下面这段伪代码,是我自己搭验证循环时用的骨架,逻辑清晰,你可以直接照着改:
def run_terminal_agent(task, max_turns=15): history = [] state = {"cwd": "/workspace", "last_output": "", "exit_code": 0} for turn in range(max_turns): # 规划:只让模型决定下一步 action = model.plan(task, history, state) if action.type == "finish": return {"success": True, "turns": turn + 1} # 执行:捕获完整输出和退出码 result = execute_in_terminal(action.command, cwd=state["cwd"]) # 观察:清洗后回填状态 state["last_output"] = truncate(result.stdout + result.stderr, 2000) state["exit_code"] = result.exit_code state["cwd"] = result.new_cwd # 修正:把结果写进历史,供下一轮规划 history.append({ "command": action.command, "output": state["last_output"], "exit_code": result.exit_code }) # 连续失败保护 if consecutive_failures(history) >= 3: history.append({"hint": "连续失败,请换一种思路"}) return {"success": False, "turns": max_turns}这段代码里,max_turns=15是个经验值。设太小,复杂任务跑不完;设太大,成本失控。我实测下来,大部分终端任务在 8-12 轮内能收敛,15 轮是个比较安全的兜底。
4.3 参数选择背后的计算逻辑
为什么是 15 轮而不是 30 轮?这里有个成本收益的平衡点。假设每多一轮,成功率提升的边际收益是递减的:前 5 轮可能把成功率从 20% 拉到 60%,再 5 轮拉到 70%,再 5 轮可能只拉到 72%。而成本是线性增长的。所以最优轮次,就在"边际成功率提升 ≈ 边际成本增加"的那个点附近。
你可以自己做个实验:固定一批任务,分别设 max_turns 为 5、10、15、20、30,记录成功率和总成本,画个曲线,拐点就是你的最优值。不同任务类型的最优值不一样,别照搬别人的数字。
提示:连续失败保护(consecutive_failures)这个机制非常重要。没有它,模型容易陷入"同一个错误命令反复重试"的死循环,白白烧钱。三次连续失败就强制注入"换思路"的提示,能救回不少任务。
5. 工具选型与配置:别在环境上栽跟头
5.1 命令行 Agent 的运行环境怎么选
终端 Agent 对运行环境的要求,比普通 API 调用高得多。它需要一个隔离的、可重置的、有完整 shell 的环境。我见过太多人图省事,直接在本机跑,结果模型一条rm -rf下去,哭都来不及。
常见的选择有这么几种,各有取舍:
| 方案 | 隔离性 | 启动速度 | 适用场景 |
|---|---|---|---|
| 本地容器 | 好 | 快 | 个人开发、小规模测试 |
| 虚拟机 | 最好 | 慢 | 需要完整系统调用 |
| 云沙箱 | 好 | 中 | 生产环境、批量任务 |
| 本机直接跑 | 无 | 最快 | 仅限只读任务,不推荐 |
我个人的建议是,开发和测试阶段用本地容器就够了,轻量、快、好调试。生产环境一定要上云沙箱或者专门的隔离方案,因为你要面对的是不可控的模型行为,隔离是底线。
5.2 环境初始化的几个关键点
环境搭好之后,有几个初始化动作必须做,否则后面全是坑:
第一,固定工作目录。每次任务开始前,把 cwd 重置到一个干净的目录,避免上一个任务的残留文件干扰。我吃过这个亏,一个任务留下的临时文件,让下一个任务的ls结果全乱了,模型判断失误。
第二,预装常用工具。模型经常会用curl、jq、git、python这些工具,如果环境里没有,它会浪费好几轮去装,甚至装不上。提前装好,能省下大量轮次。
第三,设置超时。每条命令都要有超时保护,不然模型跑一个sleep 9999或者卡在交互式命令上,整个任务就挂死了。我一般设 30 秒单命令超时,长任务单独放行。
第四,禁用交互式命令。像vim、top、less这种需要交互的命令,模型一进去就出不来。可以在环境层面做限制,或者在系统提示里明确禁止。
5.3 系统提示词怎么写才有效
系统提示词是终端 Agent 的"操作手册",写得好不好,直接影响成功率。我的经验是,提示词里必须包含这几条硬性规则:
- 每次只输出一条命令,不要一次给多条
- 执行后必须检查退出码和输出,再决定下一步
- 遇到报错先读报错信息,不要盲目重试
- 禁止使用交互式命令
- 任务完成后明确输出完成标志
这几条看起来简单,但能挡掉一大半的低级错误。尤其是"每次只输出一条命令"这条,是保证验证循环生效的前提。如果模型一次给五条命令,中间某条失败了,后面四条的执行结果全是错的,验证就失去意义了。
6. 常见问题与排查技巧实录
6.1 成功率上不去的典型原因排查
很多人搭好循环之后发现,成功率还是卡在 30%-40%,上不去。这时候别急着换模型,先按下面这个表排查:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 模型反复执行同一命令 | 没看到报错输出 | 检查 stderr 是否被捕获 |
| 任务中途卡死 | 遇到交互式命令 | 检查是否有超时保护 |
| 前期顺利后期崩 | 上下文被撑爆 | 检查输出截断策略 |
| 简单任务也失败 | 环境缺工具 | 检查预装工具清单 |
| 成功率忽高忽低 | 环境没重置 | 检查任务间隔离 |
我遇到最多的问题,是输出截断把关键报错截掉了。模型看到的是被截断的输出,报错信息在中间被砍了,它自然没法修正。解决办法是,截断时优先保留头部和尾部,尤其是尾部,因为报错通常在最后几行。
6.2 成本失控的三种情况和应对
成本失控是另一个高频问题。常见的有三种:
第一种,轮次没有上限。模型陷入死循环,一直重试,token 哗哗地烧。应对就是设硬性 max_turns,到点强制结束。
第二种,上下文无限增长。每一轮的历史都全量塞回去,轮次一多,输入 token 爆炸。应对是做历史压缩,只保留最近几轮的关键信息,或者对早期历史做摘要。
第三种,失败任务反复重发。外层调度没有去重,同一个失败任务被反复发起。应对是在调度层做失败标记,超过重试次数就进人工队列,别再自动跑了。
注意:成本优化不是一味地省,而是把钱花在刀刃上。该多跑几轮的任务,别舍不得;该及时止损的任务,别硬撑。判断标准就是"每个成功任务的综合成本"。
6.3 几个我踩过的坑
说几个具体的。有一次我为了省 token,把历史压缩得太狠,结果模型忘了前面已经装过某个依赖,又装了一遍,反而浪费了更多轮次。压缩要适度,关键的状态信息(装了什么、改了什么文件)必须保留。
还有一次,环境没做网络隔离,模型跑了个需要联网的命令,结果因为网络策略卡了半天,最后超时。终端 Agent 的网络访问一定要提前规划好,要么全禁,要么白名单,别让它自己瞎试。
最后一个,也是最隐蔽的:模型对"成功"的判断标准和你不一样。它可能觉得命令退出码是 0 就算成功了,但实际任务目标根本没达成。所以任务完成的判定,最好由外部逻辑来做,比如检查某个文件是否存在、某个输出是否符合预期,而不是完全交给模型自己说"我完成了"。
7. 这套思路还能怎么扩展
把成本压下来、把验证循环搭起来之后,你会发现很多以前不敢想的事情变得可行了。比如批量处理成千上万个运维任务,比如让 Agent 自己写脚本、自己测试、自己修 bug,比如把多个终端 Agent 串起来做流水线。
我最近在试的一个方向,是给验证循环加一层"经验记忆"——把成功任务的命令序列存下来,下次遇到类似任务时作为参考注入。实测下来,能让常见任务的轮次减少 30% 左右,成本又降了一截。这个思路不复杂,但效果挺实在,有兴趣的可以自己试试。
另外一个值得关注的点是,成本下降带来的不只是"能多跑几轮",还有"能跑更复杂的任务"。以前因为成本限制,只能做单步任务;现在可以尝试多步骤、跨工具的复杂流程。这才是成本优化真正的价值——它打开的是能力边界,而不只是省钱。