本文说明逆合成规划 Skill 的可选外部模型边界。OpenAI4S 一侧继续保持标准库优先;重型模型包、checkpoint、CUDA 库和模型专属依赖留在独立的 Python 或 conda 环境中,通过一次版本化 JSON 请求和一次 JSON 响应与 OpenAI4S 通信。
首个实现支持 RetroChimera 以及 Syntheseus 暴露的模型 wrapper 做单步逆合成推理。现在同一隔离边界还通过 reaction_model_backends.py 与 reaction_model_worker.py 支持 AiZynthFinder、RXNMapper、ReactionT5v2-forward、ReactionT5v2-yield 和 Parrot;它不会把任何模型分数解释成实验成功概率。
reaction_model_deployment.py 是这些环境和 artifact 身份的权威注册表:固定包版本及上游 revision,生成供人工审阅的安装/下载命令,对每个 artifact 文件制作快照,并在推理前验证。网络命令只输出,不会隐式执行。
| 能力 | 冻结身份 | 必需外部 artifact |
|---|---|---|
| AiZynthFinder | 4.4.1 / release commit 9859f5b… |
完整 download_public_data policy/template/filter/stock/config 快照 |
| RXNMapper | 0.4.3 / tag commit 640d9dd… |
已审阅 PyPI wheel 与内置模型,wheel SHA 已登记 |
| ReactionT5v2 forward | HF revision 9331140… |
完整本地 HF 快照,推理强制 local_files_only |
| ReactionT5v2 yield | HF revision f0658bf… |
完整本地 HF 快照,推理强制 local_files_only |
| Parrot | HF revision b9ef604…;legacy source 0fb2325… |
MIT USPTO_condition.mar 与 metadata,按精确大小和 SHA256 准入 |
AiZynthFinder public-data artifact 仍为 review-required。Parrot 原 Google Drive artifact 也仍被阻塞;只有上表第一作者另行发布的 Hugging Face 固定 revision 获得明确 MIT 准入。代码许可证不能自动覆盖其他数据集或 checkpoint。
| 后端 | 工程状态 | 科学使用状态 |
|---|---|---|
| AiZynthFinder 4.4.1 | 已实现直接 plan_routes worker 和 Scenario 2 转换,并通过协议测试;隔离环境位于 git 仓库外。 |
实际搜索仍需经过批准并做完整哈希的 policy/template/filter/stock 快照;上游称其为 public data,但下载器没有声明统一的 artifact 许可证。 |
| RXNMapper 0.4.3 | 固定隔离环境、wheel 哈希、manifest 和真实 mapping smoke test 均通过。 | 在常规域检查前提下可用于 mapping benchmark。 |
| ReactionT5v2-forward | 固定 HF 快照 9331140...,真实 CPU model-card 产物 canary 通过。 |
可作为有边界的正向/round-trip 信号,不能作为可行性证明。 |
| ReactionT5v2-yield | 固定 HF 快照可加载,并复现上游预处理;公开 canary 期望约 19.1666,实测 65.924858。 | 已隔离:问题解决并独立验证前,只允许协议测试。 |
| Parrot | 精确 MIT HF 快照、可迁移 Python 3.8 环境、MAR adapter 和真实 GPU worker canary 均通过;返回 15 个联合 beam。 | 可用于 USPTO 类别条件假设;不支持温度,冻结 benchmark 精度尚未测量。 |
外部后端用于以下有明确边界的场景:
多步 Syntheseus 搜索、模型共识排序和交互式子树重规划仍是独立功能,不会隐藏在单个 adapter 中。
OpenAI4S retrosynthesis Skill
|
| stdin 上的一次版本化 JSON 请求
v
隔离的 syntheseus_worker.py 或 reaction_model_worker.py
|
| 可选依赖导入与模型推理
v
经审阅的模型专属环境及本地 artifact
|
| stdout 上的一次版本化 JSON 响应
v
schema 校验、provenance 检查与 Harness replay
stdout 只允许输出一个 JSON 对象。worker 在处理请求前会把文件描述符 1 重定向到 stderr,并保留一个私有副本用于写响应,因此直接写 stdout 的原生库(PyTorch、DGL、CUDA、RDKit 都会这样做)不会破坏协议。仅重绑 sys.stdout 是不够的,因为这些写入根本不经过它。该副本会在任何 fork 出的子进程中关闭,因此不 exec 直接 fork 的模型不会在自身退出后仍占住 Host 的管道。
这里有三条限制,应当明说而非暗示。当没有可用的 stderr 供 stdout 迁移时,重定向会放弃,worker 随后在未受保护的 stdout 上作答——保护程度不比从前更好,但至少是可见的。重定向发生在 worker 内部,因此无法覆盖解释器到达该处之前写出的字节:继承的 PYTHONPATH 上某个 sitecustomize 打印的启动横幅仍会破坏响应,openai4s/kernel/worker.py 也有同样的限制。另外,由于模型 stdout 现在落到 stderr,而 Host 会把 stderr 引用进 nonzero_exit 消息,该消息在抛出前会先做路径清洗。
Host 不使用 shell=True,限制请求和响应大小,设置超时,核对响应中的 request_id,并拒绝未知响应字段。
| 类别 | Worker 接受的模型名 | 主要用途 | 依赖说明 |
|---|---|---|---|
| RetroChimera ensemble | RetroChimera |
推荐作为第一个外部 second-opinion 模型 | 安装独立的 retrochimera 包及 Syntheseus 接口依赖。 |
| RetroChimera 组件 | RetroChimeraEdit, RetroChimeraDeNovo |
判断图编辑和序列生成组件是否一致 | 应使用同一 checkpoint family,并在 manifest 中准确记录组件名。 |
| 模板与图模型 | GLN, Graph2Edits, LocalRetro, MEGAN, MHNreact |
引入结构不同的候选生成机制 | 每个 wrapper 可能需要对应的 Syntheseus 可选依赖组。 |
| 序列与检索模型 | Chemformer, RootAligned, RetroKNN |
引入序列对齐或检索式候选 | 只安装实际使用的依赖组和 checkpoint。 |
Adapter 将 num_results 明确限制在 10 以内。低排名预测不会被展示成同等可靠的候选;下游必须保留原始 rank 和 score type,不能把所有模型分数静默转换成同一种概率。
这里的“隔离”指的是依赖边界,而非安全边界。该 worker 只是一个普通子进程:它继承调用方的环境,不在任何 OS sandbox 下运行,自身也没有出网管控。它把 PyTorch、CUDA 和模型专属依赖挡在 OpenAI4S core 进程之外,但并不约束模型代码本身。应当把 checkpoint 及其 wrapper 视为你主动选择运行的代码。
默认禁止自动下载 checkpoint。除非显式设置 allow_model_download=True,否则在没有 model_dir 的情况下调用 single_step(...) 会在启动外部进程前直接失败。
更稳妥的生产流程是:
本地 model_dir 只发送给隔离 worker,不会复制进规范化结果、dashboard、Harness tape 或 model manifest,从而避免把工作站路径泄漏到公开 Artifact。
模型返回的 metadata 在离开 worker 前会经过同样的过滤:名为 *path* 或 *directory* 的 key 会被丢弃;剩余的字符串(无论是值还是 key),只要以绝对路径、家目录相对路径、UNC 共享或 file:// URL 开头,就替换为 <redacted-path>。错误消息的清洗更激进,会替换字符串中任意位置的路径——因为 checkpoint 缺失时抛出的异常文本会带上调用方的 model_dir。
有两条边界应当明说而非暗示。metadata 的值只在字符串开头匹配,因此 wrapper 自由文本注释里夹在句中的路径不会被遮蔽:不加锚定的匹配无法把 kcal/mol 或 F/C=C/F 中的键方向斜杠与目录区分开,为了抓一次散文提及而破坏化学数据是更差的取舍。另外,清洗发生在 worker 内部,因此对 worker 启动之前写出的字节无能为力。
应创建独立环境,不要把模型包加入 OpenAI4S core 环境。开发该 adapter 时使用的参考版本可以这样安装:
conda create -n openai4s-retro python=3.11 -y
conda activate openai4s-retro
pip install syntheseus==0.7.2 retrochimera==1.2.0
USPTO-50K checkpoint 使用可选的 Graphium 架构。加载该变体前,应以
retrochimera[graphium]==1.2.0 取代普通包;Pistachio 与 USPTO-FULL 路径
不需要这个 extra。
其他 Syntheseus wrapper 有各自的模型依赖。应根据选定模型遵循上游安装说明,而不是默认安装所有模型家族。
Adapter 不会把 syntheseus、retrochimera、PyTorch 或 CUDA 加进 pyproject.toml。Worker 会报告运行时安装的包版本;缺少或不兼容的依赖会返回结构化 backend error。
model_deployment.py 登记公开的 Pistachio、USPTO-FULL 和 USPTO-50K RetroChimera 归档,以及上游字节数、MD5、DOI 记录和 MIT 许可证。列出注册表不需要联网:
python -m skills.retrosynthesis_planning.model_deployment list
除非调用方显式授权,否则禁止下载;下载通过 OpenAI4S host.web_download 执行,因此每次重定向都会经过出网允许名单和 SSRF 防护。请在 OpenAI4S Python cell 中运行,并把目标放在 session workspace 内:
from pathlib import Path
from retrosynthesis_planning.model_deployment import (
checkpoint_spec,
download_checkpoint,
)
workspace = Path.cwd().resolve()
archive = workspace / "models" / "retrochimera" / "retrochimera_uspto50k.zip"
spec = checkpoint_spec("uspto50k")
download_checkpoint(
spec,
archive,
allow_network=True,
web_download=host.web_download,
)
host 是 cell 中已经注入的 singleton,并不是可导入的模块。显式传入该
capability 也让 helper 易于测试,并阻止独立脚本悄悄自行联网。
host.web_download 会在强制执行字节上限并计算 SHA-256 的同时,把响应流式写入原子临时文件;它不会在 daemon 内存中聚合数 GB 的 checkpoint。操作者也可以改用部署环境批准的流式下载器,然后在解压前运行下面的离线 verify 命令。独立模块本身不会直接联网。
较小的 USPTO-50K 归档适合安装冒烟测试,但不能替代覆盖更广的主 checkpoint。上游把 Pistachio 描述为发布的主力且最强 checkpoint。只有通过校验后才安装归档:
CHECKPOINT_ROOT="$PWD/models/retrochimera"
python -m skills.retrosynthesis_planning.model_deployment verify \
uspto50k "$CHECKPOINT_ROOT/retrochimera_uspto50k.zip"
python -m skills.retrosynthesis_planning.model_deployment extract \
uspto50k \
"$CHECKPOINT_ROOT/retrochimera_uspto50k.zip" \
"$CHECKPOINT_ROOT/uspto50k" \
--manifest "$CHECKPOINT_ROOT/uspto50k/model-manifest.json"
请从下载 Cell 使用的同一个 session workspace 根目录运行该命令块。因此,
$PWD/models/retrochimera 是下载、校验、解压、manifest 创建和推理共用的唯一
可写 checkpoint 根目录。
该命令最多只把经审阅大小的归档复制到私有快照,同时校验字节数和 MD5 并计算 SHA-256,随后只解压这份已验证快照。它拒绝非普通文件及超大源、ZIP 中的绝对路径、路径穿越、反斜杠、Windows 盘符相对路径/备用数据流/设备名和符号链接,并限制 member 数量及展开大小。请求的 manifest 必须位于新模型目录内;它会在私有 staging 中写好,因此 manifest 与解压文件通过一次原子目录发布同时可见。命令会拒绝在解压开始时已经存在的模型目录。调用方必须串行化针对同一目标的解压:初始存在性检查与最终 POSIX 目录 rename 并不是跨进程锁,否则竞态创建的空目录仍可能被替换。生成的 manifest 不含路径,可以直接传给 SyntheseusBackend。
当 workspace 文件系统支持硬链接时,下载与独立 manifest 写入会把发布对象绑定到 已验证 inode。对于 exFAT 或部分 SMB 挂载等拒绝硬链接的文件系统,它们仍使用私有 staging、发布前后字节校验和原子 rename,但调用方也必须串行化针对同一目标的写入。
Model manifest 是公开 provenance,不是环境配置文件。它不能包含本地 checkpoint 路径、凭据、私有数据集位置或内部实验名称。
{
"schema_version": 1,
"provider": "Microsoft Research",
"model": "RetroChimera",
"model_version": "1.2.0",
"checkpoint_id": "reviewed-uspto50k-checkpoint",
"checkpoint_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"training_dataset": "USPTO-50K",
"code_license": "MIT",
"checkpoint_license": "MIT",
"source_url": "https://doi.org/10.6084/m9.figshare.30601718.v1",
"metadata": {
"reviewed_by": "replace-with-public-review-role"
}
}
只有在 checkpoint SHA-256 存在、训练数据集已明确、代码与 checkpoint 许可证不是 unknown、unspecified 或 review-required,且 digest 没有被明确限定为仅覆盖源归档时,provenance_status 才会是 complete。部署 helper 会记录 checkpoint_sha256_scope: source_archive 和 runtime_integrity: unverified:这个 digest 证明安装的是哪份经审阅 ZIP,并不能证明执行推理时可变的解压目录仍是相同字节,因此其状态保持 incomplete。仅在 manifest 中写入 runtime_integrity: verified 不能升级该状态;这需要真正的 host 侧目录验证器。系统会基于 canonical JSON 计算 manifest fingerprint,因此即使人类可读 checkpoint ID 不变,manifest 的修改仍然可见。worker 会原样回显 manifest——清洗只作用于模型返回的 metadata,绝不作用于操作者自己的文档,因为一旦过滤,公布的 fingerprint 就无法从被审阅的文件复算出来。SyntheseusBackend 会把回传的 fingerprint 与自己发出的 manifest 比对,不一致时抛出 manifest_mismatch,因此 worker 无法悄悄替换一份没有人批准的 provenance 记录。
from pathlib import Path
from retrosynthesis_planning.external_backends import SyntheseusBackend
workspace = Path.cwd().resolve()
model_dir = workspace / "models" / "retrochimera" / "uspto50k"
manifest = model_dir / "model-manifest.json"
cache_dir = workspace / "models" / "syntheseus-cache"
cache_dir.mkdir(parents=True, exist_ok=True)
backend = SyntheseusBackend(
model="RetroChimera",
model_dir=model_dir,
manifest=manifest,
python_command=(
"conda",
"run",
"--no-capture-output",
"-n",
"openai4s-retro",
"python",
),
timeout_seconds=600,
env={
"WANDB_MODE": "offline",
"SYNTHESEUS_CACHE_DIR": str(cache_dir),
},
)
env 只把列出的值加入继承的 worker 环境。它用于模型 cache 和离线模式控制,不用于凭据;秘密仍应进入正常的 credential broker。
--no-capture-output是必需的,不是可选项:缺少它时 conda run 不会转发 stdin,
worker 读到空请求,于是每次调用都会返回 invalid_json 错误响应而不是结果。
capabilities = backend.capabilities()
result = backend.single_step(
"CC(=O)Oc1ccccc1C(=O)O",
num_results=5,
)
结果会保留:
Wire schema 与具体模型包独立版本化。当前 worker 支持 capabilities 和 single_step 两种操作。
成功的单步响应包含 target_smiles、model、有序 predictions、model_manifest、runtime、warnings 和 elapsed_seconds。失败请求包含结构化 error,其中有 code、message 和 retryable。
预期错误码包括:
checkpoint_required;dependency_missing;dependency_incompatible;unsupported_model 或 unsupported_operation;inference_failed;timeout、nonzero_exit、invalid_json 和 response_too_large。结构化模型错误属于合法 backend response,可以在 ensemble 中作为一个失败 provider 处理。进程崩溃、stdout 非法或 request ID 不匹配属于协议失败,会在 Host 侧抛出异常。
默认 PR suite 不下载模型权重。harness/evals/retrosynthesis_backend_cases.json 保存公开安全的合成响应 tape,harness/evals/retrosynthesis_backends.py 会把它们送入真实 worker 结果使用的同一个生产 response normalizer。
Replay 报告包含:
运行定向契约:
uv run pytest tests/test_harness_contract.py
uv run python -m harness.cli run --tier pr --offline
未来可以增加显式 opt-in 的 model canary 来加载少量经过审核的 checkpoint,但它必须标记 external/GPU,不能成为默认离线 PR suite 的要求。
RetroChimera 和其他学习式逆合成模型可能产生化学上不合理或分布外的候选。多个模型一致只能说明计算结果具有一定一致性,不能证明反应可行。不同模型家族的高 raw score 也不能自动视为经过统一校准。
在候选升级成可执行路线之前,应结合确定性结构检查、reaction-center 审阅、可用时的 forward 或 round-trip 校验、来源可追溯的反应先例、库存核验、安全审查和独立化学专家决策。
因此 adapter 返回的是候选与 provenance。它不会生成虚构产率,不会隐藏模型分歧,也不会把预测标记成实验验证结果。
后续兼容层包括:
这些改动应继续拆成独立 PR,让外部进程边界与 provenance contract 先接受审阅,再允许模型输出影响路线排序或 workbench UI。