跳到主要内容

v1.0+ 元包与统一 CLI:薄壳 + 懒加载

本章讲仓库根的 openadapt/ 包(不是 legacy/)。v1.0+ 把老单体拆成一堆 openadapt-* PyPI 子包,这个包本身只剩一层很薄的调度壳

1. 它要解决的小问题

老单体啥都塞一起,装一次要拖进 PyTorch、OpenCV、pynput 一大堆重依赖——用户只想跑个 version 也得等半天、还可能在无头(headless)CI 里因为「import 时截屏」直接崩。v1.0+ 的答案:功能拆包 + 懒加载——基础包只依赖 click(见 pyproject.toml:26-28),重的东西按需装、按需 import。

2. 思路/直觉:壳只认命令,功能现用现导

openadapt(元包,只依赖 click)
├─ cli.py 统一命令入口(capture / train / eval / serve / doctor)
├─ __init__.py __getattr__ 懒加载:访问某符号时才 import 对应子包
└─ config.py pydantic 统一配置(环境变量 / .env)
│ 命令真正执行时才:

from openadapt_capture import Recorder ← 用到才 import
from openadapt_ml.scripts.train import ...
from openadapt_evals import ApiAgent, ...

怎么读这张图: 壳启动时几乎什么都不 import;只有当你真跑 capture start,它才在函数体里 from openadapt_capture import Recorder。装不装子包、装哪些,由 pip install openadapt[capture] 这类 extras 决定。

3. 真实实现

3.1 CLI:每个命令把 import 藏在函数体里

cli.pyclick 建命令组:main(cli.py:28)下挂 capture/train/eval 组和 serve/version/doctor 命令。关键点——import 全在命令函数内部。例如 capture_start(cli.py:63)第一行才 from openadapt_capture import Recorder(cli.py:70),并用 try/except ImportError 给出友好的「请 pip install」提示(cli.py:93-96)。

3.2 version / doctor 的巧思:只读元数据,不 import

version(cli.py:468)特意用 importlib.metadata.version 读发行版元数据而不是 import 包——注释点破原因:import openadapt-capture 会在导入时截屏,在无头 CI 里直接崩(cli.py:471-476)。doctor(cli.py:498)同理用 importlib.util.find_spec 检测「装没装」而不执行包代码(cli.py:518-527)。这是**「探测存在性 ≠ 执行代码」**的干净做法。

3.3 库级懒加载:__getattr__

作为库用时(import openadapt; openadapt.Recorder),__init__.py 的模块级 __getattr__(openadapt/__init__.py:17)按符号名分流到对应子包:Recorder/Capture 等 → openadapt_capture;ApiAgent 等 → openadapt_evals;可选的 ElementLocatoropenadapt_grounding(缺包时抛带安装提示的 ImportError,__init__.py:76-80)。PEP 562 的模块级 __getattr__ 让「按需 import」对库使用者也透明。

3.4 统一配置

config.pypydantic-settingsOpenAdaptSettings(config.py:25)集中管 API key、路径、模型/设备/评测参数,支持 OPENADAPT_* 环境变量和 .env(config.py:37-42)。get_device()(config.py:158)在 auto 时探测 CUDA/MPS/CPU。全局单例 settings(config.py:181)。

4. 关键细节/坑

  • CLI 里的 train/eval 只是「转发器」。 train_start(cli.py:186)自己不训练,而是根据有没有 CUDA 选默认 config(cli.py:206-211)后转调 openadapt_ml.scripts.train.main;eval_run(cli.py:310)按 --checkpoint/--agent 组装 openadapt_evals 的 agent 和 adapter 再跑。真正逻辑都在子包。
  • 两个 config 不要搞混。openadapt/config.py(pydantic,v1.0+)和 legacy/openadapt/config.py(老单体)是两套,前者服务新元包,后者服务老引擎。
  • capture stop 还没实现。 cli.py:99-104 只是提示「用 Ctrl+C」,带 # TODO。这是 v1.0+ 仍在演进的信号。

5. 边界与局限

  • 这个仓库根包几乎不含算法——它是调度层。要读原理,回到 legacy/(第 1-4 章)或去对应 openadapt-* 子包仓库(README 列了全部子包链接)。
  • 拆包架构的代价:功能散落在十几个仓库,单看本仓库拼不出完整运行时全貌。

6. 代码地图

主题文件路径符号
CLI 命令树openadapt/cli.pymaincapturetrainevalserve
录制命令(转发子包)openadapt/cli.pycapture_startcapture_list
只读元数据的探测openadapt/cli.pyversiondoctor
库级懒加载openadapt/__init__.py__getattr__
统一配置openadapt/config.pyOpenAdaptSettingsget_devicesettings
打包/extraspyproject.toml[project.optional-dependencies][project.scripts]