本周主题:主动性 Agent 工程周 · Day 5(收官)
11 月 6 日有三件事同时发生:Perplexity 把 always-on 的 Teammates 绑进 200 美元/月的 Max 套餐;月之暗面把 Agent 的 Harness 直接做成 7×24 托管的云服务(Kimi Hosted Agents,140+ Skills、独立沙箱、会话可中止并导出审计);Mistral 发布 Studio,把"可自部署"当差异化。
三家路线不同,但都得回答同一个工程问题:一个常驻 Agent 的钱,到底花在哪一段。
这个问题不能靠感觉答,因为三笔账的计费单位根本不一样。
| 账 | 计费单位 | 谁在计 | 你能控制的部分 |
|---|---|---|---|
| 模型账 | token(未命中 / 缓存命中 / 输出,三档价) | 每次调用 | 前缀稳定性、步数、模型档位 |
| 沙箱账 | vCPU-小时(只算忙)+ GB-小时(含空闲) | 墙钟时间 | 停策略、预置内存、要不要常挂 |
| 评测账 | 用例 × 重复次数 × 单次跑 | 每次 eval | k 值、用例集、指纹复用 |
注意第二行:内存按"挂着的时间"计费,CPU 按"忙的时间"计费。Vercel Sandbox 的文档写得很直白——Active CPU 0.128 美元/vCPU-小时,不含等 I/O 与等模型的时间;预置内存 0.0212 美元/GB-小时,明确"含空闲时间"。E2B、Daytona、Modal 的口径大同小异。
这一条决定了下面所有的结论。
一、先写账本,再谈优化
下面这段是核心逻辑,纯标准库,去掉注释 40 行左右:
P_TICK = (0.10, 0.01, 0.50) # 值守:未命中输入 / 缓存读 / 输出
P_TASK = (2.00, 0.20, 10.00) # 执行:同上
CPU, MEM = 0.128, 0.0212 # $/vCPU-h, $/GB-h(内存含空闲)
TICKS, T_IN, T_CACHE, T_OUT = 336, 1_200, 4_000, 100
TASKS, S_IN, S_CACHE, S_OUT = 21, 88_000, 590_000, 10_000
ACCEPT, GB, BUSY = 14, 8.0, 1.47
IDLE, CASES, K, EVAL_RUN = 0.65, 40, 3, 0.018
def cost(tin, tc, tout, p):
return (tin * p[0] + tc * p[1] + tout * p[2]) / 1e6
def bill(idle=IDLE, hours=None, k=K, hit=None):
ti, tc = T_IN, T_CACHE
si, sc = S_IN, S_CACHE
if hit is not None: # 按同一命中率重算两个桶
for name in ("t", "s"):
b = (T_IN + T_CACHE) if name == "t" else (S_IN + S_CACHE)
c = int(b * hit)
if name == "t":
ti, tc = b - c, c
else:
si, sc = b - c, c
model = TICKS * cost(ti, tc, T_OUT, P_TICK) + TASKS * cost(si, sc, S_OUT, P_TASK)
h = hours if hours is not None else BUSY / (1 - idle)
return model, BUSY * CPU + GB * h * MEM, CASES * k * EVAL_RUN
m, s, e = bill()
t = m + s + e
print(f"model={m:.2f} sandbox={s:.2f} eval={e:.2f} total={t:.2f} per_success={t / ACCEPT:.4f}")
quiet = TICKS * cost(T_IN, T_CACHE, T_OUT, P_TICK) / TICKS
action = cost(S_IN, S_CACHE, S_OUT, P_TASK)
print(f"quiet={quiet:.5f} action={action:.5f} ratio={action / quiet:.0f}")
h24 = 24 * 7
m2, s2, e2 = bill(hours=h24)
print(f"sandbox_24x7={s2:.2f} per_success_24x7={(m2 + s2 + e2) / ACCEPT:.4f}")
m3, _, _ = bill(hit=0.41)
print(f"hit41_model={m3:.2f}")
实跑输出(Python 3.13,两次运行 cmp 逐字节一致):
model=8.34 sandbox=0.90 eval=2.16 total=11.41 per_success=0.8146
quiet=0.00021 action=0.39400 ratio=1876
sandbox_24x7=28.68 per_success_24x7=2.7990
hit41_model=20.20
场景设置:7 天每 30 分钟唤醒一次(336 次),其中 21 次决定动手,14 次通过验收;值守用便宜档(0.10 / 0.01 / 0.50),执行用强档(2.00 / 0.20 / 10.00);单次任务循环 8.8 万未命中 + 59 万命中 + 1 万输出。
二、三个反直觉
① 值守和执行差三个数量级。
quiet=0.00021,action=0.39400,1876 倍。
所以"值守用便宜档、执行用强档"不是省钱小技巧,是架构上的默认正确。同一份账本里,模型账 8.34 美元中值班只占 0.07,执行占 8.27。
反过来说,任何"用一个模型从头跑到尾"的常驻设计,都在用一个错误的价格做判断。而本周反复讲的那句"主动性的门槛是忍住不干活",到这里有了价格标签:不是体验问题,是三个数量级的成本结构。
② 优化顺序错得很常见:大家都去调超时,真正的大头是"要不要常挂"。
我把空转率从 65% 调到 48%(对应超时 15 分钟 → 5 分钟,取 Archil 那组 30 天 384 个循环、3.7 万次工具调用的复盘数据),墙钟从 4.20 小时缩到 2.83 小时,沙箱账从 0.90 降到 0.67——每个成功任务只省 0.0166 美元,2%。
但同一份账本,把"按需开机"换成"24×7 常挂":墙钟 4.20 → 168 小时,沙箱账 0.90 → 28.68,每个成功任务从 0.81 涨到 2.80。多花的 27.78 美元占总账 71%,换来零收益。
一个差 2%,一个差 30 倍。这就是为什么我一直建议先把"常驻粒度"定下来,再去调那些参数——调参是最容易做的优化,也是最容易做错的优化。
③ 缓存命中率是账单的开关,不是优化项。
命中 85.9% 时模型账 8.34;拉到 41%(业界对真实 agent 负载的实测下限附近)就是 20.20。hit41_model=20.20 这一行就是同一个程序换个参数跑出来的——没有一行业务代码变差,只是前缀断了。
GPT-6 起缓存读按输入价一折计,Claude 侧缓存读 0.30 vs 输入 3.00、写 3.75(+25%,第二轮回本)。真实折扣没有理论上限漂亮:有实践者手工实现后月账单从 5 万降到 1.5 万美元(命中率 73%),而行业测算认为真实 agent 负载落在 41%~80%。
三、踩坑记录(都是真踩的)
坑 1:参数传了,不等于参数接上了。
第一版 total(w, idle_ratio=0.48) 接受 idle_ratio,但内部调用 sandbox_cost(w, hours=None) 时,hours=None 会让它回落到 wall_hours(w),用的还是 w.idle_ratio = 0.65。于是杠杆一永远算出"省 $0.0000(0.0%)"——一个看起来完全正常的 0。
这类 bug 最毒的地方在于它不报错,只给你一个"这个优化没用"的错误结论。修法是把默认值解析提前:
h = hours if hours is not None else wall_hours(w, idle_ratio)
return model_cost(w) + sandbox_cost(w, h) + eval_cost(w, k)
并且加一条断言把口径钉死:abs(wall_hours(w) - busy/(1-idle)) < 1e-9。
坑 2:口径混账,把"平均值"当成"单项成本"。
我最初这样算"安静唤醒的成本":
quiet = model_cost(W) / W.ticks # 错
结果 quiet = 0.02464,action = 0.39429,比值只有 16 倍——和"三个数量级"的直觉完全不符。原因是分子里混进了执行 token,分母却是唤醒次数。不同计费对象不能相除。
正确做法是把两个 token 桶分开存(tick_in/task_in),各自乘自己的价格表,再各自除以自己的次数。改成两套价之后,比值变成 1876——差了两个数量级,结论直接反过来了。
坑 3:一套价格表算不出"降级省多少"。
值守用便宜档、执行用强档时,如果 cost() 只接一个价格元组,你就永远算不出"把执行档降一级"的真实收益,因为分母(成功率)没跟着变。价格和成功率必须成对出现,见第四节。
坑 4:中文文案进 f-string,引号会把字符串截断。
print(f" ${quiet:.5f} ← 只是"看了一眼,决定不动"") # SyntaxError
SyntaxError: invalid character ','——报错指向中文逗号,但真凶是 ASCII 双引号提前闭合了字符串。要么把引号换成中文引号(f-string 内部不冲突),要么干脆去掉。这条不算深坑,但每次「把文章里的句子直接粘进代码」都会中一次。
四、临界点公式:便宜模型什么时候反而更贵
设单次成本 c、成功率 p,则每个成功任务的成本是 c / p。
降级到便宜模型的充分必要条件:
c_c / p_c < c_e / p_e ⇔ p_c > p_e · (c_c / c_e)
也就是说:临界成功率 = 现模型成功率 × 价格比。
代入本账本:现模型单次 0.3940、成功率 90%;便宜模型单次 0.0197(价格比 5.0%)。临界成功率 = 90% × 5.0% = 4.5%。
| 便宜模型成功率 | 每成功成本 | 结论 |
|---|---|---|
| 60% | 0.0328 | 更便宜 ✓ |
| 30% | 0.0657 | 更便宜 ✓ |
| 5% | 0.3940 | 更便宜 ✓ |
| 1% | 1.9700 | 更贵 ✗ |
价格差 20 倍时,降级几乎稳赚——它只要 4.5% 的成功率就持平。但如果价差只有 2 倍(价格比 50%),临界成功率就是 45%,那才是真赌,必须自己跑评测。
这也解释了为什么"便宜的 agent 比贵的贵"不是修辞:AWS 那边给的换算是一样的——重试到成功时,每次成功的成本 ≈ 单次成本 ÷ 成功率;60% 成功率就是为每个成功付 1.7 次的钱。
五、评测账:k 值是个预算决策
pass^k 的独立近似是 p^k:
| 单次成功率 | pass^3 | pass^5 | pass^8 |
|---|---|---|---|
| 0.95 | 0.86 | 0.77 | 0.66 |
| 0.90 | 0.73 | 0.59 | 0.43 |
| 0.80 | 0.51 | 0.33 | 0.17 |
| 0.70 | 0.34 | 0.17 | 0.06 |
p=0.80 的 Agent,单跑一次记"通过",pass^1 是 80%;连做 8 次全对只有 17%。τ-bench 报过类似现象:前沿 Agent 单次分漂亮,pass^8 普遍低于 25%。
工程折中 k=3~5。本账本里 k 从 3 提到 5,评测账 2.16 → 3.60,每个成功任务 0.81 → 0.92(+13%)。这 13% 买的不是分数,是"能不能无人值守"的资格。
评测本身也在变得可缓存:Scale AI 的 AgentEnv(Apache 2.0,PyPI 标 Beta)用容器化环境 + DAG 任务给任意沙箱里的 Agent 打分,Vercel Sandbox 自 0.9.1334 起是它的第五个后端,Agent 与任务环境各跑一个 Sandbox;Vercel 的 agent-eval CLI 用 PROMPT.md + EVAL.ts(Vitest)声明任务,打分能看它跑了哪些命令、读了哪些文件、调了多少次工具,并且指纹相同的重跑复用结果、不重复付费——这正是把 k 抬到 3~5 还付得起的原因。
六、局限,说在前面
- 账本是模拟算例,按公开计价口径搭建,不是任何厂商的真实账单;token 分布是我设的场景,换业务形态数字会变,但结构不变。
- 未计入网关、日志存储、向量库、人工复核的成本——真实总账只会更高。
pass^k = p^k是独立性近似,实际存在相关性(同一个 bug 会稳定复现),真实值通常比表里更差。- 缓存折扣 90% 是理论上限,真实 agent 负载请按 41%~80% 估。
- AgentEnv 的成熟度有独立核查文提示:Beta、频繁发版、"任意 agent 任意沙箱"未经验证,先跑 README 的 hello task 再说。
七、收口
三本账、一个分母,收成一句:
always-on 的计费单位是"挂着的秒",不是"干的活"。
模型按 token、沙箱按时间、评测按重复次数——三者共同的乘数,是"它在你没看的时候占了多少资源"。所以预算表的最后一行永远是每个通过验收的任务多少钱,不是每百万 token 多少钱。分母错了,所有优化都会优化到错的地方。
本周五天的账,到这里合上了:Day1 讲谱系与架构,Day2 讲触发与沉默,Day3 讲权限不能由上下文改,Day4 讲供应链与工具链风险,今天讲钱和证据。四条判断的终审在长文里,这里只留最后一句——克制是唯一同时降低成本和提高体验的设计。
文中所有厂商数字均按自报处理,未经独立核验者不作为采购依据。