@delta-infra/cli 1.0.70 → 1.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -166,7 +166,7 @@ delta-cli auth status
166
166
  ```bash
167
167
  # 创建带 GPU 的 sandbox(--max-life 默认 30 分钟,长任务请调高)
168
168
  delta-cli sandbox create \
169
- --image deltarouter/pytorch-cuda13:latest \
169
+ --image image.yangtzeailab.com/opensandbox/pytorch-cuda13:latest \
170
170
  --cpu 4 --memory 16Gi --gpu 1 \
171
171
  --max-life 120
172
172
 
@@ -283,11 +283,15 @@ npx skills add delta-infra/delta-infra-cli -s delta-shared
283
283
 
284
284
  # 安装 sandbox 操作技能
285
285
  npx skills add delta-infra/delta-infra-cli -s delta-sandbox
286
+
287
+ # 安装 science 工具操作技能
288
+ npx skills add delta-infra/delta-infra-cli -s delta-science
286
289
  ```
287
290
 
288
291
  Skill 文件位于 `skills/` 目录:
289
292
  - `delta-shared/` — 全局通用规则(认证、配置、错误处理)
290
293
  - `delta-sandbox/` — Sandbox 操作指南(生命周期、命令路由)
294
+ - `delta-science/` — Science 工具调用指南(secondary API、invoke、endpoints)
291
295
 
292
296
  ## 开发
293
297
 
@@ -375,7 +379,7 @@ make release
375
379
  ```bash
376
380
  export GH_TOKEN=<your-github-pat>
377
381
  export NPM_TOKEN=<your-npm-token>
378
- ./release.sh v1.0.70
382
+ ./release.sh v1.0.72
379
383
  ```
380
384
 
381
385
  脚本执行步骤:
package/checksums.txt CHANGED
@@ -1,5 +1,5 @@
1
- af5ff4a7832f6d7b84d58c128e1ec4275b4f9c0af9ae4d95e83f78f69da2e6ad delta-cli-darwin-amd64.tar.gz
2
- 23ff7deb342c39dce91af911e58ab2e6a3232b0c75d4ba6f77471b0513b25863 delta-cli-darwin-arm64.tar.gz
3
- 1e4545d4bbd69b190aa83c65a59e8009e37f2c68d715613dc8185a51bdc495ed delta-cli-linux-amd64.tar.gz
4
- 846ece621a8030606004dfc398dabc0e1889fb4c34c18e7094ced9ba159f4fc4 delta-cli-linux-arm64.tar.gz
5
- 4cd39c137560399d967e2fad4c175a5902b79767f6f6cb05c66a06c7a82f4b66 delta-cli-windows-amd64.zip
1
+ 16b7386c1f5cd9ba8175267279b78bec0b5ee5137bfb5ae953bc3022c6e9af5c delta-cli-darwin-amd64.tar.gz
2
+ caf1525913944a2f9daa416a19e0097135dc5d74284398742620d3b0c3364aa3 delta-cli-darwin-arm64.tar.gz
3
+ 60e46fe37fcbde336d701ff675a9d5e453915bc76739b508f509193ea270ef82 delta-cli-linux-amd64.tar.gz
4
+ 52b9bd29c0c30f4f1c5a9a46cac36d014841834e5a3b8c7a2115153a5d18b600 delta-cli-linux-arm64.tar.gz
5
+ c94f3955966b27e9f4d242ff78869eb5f3a51e209d9358f843fb6f5236faa7a4 delta-cli-windows-amd64.zip
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delta-infra/cli",
3
- "version": "1.0.70",
3
+ "version": "1.0.72",
4
4
  "description": "Delta Infra CLI for sandbox compute and science tools.",
5
5
  "bin": {
6
6
  "delta-cli": "scripts/run.js"
@@ -15,6 +15,7 @@
15
15
  "scripts/run.js",
16
16
  "skills/delta-sandbox/SKILL.md",
17
17
  "skills/delta-sandbox/references/",
18
+ "skills/delta-science/",
18
19
  "skills/delta-shared/SKILL.md",
19
20
  "skills/delta-shared/references/"
20
21
  ],
@@ -41,7 +41,7 @@ const PLATFORM_LABELS = {
41
41
  },
