数据截至 (上游 commit bffdb59c60cb)
Code Agent 与 Behavior Best-of-N
本章讲 S3 区别于前代的两个杀手锏:嵌套 Code Agent(把数据活交给代码)和 Behavior Best-of-N(测试期跑多次再投票)。最后给一段横向对比。
4.1 为什么需要 Code Agent:有些活点鼠标干太蠢
"把 B 列 500 行求和""把日期列统一改成 ISO 格式"——这类任务用 GUI 一格格点既慢又易错,但写几行代码就稳。S3 的回答:给 Worker 一个 call_code_agent 动作,把这类活整包甩给一个嵌套的代码执行子 agent。
Worker 的提示词把分工讲得很死(procedural_memory.py:20-58):
| 用 GUI Agent | 用 Code Agent |
|---|---|
| 点击、输入、导航、视觉元素、图表、透视表、打印/导出设置 | 所有表格计算、数据处理(过滤/排序/替换)、批量操作、格式批改 |
注意一条硬规矩:图表、透视表等视觉产物永远用 GUI,绝不用 Code Agent(procedural_memory.py:58)。
4.2 Code Agent 怎么跑:有预算的代码 REPL
CodeAgent.execute(code_agent.py:113)是一个带步数预算(默认 20)的小循环:
收到任务 + 当前截图
│
└─ while 步数 < budget:
├─ LLM 产出 <thoughts>...</thoughts><answer>```python ...```</answer>
├─ 若 answer == DONE/FAIL → 结束
├─ extract_code_block:抠出 python 或 bash 代码
├─ execute_code:交给 env_controller 真跑
│ run_python_script / run_bash_script
├─ 把执行结果(stdout/stderr/状态)拼回对话
└─ 步数 +1
│
└ ─ _generate_summary:让另一个 LLM 把整段执行总结成 <150 词的报告
结束后返回一个结构化结果(code_agent.py:266-273):completion_reason(DONE/FAIL/BUDGET_EXHAUSTED)、steps_executed、summary、完整 execution_history。这个报告会在下一步被拼进 Worker 的消息里(worker.py:209-302),让大脑知道代码侧干了什么。
两个关键约束(都在提示词里):
- 每步代码不跨步持久化——必须写"自包含"的片段(
procedural_memory.py:170)。这强制 agent 把"找文件→读内容→改→验证"拆成独立小步,而不是一坨大脚本。 - GUI 必须验收:Worker 被反复警告"绝不能只信 Code Agent 的输出,必须用 GUI 打开文件亲眼核对再
agent.done()",而且"改完文件可能要完全关掉再重开应用才看得到变化"(procedural_memory.py:51-54)。
职责切分的妙处: Code Agent 只碰"文件内容和脚本",GUI agent 负责"找到并验证"。代码擅长批量精确变更,视觉擅长确认"用户真的看到对的结果"——两者验收闭环。
执行环境由 env_controller 抽象。CLI 里若开 --enable_local_env,就用 LocalEnv 在本机直接 subprocess 跑代码(local_env.py)——源码里明确标了"WARNING: Executes arbitrary code locally"。没有可用 controller 时,call_code_agent 动作会被整个隐藏(worker.py:69-73)。
4.3 Behavior Best-of-N:S3 的招牌
S3 论文标题的"首超人类 72.6%"里,从 66%(单跑)到 72.6% 的那一截,主要来自 Behavior Best-of-N(BBoN,行为最优 N 选)。
核心思路: 同一个任务,跑 N 次得到 N 条不同的操作轨迹;然后让一个 VLM 评委比较这 N 条,选出完成得最好的那条。难点在于:轨迹有几十步、几十张图,直接全塞给评委又贵又乱。BBoN 的解法是先把每步"翻译成一句话事实字幕",再让评委基于字幕 + 首尾截图来判。
这是个离线、两阶段流程(不在实时 cli_app 里,而在 osworld_setup/s3/bbon/ 的评测脚本里编排):
N 条已跑完的轨迹(每条 = 一串截图 + traj.jsonl 动作)
│
阶段一:BehaviorNarrator —— 给每一步配"事实字幕"
│ 对每对 (前截图, 动作, 后截图):
│ ├─ 在前图上标注动作位置(点=红圈、移动=蓝圈、拖拽=绿线)
│ ├─ 在后图上裁一块放大区(看清细节)
│ └─ VLM 输出"这一步实际发生了什么"的精确描述
│ → 每条轨迹得到一串 fact captions
│
阶段二:ComparativeJudge —— N 选 1
│ 把 N 条轨迹的 (首截图, 末截图) + 各自字幕喂给评委 VLM
│ 评委按一份严格 rubric 逐条核对,输出胜出轨迹的编号
│
→ 选中的那条轨迹就是 best-of-N 的答案
阶段一:BehaviorNarrator(行为字幕)
BehaviorNarrator.judge(behavior_narrator.py:170)的精髓是把动作画到截图上再让 VLM 描述,而不是干巴巴给坐标。mark_action(behavior_narrator.py:37)按动作类型画标记:
| 动作 | 视觉标记 |
|---|---|
| 点击 | 红圈 + "Click" |
| 移动 | 蓝圈 + "MoveTo" |
| 拖拽 | 蓝圈起点 + 绿线 + 绿圈终点 "DragTo" |
它还会在"后截图"上裁一块放大去噪的区域(get_zoomed_image,behavior_narrator.py:106),并画红框标出放大位置——帮 VLM 看清"那个小勾选框到底勾上没有"这种细节。提示词(BEHAVIOR_NARRATOR_SYSTEM_PROMPT,procedural_memory.py:297)反复强调:别假设动作成功了,要看视觉标记和前后变化的实证。
为什么先配字幕? 直接把几十张原图丢给评委,它很难精确说清"第 7 步到底改了啥"。先把每步"压缩成一句经过视觉核对的事实",评委就能基于可靠、紧凑的文字证据来比较,而不是自己脑补图里发生了什么。
阶段二:ComparativeJudge(比较评委)
ComparativeJudge.judge(comparative_judge.py:66)把 N 条轨迹的首/末截图 + 字幕拼进一条消息,用 VLM_EVALUATOR_PROMPT_COMPARATIVE_BASELINE(procedural_memory.py:326)那份很长的 rubric 来判。rubric 里全是"硬约束":必须保持表格原格式/行列顺序、必须关掉自己开的弹窗/标签、必须用软件内置功能而非 hack、Chrome 设置必须关标签页才生效……评委逐条核对后,在 <answer> 里吐一个整数——胜出轨迹的编号(comparative_judge.py:137-148)。
这套评测在 run_judge.py 里被组织成"渐进式":BoN2、BoN3……分别对应"从 2 次/3 次 rollout 里选",用来画出"采样次数越多、准确率越高"的曲线(osworld_setup/s3/bbon/run_judge.py:154-177)。
4.4 把两件事连起来看 S3 的世界观
S2 的赌注:把智能花在"规划"
Manager → DAG 子任务 → 知识库/网络检索 → 逐个执行
S3 的赌注:把智能花在"采样 + 评选 + 工具"
单 Worker 一路走 ──┐
数据活甩给 Code Agent ┤→ 跑 N 次 → BehaviorNarrator 配字幕
GUI 负责验收 ──┘ → ComparativeJudge N 选 1
这是两种"把算力换准确率"的不同哲学。S3 证明:对 computer-use,简单执行器 + 测试期扩展,胜过复杂的前置规划。
4.5 横向对比(同 shelf 兄弟)
| 维度 | Agent S3 | 典型对照 |
|---|---|---|
| 定位方式 | 两段式:大脑只描述,独立定位模型(UI-TARS)出坐标 | 不少 computer-use agent 让单一 VLM 直接吐坐标 |
| 规划 | 无层级,单 Worker(S3);S2 才有 DAG 规划 | 很多框架坚持显式分层规划 |
| 测试期扩展 | 离线 Behavior Best-of-N(配字幕→VLM 评委) | 朴素 self-consistency / 多数投票 |
| 数据任务 | 专门的嵌套 Code Agent + GUI 验收 | 纯 GUI 点操作 |
4.6 边界与局限(诚实)
- Behavior Best-of-N 是离线的。 它在
osworld_setup/s3/bbon/的评测脚本里跑,依赖磁盘上已存的step_*.png和traj.jsonl;实时cli_app.py的单次运行不自动做 best-of-N。要在线用得自己编排。 - 本机代码执行有风险。
--enable_local_env会在你的机器上exec/subprocess跑模型生成的任意代码,源码自己标了 WARNING;提示词里甚至硬编码了 OSWorld 评测环境的 sudo 密码osworld-public-evaluation(procedural_memory.py:110)——那是为基准容器准备的,不是给你的真机。 - 强依赖一个好的定位模型。 整个准确率的地基是 UI-TARS 这类定位模型的质量;
generate_coords的坐标解析也很朴素(正则抓数字,grounding.py:244),定位模型回复古怪时会脆。 - CLI 默认只跑 15 步。
run_agent的循环写死range(15)(cli_app.py:160),长任务在 CLI 演示模式下可能没走完就停了。 set_cell_values绑死 LibreOffice + Linux。 它走 UNO socket(grounding.py:51),非 Linux 平台会被skipped_actions跳过(worker.py:64-65)。
代码地图(导航索引)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 嵌套代码 agent | gui_agents/s3/agents/code_agent.py | CodeAgent.execute / _generate_summary |
| 触发 code agent 的动作 | gui_agents/s3/agents/grounding.py | OSWorldACI.call_code_agent |
| GUI/Code 分工提示词 | gui_agents/s3/memory/procedural_memory.py | construct_simple_worker_procedural_memory(Agent Usage Guidelines) |
| Code agent 提示词 | gui_agents/s3/memory/procedural_memory.py | CODE_AGENT_PROMPT |
| 本机执行环境 | gui_agents/s3/utils/local_env.py | LocalEnv / LocalController |
| 行为字幕(BBoN 阶段一) | gui_agents/s3/bbon/behavior_narrator.py | BehaviorNarrator.judge / mark_action / get_zoomed_image |
| 比较评委(BBoN 阶段二) | gui_agents/s3/bbon/comparative_judge.py | ComparativeJudge.judge |
| 评委 rubric | gui_agents/s3/memory/procedural_memory.py | VLM_EVALUATOR_PROMPT_COMPARATIVE_BASELINE |
| BBoN 离线编排 | osworld_setup/s3/bbon/generate_facts.py / run_judge.py | generate_fact_captions_parallel / run_experiment |