摘要:本以为"让 AI 自己点界面"最省事,结果用本地沙箱实测:同一个导出报表任务,computer use 要滚 45 轮,成本比"补一个只读接口 + 两次调用"贵 30 倍。根因就两条——读数据也要先看图、等待只能变轮询。三套脚本可改参数重跑,文末附成本护栏和判断标准。
文章目录
- 先声明:本地沙箱模拟,没接真实 API
- 任务:老后台导出报表
- 现象:账单比预想的大了一个量级
- 排查:token 都烧到哪去了
- 根因:读=操作,等待=轮询
- 修复:补只读接口 + 成本护栏
- 边界:computer use 什么时候反而划算
- 错误处理与降级策略
- 收尾
- 参考资料
先声明:本地沙箱模拟,没接真实 API
先说清楚,这篇没有真的接 OpenAI 的 computer use 跑一轮(需要 API 环境,我没有),而是用本地沙箱把它的"截图→判断→动作"循环机制模拟出来:按官方文档的 computer use tools 机制,把"让 Agent 去老后台导报表"这个任务拆成阶段,用脚本真实模拟每一轮的截图、轮询、重试开销,再按官方价格折算成 token 账单。
任务场景是我虚构的老后台,但这不影响机制验证——computer use 的成本结构不取决于数据真假,只取决于"轮数 × 单轮 token"。
三个脚本都在 Node 25.8.2 里真跑了,下面的轮数、token、账单都是运行结果,不是拍脑袋的数。所有参数都写在脚本里,你可以改参数重跑,验证你自己的任务会滚成多少轮。模拟器只能验证机制,不能替代真实 API 行为——真实环境里模型还会看错界面、多绕弯路,实际账单大概率比模拟值更贵而不是更便宜。
为什么要干这件事?上一篇(《Agents API 支持 computer use 了》)算完账之后,我总觉得纸上算和真拆一轮是两回事——"点界面贵"到底贵在哪,得把机制拆开看才知道。拆完发现,比上一篇算的还夸张。
任务:老后台导出报表
任务很简单:在一个只有界面、没有 API 的老后台里,把"上个月订单"筛选出来导成 Excel。
我的第一反应是:这正好是 computer use 的用武之地啊——不用给老系统开接口,让 Agent 自己点就行。理想情况下,打开页面、填筛选、点查询、点导出、确认、等下载,十来步就完事了,比开发一个接口省事多了。
现象:账单比预想的大了一个量级
"账单"看着没多少,一算吓一跳。
先回答最核心的问题:45 轮是拍脑袋的还是跑出来的?跑出来的。下面这个脚本把任务拆成阶段,按 computer use 的机制逐轮模拟——每一轮就是一次"截图→判断→动作",异步加载没有完成回调,只能每 300ms 截一张图轮询状态,点偏了还要多截一张重试。为啥要看这段:轮数不是结论,是机制的输出——参数改一个,轮数就跟着变。
// 本地沙箱:把"点界面导报表"拆成阶段,模拟 computer use 的循环开销// 机制:每一轮 = 截1张图(输入token) + 判断/动作(输出token)// 异步加载没有"完成回调",只能每 300ms 轮询截图看状态constPOLL_MS=300;// Agent 两次轮询截图的间隔constIMG_TOK=1800;// 单张截图折算的输入 token(图像按输入计费)constOUT_TOK=600;// 单轮"判断+动作"的输出 token// 每个阶段: base=顺利时截图数, waitMs=异步等待时长, retry=点偏重试多截的图conststages=[{name:"打开后台",base:1,waitMs:600,retry:1},// 首屏加载 + 菜单判断点偏一次{name:"进报表页",base:1,waitMs:600,retry:1},// 路由切换 + 入口点偏一次{name:"填筛选条件",base:3,waitMs:0,retry:0},// 点输入框/输入/选下拉,顺利一次过{name:"点查询+等加载",base:1,waitMs:2700,retry:0},// 查询返回前只能轮询截图{name:"点导出+弹窗",base:1,waitMs:600,retry:1},// 弹窗响应 + 导出按钮点偏一次{name:"确认导出",base:1,waitMs:400,retry:0},// 确认后生成文件{name:"等下载+收尾",base:1,waitMs:2700,retry:0},// 下载完成前只能轮询截图{name:"核对文件",base:1,waitMs:0,retry:0},// 最后截一张确认导出来了];functionwaitShots(ms){returnms>0?Math.ceil(ms/POLL_MS)+1:0;}// 有异步等待才轮询constbreakdown=stages.map(s=>{constact=s.base,poll=waitShots(s.waitMs),rty=s.retry;return{阶段:s.name,动作:act,等待轮询:poll,重试:rty,合计:act+poll+rty};});console.table(breakdown);consttotal=breakdown.reduce((s,b)=>s+b.合计,0);constideal=stages.reduce((s,st)=>s+st.base,0);console.log(`总轮数:${total}| 理想一次过:${ideal}轮 | 放大:${(total/ideal).toFixed(1)}x`);console.log(`输入token:${total*IMG_TOK}| 输出token:${total*OUT_TOK}`);运行结果(Node 25.8.2 实测):
┌─────────┬─────────────────┬──────┬──────────┬──────┬──────┐ │ (index) │ 阶段 │ 动作 │ 等待轮询 │ 重试 │ 合计 │ ├─────────┼─────────────────┼──────┼──────────┼──────┼──────┤ │ 0 │ '打开后台' │ 1 │ 3 │ 1 │ 5 │ │ 1 │ '进报表页' │ 1 │ 3 │ 1 │ 5 │ │ 2 │ '填筛选条件' │ 3 │ 0 │ 0 │ 3 │ │ 3 │ '点查询+等加载' │ 1 │ 10 │ 0 │ 11 │ │ 4 │ '点导出+弹窗' │ 1 │ 3 │ 1 │ 5 │ │ 5 │ '确认导出' │ 1 │ 3 │ 0 │ 4 │ │ 6 │ '等下载+收尾' │ 1 │ 10 │ 0 │ 11 │ │ 7 │ '核对文件' │ 1 │ 0 │ 0 │ 1 │ └─────────┴─────────────────┴──────┴──────────┴──────┴──────┘ 总轮数: 45 | 理想一次过: 10 轮 | 放大: 4.5x 输入token: 81000 | 输出token: 27000理想情况 10 轮(每个动作截一张图就完事),模拟器跑出 45 轮,放大了 4.5 倍——多出来的 35 轮全是等待轮询和点偏重试。拿到轮数,再看账单:同一个导出任务,走 computer use 和走结构化接口,按同一套官方价格算,差距到底多大。
// 成本折算:模拟器实测 45 轮 × 单轮 token × 官方价格(GPT-6.1 Sol:输入 2 / 输出 10 美元每百万 token)constROUNDS=45;// 上一个脚本实测输出:总轮数 45constPRICES={input:2,output:10};functioncuCost(rounds){constinputTokens=rounds*1800;// 每轮一张截图constoutputTokens=rounds*600;// 每轮判断+动作return{rounds,inputTokens,outputTokens,totalUsd:+((inputTokens/1e6)*PRICES.input+(outputTokens/1e6)*PRICES.output).toFixed(3)};}functionapiCost(calls){constinputTokens=calls*1000;constoutputTokens=calls*500;return{calls,inputTokens,outputTokens,totalUsd:+((inputTokens/1e6)*PRICES.input+(outputTokens/1e6)*PRICES.output).toFixed(3)};}constcu=cuCost(ROUNDS);constapi=apiCost(2);// 补只读接口后:查列表 1 次 + 导出 1 次console.log("computer use(点界面):");console.log(JSON.stringify(cu,null,2));console.log("结构化接口(2次调用):");console.log(JSON.stringify(api,null,2));console.log("倍率: "+(cu.totalUsd/api.totalUsd).toFixed(1)+"x");运行结果(Node 25.8.2 实测):
computer use(点界面): { "rounds": 45, "inputTokens": 81000, "outputTokens": 27000, "totalUsd": 0.432 } 结构化接口(2次调用): { "calls": 2, "inputTokens": 2000, "outputTokens": 1000, "totalUsd": 0.014 } 倍率: 30.9x三十倍。上一篇的演示参数是 45 轮对比 8 次调用(9.6 倍),我这个具体任务更极端——任务越简单(业务上本来一两次调用就能拿完),computer use 越亏:它的循环不会因为你业务简单就变少,界面步骤照样一步不落。
顺手回答一个肯定有人问的问题:单次 0.43 美元听着不多,至于吗?至于——这不是单次任务的事,是成本结构的事。同一个任务,接口版单次只要一分多;而"导出报表"在真实业务里是每天定时跑的,一天跑几百次,差距就从几毛钱滚成每天几十美元的差距,换成更贵的模型放得更大。单次看不出,放大才看得出来。
上面的成本差距,用一张图看得更直接。下图把任务按复杂度分成三档:简单任务就是“查列表 + 导出”2 次调用,中、高复杂度则是接口需要更多分页调用时的外推值;computer use 线同样按文中单轮真实成本做外推,实测点仍是开头的 45 轮 / $0.432,接口侧锚定 2 次调用 / $0.014。
交叉点远在右侧:要等接口复杂到上百次调用(比如分页拉取约 120 次以上),结构化接口的累计成本才会追上 computer use。日常这种“一两步就能拿完”的简单任务,computer use 亏出 30 倍不是单价差距,而是它 45 轮的界面步骤一步都省不掉——任务越简单,倍率越夸张。
排查:token 都烧到哪去了
等等,为什么是 45 轮?我一开始估的理想步骤不是十来步吗?把任务按阶段拆开,多出来的轮数主要花在三个地方,token 的去向也基本跟着这三块走:
45 轮循环的 token 去向(模拟器 45 轮实测折算,按 GPT-6.1 Sol 口径) 截图(图像输入) ██████████████████████████ 81000 tok ← token量最大(读也要看图) 判断+动作(输出) ██████████ 27000 tok(单价贵5倍,按成本算才是大头)- 等待 = 轮询:点完查询,假后台接口要几秒才返回,Agent 不知道什么时候加载完,只能一次次截图看状态。一次等待,就是好几轮循环
- 点偏 = 重试:筛选下拉框第一次点没弹出来,它又点了一次;导出按钮判断错位置,又点了一次
- 每轮循环都有三份开销:截图(图像 token)+ 判断(输出 token)+ 动作(输出 token)
把任务按阶段拆开看,理想轮数和模拟器实测的轮数差得很远:
| 阶段 | 理想(一次过) | 模拟器实测 | 多出来的原因 |
|---|---|---|---|
| 打开后台 | 1 | 5 | 首屏加载轮询 3 + 点偏重试 1 |
| 进报表页 | 1 | 5 | 路由等待轮询 3 + 点偏重试 1 |
| 填筛选条件 | 3 | 3 | 顺利一次过 |
| 点查询 + 等加载 | 1 | 11 | 查询 2.7s 才返回,只能轮询截图 |
| 点导出 + 弹窗 | 1 | 5 | 弹窗响应轮询 3 + 点偏重试 1 |
| 确认导出 | 1 | 4 | 生成文件等待轮询 3 |
| 等下载 + 收尾 | 1 | 11 | 下载 2.7s 才完成,只能轮询截图 |
| 核对文件 | 1 | 1 | 顺利一次过 |
| 合计 | 10 | 45 | 理想 10 轮滚成 45 轮,4.5 倍 |
10 轮的理想,滚成 45 轮。每一轮都不贵(按 Sol 的价格,单轮大概不到一美分),但 45 轮叠起来,原以为跟上一篇一样是个量级差,实际直接滚成了三十倍。
根因:读=操作,等待=轮询
复盘下来,根因是两个机制叠加,都不是 Agent"蠢":
- computer use 里"读数据"也要先"看图"——筛选出结果,人扫一眼就完事;Agent 得截一张图,把结果读进上下文。读一次,就是一次带图像的输入
- 异步任务没有"完成回调"——加载、下载这种异步操作,computer use 收不到"完成了"的通知,只能反复截图判断状态。等待越久,轮询越多
这两个机制让"理想步数"和"实际轮数"严重脱节。我估预算的时候,是按"打开-筛选-查询-导出-确认-下载"这些阶段一路顺畅来算的,默认每步一次过——真实世界里,等待和出错会把它撑成四五倍。
这里有个直接的教训:给 computer use 任务估预算,不能按理想步骤数估,得按"实际轮数"估,而实际轮数基本要翻四五倍(我这套参数模拟出来是 4.5 倍)。
修复:补只读接口 + 成本护栏
同一任务,补一个只读导出接口(跟界面系列第二弹说的结构化通道一个思路),成本立刻下来:
- 两次调用:查列表 + 导出
- 账单立刻降了一个量级(上面脚本里那两组数字)
- 额外收益:可鉴权、可审计、出错可重放
但有些场景确实没法开接口(第三方软件、权限拿不到),这时候 computer use 还得用。那就得上成本护栏——跑之前先按实际轮数估预算,超了就拦下来,别让它闷头跑。为啥要看这段:护栏的作用不是省那几毛钱,是让"烧爆账单"这种事故不发生。
// 成本护栏:跑 computer use 任务前先估预算// 45 轮来自上面的沙箱模拟器实测,换成你任务的轮数即可functionguard(estimatedLoops,imgTokensPerShot,thinkTokensPerStep,budgetUsd){constinputTokens=estimatedLoops*imgTokensPerShot;constoutputTokens=estimatedLoops*thinkTokensPerStep;constcost=(inputTokens/1e6)*2+(outputTokens/1e6)*10;// GPT-6.1 Solreturn{estimatedLoops,cost:+cost.toFixed(3),budgetUsd,allowed:cost<=budgetUsd};}console.log(guard(45,1800,600,0.2));// 预算 0.2 美元console.log(guard(45,1800,600,0.5));// 预算 0.5 美元console.log(guard(45,1800,600,0.1));// 预算 0.1 美元运行结果(Node 25.8.2 实测):
{ estimatedLoops: 45, cost: 0.432, budgetUsd: 0.2, allowed: false } { estimatedLoops: 45, cost: 0.432, budgetUsd: 0.5, allowed: true } { estimatedLoops: 45, cost: 0.432, budgetUsd: 0.1, allowed: false }45 轮的成本四毛多,预算给 0.2、0.1 的直接拦掉,给 0.5 的放行。真线上跑的时候,预算按"实际轮数"估(理想步骤 × 四五倍,我这套参数模拟出来是 4.5 倍),别按理想步骤估。
边界:computer use 什么时候反而划算
这套账算下来,computer use 也不是一无是处。反过来看,有几类场景它是划算的:
| 场景 | 为什么划算 |
|---|---|
| 一次性任务 | 反正只跑一次,开发接口的人力成本比 token 成本贵 |
| 第三方软件 | 没有接口权限,computer use 是唯一解 |
| 低频只读场景 | 偶尔用一次、只拿数据不写,每次 token 成本不高,不值得为它做接口 |
判断标准跟上一篇一致:这个任务会不会反复跑?这个数据能不能只读拿到?反复跑、能只读拿到,就开接口;一次性、拿不到接口,再放 computer use。
错误处理与降级策略
前面那套成本护栏解决的是“跑之前放不放行”,但任务真跑起来,还有另一类问题:跑到一半失败了怎么办。computer use 不像结构化接口那样有明确的错误码和重放机制,它更接近“点着点着卡住了”——所以得提前想清楚:哪些失败可以原地重试,哪些该立刻止损降级。
先说重试策略。失败要先分两类:
- 偶发失败:点偏了、加载慢了一点、弹窗出来慢了半拍。这类值得重试,但必须有次数上限,不能同一个动作无限重试。
- 结构性失败:按钮位置变了、页面改版、筛选框根本没了。这类重试也白搭,越试越烧钱,应该直接降级。
对应到实现上,最好设“双层护栏”:单步重试上限 + 全程最大轮数上限。单步重试解决“这一步点偏了再试几次”,全程轮数上限解决“整体卡死、陷入循环”。最大轮数不能拍脑袋设一个超大值,可以按任务的理想步数乘一个放大系数(本文这套参数里实测是 4.5 倍),再留一点余量。
然后是降级顺序。跑不下去的时候,按这个优先级走:
- 能转结构化接口就先转接口:像导报表这种“读数据”任务,本来就有只读接口可选,computer use 一失败,直接改调接口最稳。
- 没有接口就转人工:把当前状态、已完成到哪一步、最后一张截图、建议人工执行的动作整理出来交给人,而不是让 Agent 继续盲目试探。
- 绝不无限重试:超过最大轮数或单步重试上限,必须强制终止,避免成本雪崩。
下面是一段伪代码,展示这个降级判断逻辑:
function runTaskWithFallback(task) { const idealSteps = estimateIdealSteps(task); // 理想情况一步过需要多少步 const maxRounds = idealSteps * 5; // 按放大系数设上限,本篇实测约 4.5x const maxStepRetry = 3; // 单步最多原地重试 3 次 let rounds = 0; while (!task.done) { rounds++; if (rounds > maxRounds) { return fallback(task, "超过最大轮数上限"); } const result = computerUseStep(task); if (result.ok) continue; if (result.retryable && result.stepRetry < maxStepRetry) { result.stepRetry++; continue; // 偶发失败,原地重试 } // 重试仍然失败:能开接口就降级到结构化接口 if (task.hasReadonlyApi) { return task.callApi(); // 结构化接口兜底 } return handoffToHuman(task); // 没有接口,转人工处理 } return task.result; } function fallback(task, reason) { if (task.hasReadonlyApi) return task.callApi(); return handoffToHuman(task, reason); }核心就是一句:允许有限重试,但必须预留降级出口。computer use 最怕的不是某一步失败,而是失败之后没有刹车、重复烧钱。
收尾
一句话总结:computer use 不是不能用,是"每一步都在付账"这件事,比想象中严重——读数据要付账,等加载也要付账,点偏了重试还要付账。让 AI 点界面之前,先把账算清楚(这篇的模拟器脚本拿回去,改参数就能算);能开接口的补个接口;真不能开接口的,至少先估预算、上成本护栏。
对了,这篇的轮数是本地沙箱模拟器跑出来的,参数都写在脚本里——你在自己的任务里改参数重跑,就能验证"等待轮询、点偏重试"这两件事到底会把账单撑到多少倍。模拟器只能验证机制,真实模型还会看错界面、多绕弯路,实际账单大概率只高不低。价格按 09.29 DevDay 公告的 GPT-6.1 Sol 定价折算(输入 $2 / 输出 $10 每百万 token),computer use 的能力和价格都在快速迭代,动手之前以官方文档最新版本为准。
参考资料
- Agents API 官方文档:computer use 的上游能力入口,本文“截图→判断→动作”的循环机制属于 Agents API 的 computer use 能力范围。
- computer use tools:文中所模拟的 computer use 每一步“截图、判断、动作”的官方机制说明来源。
- OpenAI API 定价页:09.29 DevDay 公告的 GPT-6.1 Sol 定价折算依据(输入 $2 / 输出 $10 每百万 token),实际动手前以官方最新版为准。