42
42
  };
43
43
 
44
- const SKILL_NAMES = ["delta-sandbox", "delta-shared"];
44
+ const SKILL_NAMES = ["delta-sandbox", "delta-shared", "delta-science"];
45
45
  const LANG = "zh";
46
46
 
47
47
  function platformSkillDir(platform) {
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: delta-science
3
+ description: "用于所有涉及化合物、材料、衍射、分子动力学、贝叶斯优化、反应条件或抗体优化的 Science 任务,包括简短、模糊的人类请求。所有在线操作必须通过 Delta CLI。对于多个已命名分子的性质与结构比较,规划三个 Memento 步骤:PubChem 用一次批量调用写入 @ROOT/compounds_basic.json;RDKit 用一次 similarity-matrix 调用写入 @ROOT/science_comparison_result.json;主代理随后直接用 read_file 读取并回答。RDKit 返回产物后立即完成该步骤。主代理不得再次运行 python_repl、探测路径、查找可执行文件、重复调用 Science、分派 filesystem Skill,或补充交接结果中没有的阈值结论、结构原因和来源链接。"
4
+ ---
5
+
6
+ # Delta Science
7
+
8
+ 把普通的人类科学任务转换为一个或多个经过验证的 Delta CLI 调用。用户只需描述科学目标;本 Skill 负责选择工具和 operation、构造参数、执行、校验并简洁报告结果。
9
+
10
+ ## 强制调用链
11
+
12
+ - 每个在线 Science 操作都必须通过 `delta-cli science invoke` 执行。
13
+ - 禁止使用 `httpx`、`requests`、`curl`、PowerShell Web 命令或浏览器直接访问公司网关、PubChem 或业务服务。
14
+ - CLI 调用失败时,禁止改用本地 RDKit、pymatgen 或直接 HTTP;如实报告 CLI 或服务端错误。
15
+ - 每次调用都使用 `scripts/invoke.py`。它负责解析 CLI 路径、安全传递 JSON、校验响应信封并记录调用链证据。
16
+ - 不要预先检查 wrapper 源码。在 Memento 中,必须在一个 `python_repl` 中用 `subprocess.run(..., shell=False)` 和 argv 列表执行。
17
+ - 用户请求中不需要出现 CLI 语法、operation、URL 或 JSON 字段;根据本 Skill 和 references 自动推断。
18
+
19
+ 固定执行形式如下:
20
+
21
+ ```python
22
+ import json, os, subprocess, sys
23
+ wrapper = os.path.join(os.environ["SKILLS_ROOT"], "delta-science", "scripts", "invoke.py")
24
+ payload = {"identifier": "aspirin", "namespace": "name"}
25
+ p = subprocess.run(
26
+ [sys.executable, wrapper, "--tool", "pubchem", "--endpoint", "compound-summary",
27
+ "--data-json", json.dumps(payload)],
28
+ capture_output=True, text=True,
29
+ )
30
+ result = json.loads(p.stdout)
31
+ ```
32
+
33
+ 每次调用只替换 tool、endpoint 和 payload。只有 references 明确说明是查询参数时才使用 `--params-json`,否则使用 `--data-json`。必须满足 `result["ok"] is True` 且 `result["transport"] == "delta-cli"`,之后只使用 `result["native"]`。
34
+
35
+ 第一次在线调用前读取 [references/cli-contract.md](references/cli-contract.md)。构造参数前,只读取 [references/services.md](references/services.md) 中被选服务的部分。多服务任务还要读取 [references/workflows.md](references/workflows.md)。
36
+
37
+ ## 执行流程
38
+
39
+ 1. 在内部重述科学目标;不要向用户追问本 Skill 已经定义的实现细节。
40
+ 2. 选择能完成任务的最窄服务,并严格保持用户请求范围。推断 operation 或参数不代表可以增加用户没有要求的图片、文件、健康检查、描述符探测、分析或交付物。
41
+ 3. 如果 Memento 仅为跨步骤传递已验证数据而要求文件,可以在完成在线调用的同一个 `python_repl` 中写一个有界的内部 JSON。不得为生成或检查交接文件而重复业务调用,也不要把内部交接文件描述成用户要求的产物。
42
+ 4. 已知标识符直接使用。普通分子名称先由 PubChem 解析,再把返回的 SMILES 交给 RDKit。
43
+ 5. 构造最小合法参数。对于模糊探索任务使用有界默认值,并在最终答案中说明会显著影响科学结论的假设。
44
+ 6. 通过 wrapper 调用,并保留 `transport` 与 native 结果。只有进程退出码和所有响应信封均成功时,才算调用成功。
45
+ 7. 单个已命名化合物的基础查询只调用一次 PubChem `compound-summary`;成功后直接完成,不得重放。
46
+ 8. 多个已命名化合物的性质与结构相似度比较,默认只做两个 Science 调用:
47
+ - PubChem `compound-batch-summary`
48
+ - RDKit `similarity-matrix`
49
+
50
+ 不得额外调用 health、parse、descriptors、fingerprint、substructure 或 render,除非用户明确需要这些不同输出。
51
+
52
+ PubChem 默认请求体必须严格为:
53
+
54
+ ```json
55
+ {"identifiers":["aspirin","caffeine"],"namespace":"name"}
56
+ ```
57
+
58
+ 不要添加 `properties`、`synonym_limit` 或 `CID`。默认响应已经包含基础字段,不支持的属性会导致响应校验失败。记录位于 `native["results"]`;用每条记录的 `input` 保留原始标签,并从嵌套的 `properties` 读取属性。
59
+
60
+ 两个服务应在各自规定的单个 `python_repl` 中完成。不得打印原始 stdout、stderr、完整信封或完整 native 对象。RDKit 请求使用:
61
+
62
+ ```json
63
+ {
64
+ "molecules": [{"id":"aspirin","smiles":"..."}],
65
+ "fingerprint": {"type":"morgan","radius":2,"n_bits":2048},
66
+ "sanitize": true
67
+ }
68
+ ```
69
+
70
+ 9. 下游调用只能使用上一步已验证的 native 输出。禁止伪造 CIF、SMILES、粉末衍射数据、历史实验或优化观测。
71
+ 10. 跨服务比较时保留数据来源。例如 PubChem `XLogP` 与 RDKit `MolLogP` 使用不同模型,不得相互覆盖。
72
+ 11. 大型或二进制产物只能从文档明确的 native 字段解码。PNG 必须验证文件头为 PNG magic bytes,不能把以 `{` 开头的 JSON 保存成图片。
73
+ 12. 先报告科学结果,再列出使用的服务与 CLI 调用链证据。失败和被阻塞的后续步骤必须明确说明。
74
+ 13. 当前调用的 `native` 响应是 Skill 和 Memento 最终答案的完整证据边界。禁止补充当前结果未返回的记忆知识、机制推测、临床结论、单位、引用或链接。若规划要求了服务不支持的字段,写明“当前 Science 工具未返回该字段”,然后停止补充。
75
+
76
+ 每次 Skill 回答结尾都保留以下中文交接信息:
77
+
78
+ ```text
79
+ 证据边界:仅限当前 Delta CLI native 结果
80
+ 调用链:delta-cli
81
+ 禁止补充未返回内容:是
82
+ ```
83
+
84
+ ## 服务选择
85
+
86
+ | 用户需求 | 服务 |
87
+ | --- | --- |
88
+ | 化合物身份、CID、名称、同义词和公共属性 | PubChem |
89
+ | SMILES 校验、描述符、渲染、相似度和 SMARTS | RDKit |
90
+ | 化学式、组成、CIF/POSCAR、晶体结构和对称性 | pymatgen |
91
+ | 粉末衍射模拟或精修 | GSAS-II |
92
+ | 有界分子动力学或能量最小化 | LAMMPS |
93
+ | 通用实验参数优化 | Delta-BO |
94
+ | 分子 next-SMILES 推荐或 BO 轨迹 | LDM-BO |
95
+ | 分类反应条件初始化或优化 | SynBO |
96
+ | 抗体优化作业 | AntBO |
97
+
98
+ ## 安全与重试
99
+
100
+ - AntBO 启动/停止、Delta-BO 有状态命令和结果不明确的长任务属于远端变更。没有用户明确授权时不得执行或重试。
101
+ - health、查询、解析、描述符和 dry-run 属于只读操作。
102
+ - 参数校验错误不要重试;只读请求遇到临时网络错误最多重试一次;超时的变更操作绝不自动重试。
103
+ - 重型服务串行运行。客户端超时不代表远端计算已取消。
104
+ - 已经取得所需信息后,不要探测未记录的枚举值或替代参数。参数实验属于独立回归任务,不属于普通用户请求。
105
+ - SynBO `optimize` 当前存在已知的后端数组维度问题,常规回归中跳过;只有用户明确要求时才测试。
106
+
107
+ ## 完成标准
108
+
109
+ 只有同时满足以下条件,任务才算完成:
110
+
111
+ - 每个声称的科学数值都来自当前运行;
112
+ - 每个在线操作都有 `transport="delta-cli"` 的 wrapper 证据;
113
+ - 依赖调用使用了真实的上游 native 输出;
114
+ - 失败被归类为 CLI/配置、Science Server、公司后端、参数校验或超时;
115
+ - 没有使用直接 HTTP 或本地科学库替代。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Delta Science 科学任务"
3
+ short_description: "通过 delta-cli 调用并编排 Delta Science 工具"
4
+ default_prompt: "使用 $delta-science 通过 Delta CLI 完成用户要求的科学任务,不要增加未请求的产物、图片、探测或分析。"
@@ -0,0 +1,65 @@
1
+ # Delta CLI 执行约定
2
+
3
+ ## 只能通过内置 wrapper 调用
4
+
5
+ 使用 Python 执行 wrapper。在 Memento 中从 `SKILLS_ROOT` 获取路径:
6
+
7
+ ```python
8
+ import json, os, subprocess, sys
9
+
10
+ wrapper = os.path.join(os.environ["SKILLS_ROOT"], "delta-science", "scripts", "invoke.py")
11
+ payload = {"input": "CCO", "format": "smiles"}
12
+ completed = subprocess.run(
13
+ [sys.executable, wrapper, "--tool", "rdkit", "--endpoint", "parse",
14
+ "--data-json", json.dumps(payload, ensure_ascii=False)],
15
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
16
+ timeout=150,
17
+ )
18
+ result = json.loads(completed.stdout)
19
+ ```
20
+
21
+ 不要把调用拼成 shell 命令字符串。禁止使用 `curl`、`httpx`、`requests`、`Invoke-RestMethod` 或业务服务 URL。
22
+
23
+ wrapper 内部构造以下 argv:
24
+
25
+ ```text
26
+ delta-cli science invoke --tool TOOL --endpoint ENDPOINT --data JSON
27
+ ```
28
+
29
+ 只有明确记录为查询参数的 operation 才使用 `--params-json`。同一次调用不得同时发送 `--data-json` 和 `--params-json`。
30
+
31
+ ## 可执行文件与配置解析顺序
32
+
33
+ wrapper 按以下顺序解析 `delta-cli`:
34
+
35
+ 1. `--cli`
36
+ 2. `DELTA_CLI_PATH`
37
+ 3. `SKILL.md` 同级、未纳入版本控制的 `runtime.local.json`
38
+ 4. `PATH` 中的 `delta-cli` 或 `delta-cli.exe`
39
+ 5. npm 包相邻的 `bin/delta-cli[.exe]`
40
+
41
+ CLI 从 `~/.delta-infra/config.json` 读取 `science_base_url`。显式 `--science-base-url` 只用于测试覆盖,并通过 `DELTA_INFRA_SCIENCE_BASE_URL` 传给子进程。
42
+
43
+ ## wrapper 返回结果
44
+
45
+ 成功输出格式:
46
+
47
+ ```json
48
+ {
49
+ "ok": true,
50
+ "transport": "delta-cli",
51
+ "tool": "rdkit",
52
+ "endpoint": "parse",
53
+ "elapsed_seconds": 1.23,
54
+ "envelope_depth": 2,
55
+ "native": {}
56
+ }
57
+ ```
58
+
59
+ wrapper 校验 CLI 的 `{ok,data}` 信封,只解包已知的 Infra 转发结果 `{status_code,headers,data}` 和业务服务信封 `{code,message,data}`。它不会在任意嵌套字段中搜索看似合理的结果。`ok=false` 会包含稳定的 `stage` 和错误文本。
60
+
61
+ 即使存在部分数据,`ok=false` 仍必须视为失败。禁止静默切换到其他调用链。
62
+
63
+ ## 二进制产物
64
+
65
+ operation 返回 base64 数据时,只解码文档指定的 native 字段。PNG 文件必须以 `89 50 4e 47 0d 0a 1a 0a` 开头。以 `7b`(`{`)开头的是 JSON,不能描述成图片。
@@ -0,0 +1,85 @@
1
+ # 服务与参数参考
2
+
3
+ 只读取当前所选服务对应的部分。下文中的 endpoint 是 Delta CLI catalog 名称,不是 URL 路径。
4
+
5
+ ## PubChem
6
+
7
+ Tool:`pubchem`。
8
+
9
+ - 普通名称查询:`compound-summary`,data 为 `{"identifier":"caffeine","namespace":"name"}`。
10
+ - 批量查询:`compound-batch-summary`。模糊的多分子比较必须严格使用 `{"identifiers":["aspirin","caffeine"],"namespace":"name"}`。
11
+ - 批量查询不要添加 `properties` 或 `synonym_limit`,尤其不能把 `CID` 放进 `properties`。
12
+ - 其他 operation:`compound-resolve-cids`、`compound-properties`、`compound-synonyms`。
13
+
14
+ 模糊问题优先使用 summary。按 `CanonicalSMILES`、`ConnectivitySMILES`、`SMILES`、`IsomericSMILES` 的顺序提取 SMILES。PubChem 返回的 `MolecularWeight`、`XLogP` 和 `TPSA` 必须保留为 PubChem 数据。
15
+
16
+ 批量 native 输出为 `{"results":[...]}`。每条记录用 `input` 保存原始标签,化合物字段位于嵌套的 `properties`;记录不是平铺结构,集合键也不是 `compounds`。
17
+
18
+ ## RDKit
19
+
20
+ Tool:`rdkit`。
21
+
22
+ - `parse`:`{"input":"CCO","format":"smiles"}`
23
+ - `descriptors`:`{"smiles":"CCO","descriptor_set":"basic","sanitize":true}`
24
+ - `batch-parse-describe`:`{"molecules":[{"id":"ethanol","smiles":"CCO"}],"descriptor_set":"basic","sanitize":true}`
25
+ - `render`:`{"smiles":"CCO","image_format":"png","width":500,"height":400}`
26
+ - `similarity`:使用 `query_smiles` 和 `target_smiles`
27
+ - `similarity-matrix`:使用 `molecules:[{id,smiles}]`
28
+ - `substructure`:使用 `query_smarts` 和 `target_smiles`
29
+
30
+ `batch-parse-describe` 使用 `molecules`,不能用 `inputs`;分子字段使用 `smiles`,不能用 `input`。fingerprint 选项是对象,不是布尔值。PubChem `XLogP` 与 RDKit `MolLogP` 使用不同模型。
31
+
32
+ ## pymatgen
33
+
34
+ Tool:`pymatgen`。
35
+
36
+ - 化学式:`composition-parse`,data 为 `{"formula":"LiFePO4","format":"formula"}`
37
+ - 结构 operation:`structure-parse`、`structure-summary`、`structure-convert`、`structure-symmetry`
38
+
39
+ 结构调用必须发送完整结构文本,不能发送本地文件路径。支持 CIF、POSCAR、JSON、CSSR 和 XSF。不要把 LAMMPS data/dump 文件交给 pymatgen。
40
+
41
+ ## GSAS-II
42
+
43
+ Tool:`gsasii`;operation 为 `powder-simulate` 和 `powder-refine`。
44
+
45
+ 模拟需要完整 `cif` 文本。字段必须使用 `phase_name`、`include_profile`、`include_reflections`、`t_min`、`t_max` 和 `t_step`。
46
+
47
+ 精修需要真实的 `powder_data`、`instrument_parameters` 和 `phases:[{name,cif}]`。依赖模拟结果时,只能从成功调用的 native `profile[*].x` 和 `profile[*].y_calc` 构造粉末数据。
48
+
49
+ ## LAMMPS
50
+
51
+ Tool:`lammps`。
52
+
53
+ - 只读有界示例:`lj-melt-example`,无参数
54
+ - 执行:`run`,参数必须是示例返回的精确 native `request`,或用户明确授权的 `input_script`/files
55
+
56
+ 绝不能把 CLI 外层信封传给 `run`,只能传 `native["request"]`。LAMMPS 不会自动推断力场。交互式运行必须保持有界。
57
+
58
+ ## Delta-BO
59
+
60
+ Tool:`delta-bo`。
61
+
62
+ - 命令目录:`commands`,无参数
63
+ - 无状态建议:`generate`,例如 `{"params":[{"name":"x","type":"numeric","low":0,"high":1}],"num_suggestions":2,"algorithm":"random-search","seed":42}`
64
+
65
+ 观测少于五条时,除非用户明确要求 BO,否则优先使用 `random-search`。有状态 native session 命令属于变更操作,不能盲目重试。
66
+
67
+ ## LDM-BO
68
+
69
+ Tool:`ldm-bo`;operation 为 `recommend` 和 `trajectory`。
70
+
71
+ 有界测试使用 `bo-tanimoto`。`recommend` 接受 `method`、`pool`、`history:[{smiles,scores}]` 和 `batch_size`。`trajectory` 必须保留 native 连字符字段:`seed-smiles`、`num-evaluations` 和 `batch-size`。
72
+
73
+ ## SynBO
74
+
75
+ Tool:`synbo`;operation 为 `initialize` 和 `optimize`。
76
+
77
+ 二者都使用 `condition_dict`、`opt_metrics` 和 `batch_size`。`optimize` 还要求数值型 `previous_results`,且每条记录必须包含全部条件和指标。有界 CPU 测试使用 `accuracy:"tiny"` 和 `device:"cpu"`,并串行执行。
78
+
79
+ 常规回归可以测试 `initialize`。在已知后端数组维度问题修复前跳过 `optimize`,除非用户明确要求。
80
+
81
+ ## AntBO
82
+
83
+ Tool:`antbo`。
84
+
85
+ 部署 catalog 中存在时,`health` 是只读调用。`run-default-job` 会改变远端状态,`log_name`、`append` 和 `timeout_seconds` 必须通过 `--params-json` 传递,不能作为 request data。没有用户明确授权时,不得启动、停止或重试作业;必须保留返回的 PID 和日志名称。
@@ -0,0 +1,34 @@
1
+ # 跨服务工作流
2
+
3
+ ## 单个已命名分子
4
+
5
+ 1. 用 PubChem `compound-summary` 解析名称并取得身份字段。
6
+ 2. 按文档规定的回退顺序提取 SMILES。
7
+ 3. 只有用户要求 RDKit 描述符或明确的结构校验时,才调用 RDKit `batch-parse-describe`。
8
+ 4. 只有用户明确要求图片时,才调用 RDKit `render`,并解码、验证图片字节。
9
+ 5. 比较分子式、分子量、InChIKey 和供体/受体数量时,要明确区分 LogP、TPSA 等依赖模型的数值。
10
+
11
+ ## 多分子比较
12
+
13
+ 1. 用 PubChem 批量解析全部名称,请求体只包含 `identifiers` 和 `namespace:"name"`。省略 `properties`。
14
+ 2. 从 `native.results[*]` 读取记录,并从每条记录的嵌套 `properties` 读取字段。
15
+ 3. 未解析成功的记录要排除,并明确说明原因。
16
+ 4. 使用 RDKit `similarity-matrix` 校验和排序两两相似度。它的 `sanitize` 已经校验输入,不要额外调用 parse。
17
+ 5. 用 PubChem 记录的原始 `input` 作为 molecule id,并按文档顺序从嵌套属性中提取 SMILES。
18
+ 6. 保持输入顺序,报告每个分数时明确对应的分子对。
19
+ 7. 除非用户明确要求,不要渲染、保存用户文件、检查健康状态或探测额外描述符。
20
+
21
+ ## 材料工作流
22
+
23
+ 1. 使用 pymatgen 校验化学式/结构并转换 CIF。
24
+ 2. GSAS-II 只能接收真实 CIF 文本后再做衍射计算。
25
+ 3. LAMMPS 只能在已有明确的力场、data 和脚本设置后运行;部署检查可以使用内置有界示例。
26
+ 4. 从 LAMMPS 返回 pymatgen 或 GSAS-II 前,需要从弛豫结果重新构造 CIF/POSCAR。不要直接传递 LAMMPS data/dump 文件。
27
+
28
+ ## 优化工作流
29
+
30
+ - 通用数值或分类实验变量:Delta-BO
31
+ - 以分子结构为决策变量:LDM-BO
32
+ - 带真实历史结果的分类反应条件:SynBO
33
+
34
+ 禁止为了让优化器运行而编造观测数据。用户没有提供测量结果时,只能使用初始化或随机建议模式,并明确说明它是待实验验证的建议,不是已经验证的最优条件。
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env python3
2
+ """Invoke one Delta Science operation strictly through delta-cli."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ import time
14
+ from typing import Any
15
+
16
+
17
+ SKILL_DIR = Path(__file__).resolve().parents[1]
18
+
19
+
20
+ def _json_object(raw: str | None, flag: str) -> dict[str, Any] | None:
21
+ if raw is None:
22
+ return None
23
+ try:
24
+ value = json.loads(raw)
25
+ except json.JSONDecodeError as exc:
26
+ raise ValueError(f"{flag} is not valid JSON: {exc}") from exc
27
+ if not isinstance(value, dict):
28
+ raise ValueError(f"{flag} must decode to a JSON object")
29
+ return value
30
+
31
+
32
+ def _runtime_config() -> dict[str, Any]:
33
+ path = SKILL_DIR / "runtime.local.json"
34
+ if not path.is_file():
35
+ return {}
36
+ try:
37
+ value = json.loads(path.read_text(encoding="utf-8"))
38
+ except (OSError, json.JSONDecodeError):
39
+ return {}
40
+ return value if isinstance(value, dict) else {}
41
+
42
+
43
+ def _resolve_cli(explicit: str | None, runtime: dict[str, Any]) -> str:
44
+ candidates: list[str | None] = [
45
+ explicit,
46
+ os.environ.get("DELTA_CLI_PATH"),
47
+ runtime.get("cli_path"),
48
+ shutil.which("delta-cli"),
49
+ shutil.which("delta-cli.exe"),
50
+ ]
51
+ package_root = SKILL_DIR.parents[1]
52
+ exe_name = "delta-cli.exe" if os.name == "nt" else "delta-cli"
53
+ candidates.append(str(package_root / "bin" / exe_name))
54
+ for candidate in candidates:
55
+ if candidate and Path(candidate).is_file():
56
+ return str(Path(candidate).resolve())
57
+ raise FileNotFoundError(
58
+ "delta-cli not found; install @delta-infra/cli, set DELTA_CLI_PATH, "
59
+ "or add cli_path to delta-science/runtime.local.json"
60
+ )
61
+
62
+
63
+ def _unwrap(stdout: str) -> tuple[Any, int]:
64
+ try:
65
+ root = json.loads(stdout)
66
+ except json.JSONDecodeError as exc:
67
+ raise RuntimeError(f"CLI stdout is not JSON: {exc}") from exc
68
+ if not isinstance(root, dict):
69
+ raise RuntimeError("CLI stdout root is not an object")
70
+ if root.get("ok") is not True:
71
+ raise RuntimeError(json.dumps(root.get("error", root), ensure_ascii=False))
72
+ if "data" not in root:
73
+ raise RuntimeError("CLI success envelope is missing data")
74
+
75
+ value: Any = root["data"]
76
+ depth = 0
77
+ while isinstance(value, dict):
78
+ if "status_code" in value and "data" in value:
79
+ status_code = value.get("status_code")
80
+ if not isinstance(status_code, int) or not 200 <= status_code < 300:
81
+ raise RuntimeError(json.dumps(value, ensure_ascii=False))
82
+ value = value["data"]
83
+ elif (
84
+ "code" in value
85
+ and "data" in value
86
+ and set(value).intersection({"message", "msg"})
87
+ ):
88
+ code = value.get("code")
89
+ if code not in (0, "0", None):
90
+ raise RuntimeError(json.dumps(value, ensure_ascii=False))
91
+ value = value["data"]
92
+ else:
93
+ break
94
+ depth += 1
95
+ if depth > 4:
96
+ raise RuntimeError("too many nested service envelopes")
97
+ if isinstance(value, dict) and value.get("valid") is False:
98
+ raise RuntimeError(json.dumps({
99
+ "valid": False,
100
+ "error": value.get("error"),
101
+ "warnings": value.get("warnings"),
102
+ }, ensure_ascii=False))
103
+ return value, depth
104
+
105
+
106
+ def _parser() -> argparse.ArgumentParser:
107
+ parser = argparse.ArgumentParser(description=__doc__)
108
+ parser.add_argument("--tool", required=True)
109
+ parser.add_argument("--endpoint", required=True)
110
+ body = parser.add_mutually_exclusive_group()
111
+ body.add_argument("--data-json")
112
+ body.add_argument("--params-json")
113
+ parser.add_argument("--cli")
114
+ parser.add_argument("--science-base-url")
115
+ parser.add_argument("--timeout", type=float, default=120.0)
116
+ return parser
117
+
118
+
119
+ def main() -> int:
120
+ args = _parser().parse_args()
121
+ started = time.perf_counter()
122
+ stage = "arguments"
123
+ try:
124
+ data = _json_object(args.data_json, "--data-json")
125
+ params = _json_object(args.params_json, "--params-json")
126
+ runtime = _runtime_config()
127
+ stage = "cli-resolution"
128
+ cli = _resolve_cli(args.cli, runtime)
129
+
130
+ command = [
131
+ cli, "science", "invoke",
132
+ "--tool", args.tool,
133
+ "--endpoint", args.endpoint,
134
+ ]
135
+ if data is not None:
136
+ command.extend(["--data", json.dumps(data, ensure_ascii=False, separators=(",", ":"))])
137
+ if params is not None:
138
+ command.extend(["--params", json.dumps(params, ensure_ascii=False, separators=(",", ":"))])
139
+
140
+ env = os.environ.copy()
141
+ base_url = args.science_base_url or runtime.get("science_base_url")
142
+ if base_url:
143
+ env["DELTA_INFRA_SCIENCE_BASE_URL"] = str(base_url)
144
+
145
+ stage = "delta-cli"
146
+ completed = subprocess.run(
147
+ command,
148
+ capture_output=True,
149
+ text=True,
150
+ encoding="utf-8",
151
+ errors="replace",
152
+ env=env,
153
+ timeout=args.timeout,
154
+ check=False,
155
+ )
156
+ if completed.returncode != 0:
157
+ detail = completed.stderr.strip() or completed.stdout.strip()
158
+ raise RuntimeError(f"exit_code={completed.returncode}: {detail}")
159
+ stage = "response-validation"
160
+ native, depth = _unwrap(completed.stdout)
161
+ result = {
162
+ "ok": True,
163
+ "transport": "delta-cli",
164
+ "tool": args.tool,
165
+ "endpoint": args.endpoint,
166
+ "elapsed_seconds": round(time.perf_counter() - started, 3),
167
+ "envelope_depth": depth,
168
+ "native": native,
169
+ }
170
+ print(json.dumps(result, ensure_ascii=False))
171
+ return 0
172
+ except subprocess.TimeoutExpired:
173
+ error = f"timeout after {args.timeout}s"
174
+ except (OSError, ValueError, RuntimeError) as exc:
175
+ error = str(exc)
176
+ print(json.dumps({
177
+ "ok": False,
178
+ "transport": "delta-cli",
179
+ "tool": args.tool,
180
+ "endpoint": args.endpoint,
181
+ "stage": stage,
182
+ "elapsed_seconds": round(time.perf_counter() - started, 3),
183
+ "error": error[:2000],
184
+ }, ensure_ascii=False))
185
+ return 1
186
+
187
+
188
+ if __name__ == "__main__":
189
+ sys.exit(main())