数据截至 (上游 commit 2689884a6257)
OpenAdapt — 架构与原理
30 秒导读: OpenAdapt 想解决的是「有些活只能在别人的图形界面里干,没有 API 可调」。它的做法是:让人演示一遍,把这次演示变成一段能重复跑的程序。本仓库(
OpenAdaptAI/OpenAdapt)在这个 commit 上已经不再包含那个编译器/运行时——它是安装入口和统一命令行;真正的引擎在openadapt-flow;而 2023–2025 那套完整的「录屏 + 录输入 → 大模型改写 → 注入回放」单体代码被原样冻结在legacy/。
1. 这是什么(零基础也能懂)
一句话定义
OpenAdapt 是一个 GUI 流程自动化框架:你用鼠标键盘把一件事做一遍,它录下来,之后它能自己再做一遍。
解决谁的什么问题
设想你在一家诊所做行政:每天要在一个十年前的桌面病历系统里,把二十个病人的随访记录挨个敲进去。这个系统没有 API,没有导入按钮,厂商也不会为你开一个。
能选的路只有三条:
| 路子 | 做法 | 卡在哪 |
|---|---|---|
| 传统 RPA | 人工写脚本,写死坐标或控件 ID | 界面一改就全崩,脚本自己不知道自己错了 |
| 通用 computer-use agent | 每一步都让大模型看截图、决定点哪 | 慢、贵、不确定,同一个输入两次结果可能不同 |
| OpenAdapt | 演示一次 → 编译成程序 → 之后按程序跑 | 需要先「演示」,且只在被演示过的流程上可靠 |
它能做什么(功能清单)
- 录:同时录鼠标键盘动作、屏幕截图/视频、当前活动窗口的无障碍树(accessibility tree,操作系统暴露给读屏软件的那棵控件树),浏览器里还能额外录 DOM 事件。
- 理:把原始事件流降噪——两次快速点击合成一个
doubleclick,几百个鼠标移动事件压成几个。 - 放:按策略重新执行。策略从「原样重放坐标」到「让多模态模型按自然语言指令改写这次流程」都有。
- 擦:回放/分享前把截图和文本里的个人信息(PII/PHI)打码。
- 治:仓库自己的一套发布纪律——防止把客户专属内容泄漏进开源仓库、防止版本清单和真实发布漂移。
用起来什么样
今天的入口(引擎在另一个包里,本克隆只有转发壳):
pip install 'openadapt[browser]'
openadapt quickstart # 跑一遍内置合成教程:录 → 编译 → 回放 → 验证
冻结的 legacy 单体用起来是这样(见 legacy/openadapt/record.py:1-7、legacy/openadapt/strategies/visual.py:42-45):
python -m openadapt.record "把这三个病人的随访记录填进去"
python -m openadapt.replay VisualReplayStrategy --instructions "改成填另外两个病人"
注意第二条命令的形状:录一遍 + 一句自然语言修改意图 = 一次新的执行。这是 OpenAdapt 最初的核心主张。
一句话直觉
把它想成会看屏幕的宏录制器。
- 老式宏录的是「在 (832, 419) 点一下」——窗口一挪就点空。
- OpenAdapt 录的是「在那个叫『保存』的按钮上点一下」——下次它重新在屏幕上找这个按钮。
再加一层今天产品才强调的东西:点击成功不等于事情办成。所以运行时会用另一条独立通道(比如一个只读 API)去确认那条记录真的写进去了,确认不了就停下来,而不是接着往下点。
2. 顶层全景(它大概怎么转)
先看清楚:这个仓库现在装的是什么
这是读这份文档最容易踩空的一步。仓库根目录下的 openadapt/ 只有 3 个 Python 文件、1327 行,里面没有任何录制或回放实现。
pip install openadapt
│
▼
┌───────────────────────────────┐
│ ① 启动器(本仓库 openadapt/) │ openadapt/cli.py
│ 统一命令入口 │ openadapt/__init__.py
│ 未包装的命令原样转发 │
└───────────────┬───────────────┘
│ 进程内调用 flow_main(argv)
▼
┌───────────────────────────────┐
│ ② openadapt-flow(PyPI 依赖, │ ← 编译器 + 受管运行时
│ 不在本克隆里) │ 录制/编译/回放/验证都在这
└───────────────────────────────┘
同一个仓库里另外两块,和上面这条链是并列的:
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ ③ legacy/(冻结的单体 v0.46.0) │ │ ④ scripts/ + .github/ │
│ 录制/回放/脱敏的全部实现 │ │ 发布治理与开源边界守卫 │
└─────────────────────────────────┘ └─────────────────────────────────┘
部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
| 统一 CLI | 把 openadapt <verb> 转成引擎的 argv 再调用 | openadapt/cli.py |
| 懒加载导出 | from openadapt import Recorder 时才去 import 重依赖 | openadapt/__init__.py:21-110(__getattr__) |
| 版本单一来源 | 从已安装分发元数据读版本,不硬编码 | openadapt/version.py:13-19 |
| legacy 录制 | 多线程采集 + 多进程写库 | legacy/openadapt/record.py:1269(record) |
| legacy 事件处理 | 归并降噪成干净动作序列 | legacy/openadapt/events.py:878(merge_events) |
| legacy 回放 | 抽象策略基类 + 六种具体策略 | legacy/openadapt/strategies/base.py:17(BaseReplayStrategy) |
| legacy 数据模型 | SQLAlchemy 表:录制/动作/截图/窗口/浏览器事件 | legacy/openadapt/models.py:46,129,629,755,924 |
| 边界守卫 | 客户专属内容进公开仓库就让 CI 红 | scripts/check_source_boundary.py:243(scan) |
| 平台清单 | 从 PyPI 真实数据生成版本/哈希清单 | scripts/generate_platform_manifest.py:963(generate) |
| 发布健康 | 探测「已合并但没发版」「有标签没发布」 | scripts/check_release_health.py:265(evaluate) |
主线 A:今天跑一条命令会发生什么
一句话:壳不做事,壳只负责把参数原样递给引擎,并保住引擎的退出码。
openadapt flow replay bundle --backend rdp --rdp-host 10.0.0.7
│
│ click 解析到 `flow` 组;`replay` 不是本仓库显式包装的子命令
▼
_FlowPassthroughGroup.get_command ← 现造一个「吞掉所有未知选项」的命令
│
▼
_run_flow(["replay", "bundle", "--backend", "rdp", ...])
│
▼
openadapt_flow.__main__.main(argv) ← 引擎(不在本克隆)
│
▼
sys.exit(引擎退出码)
为什么要这么绕,以及这个设计避开了什么坑,见 01-launcher-and-cli.md。
主线 B:legacy 里一次「录制 → 回放」的完整旅程
这条线才是 computer-use 的正题。怎么读这张图:从上往下是时间顺序,上半段是录制期,下半段是回放期。
【录制期】人操作 GUI
│
├─ pynput 键鼠钩子 ──┐
├─ 循环截屏 ─────────┼──→ event_q(单一有序队列)
└─ 轮询活动窗口+a11y ┘ │
▼
process_events:给每个「动作」贴上
「它发生时最近的那张截图 + 那个窗口」
│
▼
每种事件一个独立写进程 ──→ SQLite
【回放期】
events.get_events:归并降噪 ──→ 干净的动作序列
│
▼
策略:把「坐标」换成「元素描述」,在**当前**屏幕上重新定位
│
▼
playback.play_action_event ──→ pynput 注入鼠标/键盘
3. 诚实边界(先说清楚,免得白读)
这份文档只写这个克隆里真实存在的代码。以下内容在本 commit 的克隆里看不到实现:
openadapt-flow的编译器、确定性回放、效果验证、失败即停、受管修复——README 和 CLI 帮助文本描述了它们的契约,但代码在另一个仓库。凡是本文档引用 README 的地方,都会标明「这是声明,不是本克隆里的实现」。openadapt-capture/openadapt-ml/openadapt-evals等兄弟包同理:本仓库只有对它们的导入和 CLI 包装。- 桌面应用
openadapt-desktop不在此仓库。
所以:想学 GUI 自动化的真实工程细节,读 legacy/;想学一个开源项目怎么管发布边界,读 scripts/;想学一个薄壳 CLI 怎么不给自己挖坑,读 openadapt/cli.py。
4. 阅读地图
| 你想弄明白 | 读这一章 |
|---|---|
| 这个包为什么这么薄,壳怎么写才不拖累引擎 | 01-launcher-and-cli.md |
| 怎么在桌面上同时录下动作、画面和控件树,还不互相拖慢 | 02-legacy-recording.md |
| 原始事件流噪声怎么消,双击怎么从两次按下里认出来 | 03-legacy-event-processing.md |
| 界面挪了位置,怎么还能点对地方 | 04-legacy-replay-strategies.md |
| 截图和输入里的隐私怎么擦 | 05-privacy-and-scrubbing.md |
| 一个多仓库开源项目怎么防止版本谎报和内容泄漏 | 06-release-governance.md |
| 有什么值得抄走的、它在哪会崩、和别的 GUI agent 比如何 | 07-takeaways-and-boundaries.md |
建议顺序:
- 完全没接触过 GUI agent 的人:02 → 03 → 04 → 07。03 不能跳——04 章讲回放时会直接用到 03 章建立的父子事件树,05 章的脱敏编排也要用到它。
- 做工程/发布的人:01 → 06。这两章讲的是薄壳设计和 CI 守卫,和 GUI 自动化本身无关。
5. 代码地图(全局入口)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| CLI 根命令组 | openadapt/cli.py | main、_FlowFirstGroup |
| 引擎转发 | openadapt/cli.py | _FlowPassthroughGroup、_run_flow、_invoke_flow |
| 可选包懒加载 | openadapt/__init__.py | __getattr__ |
| legacy 录制主函数 | legacy/openadapt/record.py | record、process_events |
| legacy 事件降噪 | legacy/openadapt/events.py | merge_events、get_events |
| legacy 回放基类 | legacy/openadapt/strategies/base.py | BaseReplayStrategy |
| legacy 视觉定位策略 | legacy/openadapt/strategies/visual.py | VisualReplayStrategy、get_window_segmentation |
| legacy DOM 分割策略 | legacy/openadapt/strategies/visual_browser.py | VisualBrowserReplayStrategy |
| legacy 数据模型 | legacy/openadapt/models.py | Recording、ActionEvent、Screenshot、WindowEvent |
| 开源边界守卫 | scripts/check_source_boundary.py | SourcePolicy、scan |
| 发布健康探测 | scripts/check_release_health.py | evaluate、bump_level |