01 · 顶层全景与主循环
这一章讲清两件事:(1)
DesktopEnv的三个核心方法reset / step / evaluate各做什么;(2) 一条动作命令怎么从你的 Python 代码,穿过「起 VM 的 provider」「发 HTTP 的 controller」「VM 里的 Flask 服务」三层,最终落到真机上。
1.1 DesktopEnv:一个伪装成 Gym 的真机
OSWorld 把「一台受控的真电脑」包成了一个标准 gym 环境(desktop_env/desktop_env.py:85,class DesktopEnv(gym.Env))。这样做的好处是:任何熟悉 RL 训练循环的人,都能直接用 reset → step → step → … 的老套路来驱动它。
三个方法的分工:
| 方法 | 职责 | 关键源码 |
|---|---|---|
reset(task_config) | 回滚到干净快照 → 按任 务 config 布置初始状态 → 返回首帧观测 | desktop_env.py:273 |
step(action) | 在真机执行一个动作 → 睡 pause 秒 → 返回新观测 | desktop_env.py:416 |
evaluate() | 跑 postconfig → 进真机取证 → 按 metric 打分 | desktop_env.py:458 |
注意一个和普通 gym 的关键差异:step 返回的 reward 恒为 0、done 恒为 False(除非 agent 主动发 DONE/FAIL)。真正的「得分」不在每一步,而在最后单独的 evaluate()。源码里 reward = 0 / done = False 旁边直接写着 todo(desktop_env.py:423-424)——OSWorld 是稀疏奖励、终局判分的基准,不是逐步给 reward 的 RL 环境。
1.2 reset:怎么把机器「洗回」初始状态
一次 reset 要保证:无论上一题把机器弄多脏,这一题都从一个确定、可复现的初始状态开始。它的逻辑(desktop_env.py:273 reset)分三步:
reset(task_config)
│
├─① 若环境「用过」→ _revert_to_snapshot():回滚 VM 快照,再 _start_emulator 重连
│ (is_environment_used 标志见 desktop_env.py:298)
│
├─② _set_task_info:把任务的 instruction / config / evaluator 装进 env
│
└─③ setup_controller.setup(self.config):按 config 逐条布置初始状态
成功 → break;失败 → sleep 5s 重试,最多 MAX_RETRIES=5 次
「用过才回滚」是一个刻意的性能优化。 不同 provider 的初始洁净度不一样:docker/aws/gcp/azure 每次都从干净镜像起,天生是「没用过」;vmware/virtualbox 从上次的脏盘起,天生是「用过」(desktop_env.py:155-160)。云上回滚快照又慢又贵,所以只有真正被 step/setup 弄脏过,才付回滚的代价(is_environment_used 在 step() 里被置真,见 desktop_env.py:421)。
布置初始状态由 SetupController.setup 完成,它是声明式的:config 是一串 {"type": ..., "parameters": ...},setup 按 type 拼出方法名 _{type}_setup 再反射调 用(desktop_env/controllers/setup.py:92)。这套「type→方法」的约定和具体有哪些 setup 动作,放在 03 章 细讲。
1.3 观测长什么样
每次 reset/step 结束都调 _get_obs()(desktop_env.py:337),返回一个字典:
| 字段 | 内容 | 何时有 |
|---|---|---|
screenshot | 当前屏幕 PNG 字节(带光标) | 总是有 |
accessibility_tree | 无障碍树 XML(AT-SPI/UIA 抓的界面结构) | require_a11y_tree=True 时 |
terminal | 终端输出 | require_terminal=True 时 |
instruction | 本任务的自然语言指令 | 总是有 |
这四样就是 agent 能「看到」的全部。截图给视觉模型看,无障碍树给纯文本模型或做视觉定位用——两者怎么被消费,是 02 章 的主题。
1.4 一条命令怎么落到真机:三层穿透
这是理解 OSWorld 的关键。你在 host 写 env.step("pyautogui.click(500, 300)"),这串字符要跑到虚拟机里真的点一下。中间穿过三层:
① DesktopEnv.step(action) desktop_env.py:416
│ 普通动作 → controller.execute_python_command(command)
▼
② PythonController.execute_python_command controllers/python.py:195
│ 给命令加「导库前缀」,POST http://<vm_ip>:5000/execute
▼ ─────────────────────── HTTP ───────────────────────▶
③ Flask 服务 execute_command() server/main.py:78
subprocess.run(["python","-c", ...]) ← 在真机里真的跑 pyautogui
第 ① 层——特殊动作 vs 普通动作。 step 先拦截三个「元动作」:WAIT(睡一会)、FAIL(判定不可行、结束)、DONE(判定完成、结束)(desktop_env.py:428