跳到主要内容

Worker 主循环与反思

本章讲 S3 的"大脑"每一步到底在干嘛:Worker.generate_next_action 的单步流程、反思 agent 如何防止原地打转、长对话怎么裁剪图片、以及"必须只输出一个动作"的格式校验重试。

3.1 一步循环全景

Worker 既是"生成器"又编排"反思器"。reset() 时它创建两个 LLM agent(worker.py:79-82):

  • generator_agent:大脑,系统提示词由 ACI 动作自动拼成(见 02 章)。
  • reflection_agent:反思器,系统提示词是固定的 REFLECTION_ON_TRAJECTORY

单步流程(generate_next_action,worker.py:178):

新截图 obs

├─ 把截图绑到 grounding agent(后续动作要用)

├─ _generate_reflection:对"上一步动作 + 现在的屏幕"产出一句反思
│ (turn 0 不反思,只记录初始屏)

├─ 拼用户消息 = 反思 + 文本便签 notes + (上轮 Code Agent 结果) + 截图

├─ call_llm_formatted:生成"计划 + 单个动作代码",带格式校验重试

├─ parse_code_from_string:抠出最后一个 ``` 代码块
│ └─ create_pyautogui_code:eval 它 → 触发定位 → 得到真机代码

└─ flush_messages:裁剪历史(只留最近 k 张图)
→ 返回 [exec_code]

3.2 反思 agent:专治"原地打转"

computer-use 最常见的失败模式是循环——agent 反复点同一个没反应的按钮。反思 agent 的唯一职责就是发现这种循环并提醒,但刻意不给具体建议

看它的提示词约束(procedural_memory.py:120 REFLECTION_ON_TRAJECTORY),它被要求只能输出三种之一:

情况反思该说什么
Case 1:没按计划走(常因动作循环)指出为什么偏了,鼓励改动作——但不准推荐具体动作
Case 2:进展正常简短地说"继续"
Case 3:任务已完成告诉 agent 已成功

为什么不让反思器出主意? 让它只做"裁判"不做"教练",避免它的具体建议污染生成器的决策、或两个 agent 互相带偏。它的价值是元层面的进度监控,而非另一个规划者。这与 S2 用 Manager 做真正规划形成对比(见 01/04)。

反思结果会以 REFLECTION: ... 前缀拼进生成器的用户消息(worker.py:201-203),作为"参考"而非命令。

3.3 输出契约:一段推理 + 恰好一个动作

生成器被强制按固定结构输出(procedural_memory.py:87-115):先"上一步验证"、再"屏幕分析"、再"下一步(自然语言)"、最后"落地动作"(一个 python 代码块)。规则里反复强调一次只能一个动作、代码块里只能一行

这条契约靠两个格式校验器在运行时硬性兜底(worker.py:310-313):

# 真实源码,worker.py:310
format_checkers = [
SINGLE_ACTION_FORMATTER, # 必须恰好 1 个 agent.xxx() 调用
partial(CODE_VALID_FORMATTER, self.grounding_agent, obs), # 这行代码 eval 得通过
]
plan = call_llm_formatted(self.generator_agent, format_checkers, ...)
  • SINGLE_ACTION_FORMATTER:用正则数代码块里有几个 agent.xxx(...),必须正好 1 个(formatters.py:10-19)。
  • CODE_VALID_FORMATTER:试着 eval 这行动作,抛异常就算不合法(formatters.py:30-40)。这一步会真的触发定位模型,等于在真机动手前先把整条链路跑通一遍。

校验不过怎么办?call_llm_formatted(common_utils.py:59)把失败原因拼成反馈消息塞回对话、让模型重答,最多重试 3 次。这是个轻量但有效的"自我纠错"循环。

3.4 长对话裁剪:文字全留,图片只留最近 k 张

computer-use 每步都塞一张截图,几十步后上下文会爆。flush_messages(worker.py:90)按模型类型分两种策略:

长上下文模型(anthropic / openai / gemini):
┌─────────────────────────────────────────────┐
│ 全部文字保留(推理链有价值,便宜) │
│ 图片:从后往前数,超过 max_trajectory_length │
│ 张的旧图片逐个删掉(图片最贵) │
└─────────────────────────────────────────────┘

非长上下文模型:
┌─────────────────────────────────────────────┐
│ 直接整轮丢弃最早的 user/assistant 对 │
└─────────────────────────────────────────────┘

真实实现见 worker.py:98-123max_trajectory_length 默认 8(agent_s.py:56),即长上下文模式下最多保留最近 8 张截图。

巧妙处: 它认识到"图片贵、文字便宜",所以对强模型只丢图不丢文字——保留完整推理历史,只牺牲看不到的旧画面。这是针对多模态 agent 上下文成本的精细化处理。

3.5 Thinking 模式:对部分 Claude 开"延展思考"

Worker 会按模型名判断是否启用"thinking"(延展推理):

# 真实源码,worker.py:50-56
self.use_thinking = worker_engine_params.get("model", "") in [
"claude-opus-4-20250514", "claude-sonnet-4-20250514",
"claude-3-7-sonnet-20250219", "claude-sonnet-4-5-20250929",
"claude-opus-4-5-20251101",
]

命中时,LLM 调用走 generate_with_thinking,在 Anthropic 引擎里开 thinking={"type": "enabled", "budget_tokens": 4096} 并保留思考 token(core/engine.py:128-147)。split_thinking_response(common_utils.py:130)负责把 <thoughts><answer> 切开,所以下游既能拿到结论也能拿到推理。

3.6 文本便签 notes:本任务内的临时记忆

S3 没有 S2 那种跨任务知识库,但保留了一个任务内的轻量便签。save_to_knowledge 动作把文本塞进 grounding_agent.notes(grounding.py:465),每步又把整个 notes 缓冲拼进生成器消息(worker.py:205-207):

generator_message += f"\nCurrent Text Buffer = [{','.join(self.grounding_agent.notes)}]\n"

典型用途:把一个对话框里读到的数字/路径"记下来",稍后粘到别处——避免跨步骤遗忘。注意它是"用完即弃"的任务内状态,不持久化、不跨任务。

代码地图(导航索引)

主题文件路径符号名
单步主循环gui_agents/s3/agents/worker.pyWorker.generate_next_action
反思生成gui_agents/s3/agents/worker.pyWorker._generate_reflection
反思提示词gui_agents/s3/memory/procedural_memory.pyREFLECTION_ON_TRAJECTORY
上下文裁剪gui_agents/s3/agents/worker.pyWorker.flush_messages
格式校验重试gui_agents/s3/utils/common_utils.pycall_llm_formatted
校验器定义gui_agents/s3/utils/formatters.pySINGLE_ACTION_FORMATTER / CODE_VALID_FORMATTER
Thinking 模式gui_agents/s3/core/engine.pyLMMEngineAnthropic.generate_with_thinking