跳到主要内容

顶层架构与两段式设计

本章讲清两件事:为什么要把"大脑"和"定位"拆成两个模型,以及 S1 → S2 → S3 的演化主线(理解 S3 为什么"扁平"的关键)。

1.1 核心矛盾:一个模型既要会想,又要会瞄准,很难两全

computer-use 的难点不是"调用模型",而是把模型说的那句话,精确落到屏幕的某个像素上

这里藏着一个能力错配:

  • 会推理的大模型(GPT-5、Claude)擅长"看懂这屏在干嘛、下一步该做什么",但它们报坐标往往不准。
  • 专门的视觉定位模型(如 UI-TARS)被训练成"给我一句描述 + 一张图,我回你一个精确的点",但它们的通用推理弱。

Agent S 的回答是:不强求一个模型全包,而是分工。 大脑模型只需要会用英文描述它想点什么("右上角那个红色叉号"),定位模型负责把描述变成 (x, y)

┌───────────── 大脑模型(--model, 如 GPT-5)─────────────┐
│ 职责:看屏 → 推理 → 输出 agent.click("红色叉号") │
│ 弱点:自己报坐标不准 → 所以它只"描述",不"报点" │
└────────────────────────┬───────────────────────────────┘
│ 把描述"红色叉号"交出去

┌──────────── 定位模型(--ground_*, 如 UI-TARS)─────────┐
│ 职责:描述 + 截图 → 输出一个点 (x, y) │
│ 强项:被专门训练做"指哪"——准 │
└─────────────────────────────────────────────────────────┘

这就是为什么 CLI 必须传两组模型参数(cli_app.py:333-349):engine_params 给大脑,engine_params_for_grounding 给定位器,两者甚至可以指向完全不同的服务商和端点。

1.2 顶层类:UIAgentAgentS3

S3 的顶层封装薄到几乎透明。AgentS3 继承自抽象基类 UIAgent,只做一件事:把请求转交给内部的 Worker

# 示意,非源码 —— 演示 AgentS3.predict 的本质
def predict(self, instruction, observation):
# 没有规划层、没有子任务调度,直接问 Worker:下一个动作是什么?
executor_info, actions = self.executor.generate_next_action(
instruction=instruction, obs=observation
)
return executor_info, actions # actions 是一行 pyautogui 代码

真实实现见 AgentS3.predict(agent_s.py:85-94)。它的类注释一句话点破设计取向:

class AgentS3(UIAgent): """Agent that uses no hierarchy for less inference time"""(agent_s.py:48-49)

"no hierarchy"(无层级) 就是 S3 区别于 S2 的口号。AgentS3.reset() 里也只构造了一个 Worker,没有 Manager(agent_s.py:75-83)。

1.3 演化主线:S1 → S2 → S3

这个仓库里同时保留了四代代码(gui_agents/s1s2s2_5s3),理解它们的取舍演化,比单看 S3 更能看懂"为什么是现在这样"。

代次规划方式记忆 / 知识标志特征
S1有 Manager有知识库 + 网络检索首版,引入 ACI(Agent-Computer Interface)概念
S2层级规划:Manager 把任务编成 DAG 子任务图叙事记忆 + 情节记忆 + Perplexica 网络搜索"经理 + 工人"分层,SOTA on OSWorld
S2.5简化版砍掉部分组件"更简单、更快"
S3无层级:一个 Worker 一路走到底无知识库、无网络检索加 Code Agent + 离线 Behavior Best-of-N,首超人类

S2 的 Manager 确实重——它有 DAG 翻译器、叙事/情节记忆总结、知识库检索、网络搜索(见 gui_agents/s2/agents/manager.py 中的 _generate_dagsummarize_narrativeKnowledgeBaseget_action_queue 等符号)。

S3 的判断是:这些层级机制带来的延迟和复杂度,不如把算力花在"多跑几次再投票"上。 于是:

  • 删掉 Manager / DAG / 知识库 / 网络检索 → 单步推理更快、实现更简单。
  • 新增 Code Agent:数据类子任务交给代码执行,比一格格点准得多(见 04 章)。
  • 新增 Behavior Best-of-N:在测试期对同一任务跑 N 次,用一个 VLM 评委选最优 rollout(见 04 章)。

这条主线值得记住:S2 把智能放在"规划"上,S3 把智能放在"采样 + 评选"上。 后者更贴合"测试期扩展(test-time scaling)"的潮流。

1.4 部件如何串起来(一步的完整数据流)

01 的抽象落到具体调用链:

run_agent (cli_app.py:155)
└─ 截图 → obs["screenshot"]
└─ agent.predict() agent_s.py:85
└─ Worker.generate_next_action() worker.py:178
├─ (可选) _generate_reflection worker.py:125
├─ 大脑模型产出计划 + 动作代码 worker.py:314 call_llm_formatted
└─ create_pyautogui_code common_utils.py:15
└─ eval("agent.click('红色叉号')")
└─ OSWorldACI.click grounding.py:346
└─ generate_coords → 定位模型 → (x,y) grounding.py:230
└─ exec(code) 真机执行 cli_app.py:215

注意一个巧妙处:Worker 输出的动作代码字符串(如 agent.click("..."))是通过 eval 运行的,运行的副作用就是去问定位模型要坐标、返回一段 pyautogui... 字符串;这段字符串再被上层 exec 真正执行。也就是说,"翻译"和"执行"被切成两次求值——这让格式校验器可以在真机动手前先 eval 一遍验证动作合法(见 03 章的 CODE_VALID_FORMATTER)。

代码地图(导航索引)

主题文件路径符号名
顶层封装 / 无层级gui_agents/s3/agents/agent_s.pyAgentS3 / UIAgent.predict
两组引擎参数gui_agents/s3/cli_app.pymain(engine_params / engine_params_for_grounding)
S2 层级规划(对照)gui_agents/s2/agents/manager.pyManager._generate_dag / get_action_queue
S2 知识库(对照)gui_agents/s2/core/knowledge.pyKnowledgeBase
多后端 LLM 封装gui_agents/s3/core/mllm.pyLMMAgent