@zhuoyuezs/ml-platform 0.2.7 → 0.2.9
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/CI-ARTIFACT.md +2 -2
- package/checksums.json +36 -36
- package/package.json +1 -1
- package/release.json +18 -18
- package/runtime/business-client/README.md +66 -3
- package/runtime/business-client/package-lock.json +2 -2
- package/runtime/business-client/package.json +1 -1
- package/runtime/business-client/src/catalog.js +25 -6
- package/runtime/business-client/src/cli.js +90 -6
- package/runtime/business-client/src/http.js +192 -32
- package/skills/feature-management/SKILL.md +17 -7
- package/skills/feature-management/references/commands.md +50 -7
- package/skills/feature-management/references/contracts.md +124 -6
- package/skills/feature-management/references/operator-authoring.md +116 -8
- package/skills/model-deployment-management/SKILL.md +3 -0
- package/skills/model-deployment-management/references/operations.md +14 -1
- package/skills/model-lifecycle-management/SKILL.md +16 -0
- package/skills/model-lifecycle-management/references/discovery.md +18 -3
- package/skills/model-lifecycle-management/references/evaluation.md +133 -0
- package/skills/model-lifecycle-management/references/packaging.md +46 -0
- package/skills/model-lifecycle-management/references/training-contracts.md +172 -1
- package/skills/model-lifecycle-management/references/training.md +26 -0
- package/skills/prediction-report-management/SKILL.md +42 -6
package/CI-ARTIFACT.md
CHANGED
|
@@ -17,5 +17,5 @@ npm install --global ./zhuoyuezs-ml-platform-*.tgz
|
|
|
17
17
|
ml-platform version
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
运行已安装的 CLI 不需要 Python、`uv`、Git
|
|
21
|
-
|
|
20
|
+
运行已安装的 CLI 不需要 Python、`uv`、Git 仓库或平台源码。CI 构建阶段使用
|
|
21
|
+
`node:22-bookworm` 中的 Python 3.11 运行 release staging 脚本。
|
package/checksums.json
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
"files": [
|
|
3
3
|
{
|
|
4
4
|
"path": "runtime/business-client/README.md",
|
|
5
|
-
"sha256": "sha256:
|
|
6
|
-
"size_bytes":
|
|
5
|
+
"sha256": "sha256:9f81e4400f46c53952a0df6f7442d4c0b58cff4ca1be61f1daf785e111b3f8eb",
|
|
6
|
+
"size_bytes": 20557
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"path": "runtime/business-client/package-lock.json",
|
|
10
|
-
"sha256": "sha256:
|
|
10
|
+
"sha256": "sha256:9d19700ee4b348406bd0134f0539c37c2800e41a87dc130c1b0476eb2d23b931",
|
|
11
11
|
"size_bytes": 1628
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"path": "runtime/business-client/package.json",
|
|
15
|
-
"sha256": "sha256:
|
|
15
|
+
"sha256": "sha256:d967d64289276cf1ce94f826a138f60fc93982b7478c1fb9f78f068f47f9d803",
|
|
16
16
|
"size_bytes": 671
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "runtime/business-client/src/catalog.js",
|
|
20
|
-
"sha256": "sha256:
|
|
21
|
-
"size_bytes":
|
|
20
|
+
"sha256": "sha256:94e4b43f248b752d9a9cec42e1f705849f01e4167bb570b9557b556c5be12c19",
|
|
21
|
+
"size_bytes": 59791
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"path": "runtime/business-client/src/cli-arguments.js",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"path": "runtime/business-client/src/cli.js",
|
|
30
|
-
"sha256": "sha256:
|
|
31
|
-
"size_bytes":
|
|
30
|
+
"sha256": "sha256:c235053473fb55737dc2c6269a556ed9e4206b074327c0acaf8ac663f1ea00da",
|
|
31
|
+
"size_bytes": 79409
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "runtime/business-client/src/config.js",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"path": "runtime/business-client/src/http.js",
|
|
40
|
-
"sha256": "sha256:
|
|
41
|
-
"size_bytes":
|
|
40
|
+
"sha256": "sha256:91dc66d086deb5b5c0fd016560e6fddd369ccce9d430ce8f926bcae3f324cc86",
|
|
41
|
+
"size_bytes": 29035
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"path": "skills/data-source-exploration/SKILL.md",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"path": "skills/feature-management/SKILL.md",
|
|
55
|
-
"sha256": "sha256:
|
|
56
|
-
"size_bytes":
|
|
55
|
+
"sha256": "sha256:1435e380ede73fc9775bd0579ff9df08baf07b4eed8b1bbc174b5b394a72914f",
|
|
56
|
+
"size_bytes": 35359
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"path": "skills/feature-management/agents/openai.yaml",
|
|
@@ -107,18 +107,18 @@
|
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
109
|
"path": "skills/feature-management/references/commands.md",
|
|
110
|
-
"sha256": "sha256:
|
|
111
|
-
"size_bytes":
|
|
110
|
+
"sha256": "sha256:375f953bac0263f9a40d217c99f029226d2a838c9510fce8d6e2324b8f548fb1",
|
|
111
|
+
"size_bytes": 18288
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
114
|
"path": "skills/feature-management/references/contracts.md",
|
|
115
|
-
"sha256": "sha256:
|
|
116
|
-
"size_bytes":
|
|
115
|
+
"sha256": "sha256:717b81364f8508cd73d52f18d7b5dd012a3f3638921a8a5a93c41991a4f27998",
|
|
116
|
+
"size_bytes": 38182
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
"path": "skills/feature-management/references/operator-authoring.md",
|
|
120
|
-
"sha256": "sha256:
|
|
121
|
-
"size_bytes":
|
|
120
|
+
"sha256": "sha256:68ec5861800a6623db8e3cfae8e3363493c54772c4719d60dc66e4f4f0d3a347",
|
|
121
|
+
"size_bytes": 12570
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
"path": "skills/feature-management/references/platform-capability-guide.md",
|
|
@@ -132,8 +132,8 @@
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
"path": "skills/model-deployment-management/SKILL.md",
|
|
135
|
-
"sha256": "sha256:
|
|
136
|
-
"size_bytes":
|
|
135
|
+
"sha256": "sha256:84c58dbc71046ead45e3bd1aea55a5f98908e633314dbd088ce6d2c83987011c",
|
|
136
|
+
"size_bytes": 5195
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"path": "skills/model-deployment-management/agents/openai.yaml",
|
|
@@ -147,8 +147,8 @@
|
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
"path": "skills/model-deployment-management/references/operations.md",
|
|
150
|
-
"sha256": "sha256:
|
|
151
|
-
"size_bytes":
|
|
150
|
+
"sha256": "sha256:262ac8d3ab7524a9b30a14a95550a28821626a8832fd967fe3e9acd015f8c7b2",
|
|
151
|
+
"size_bytes": 6987
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
"path": "skills/model-deployment-management/references/troubleshooting.md",
|
|
@@ -157,8 +157,8 @@
|
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"path": "skills/model-lifecycle-management/SKILL.md",
|
|
160
|
-
"sha256": "sha256:
|
|
161
|
-
"size_bytes":
|
|
160
|
+
"sha256": "sha256:55f0172c0b79a04b8ddfeb6bb9f617e7cb5da622b6bd43b3dbae2d3318f6f877",
|
|
161
|
+
"size_bytes": 7447
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
"path": "skills/model-lifecycle-management/agents/openai.yaml",
|
|
@@ -167,13 +167,13 @@
|
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
"path": "skills/model-lifecycle-management/references/discovery.md",
|
|
170
|
-
"sha256": "sha256:
|
|
171
|
-
"size_bytes":
|
|
170
|
+
"sha256": "sha256:6ae9afc7169f4e51eefa8ac124b2fa5d9d26bed13ebbc57ec9fbbbcdc6b1960c",
|
|
171
|
+
"size_bytes": 6643
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
"path": "skills/model-lifecycle-management/references/evaluation.md",
|
|
175
|
-
"sha256": "sha256:
|
|
176
|
-
"size_bytes":
|
|
175
|
+
"sha256": "sha256:ba16117c0bf37ac92e28dc9b5e3bda266ac6ad29993c3d4bfcc1c955bb0ab3fa",
|
|
176
|
+
"size_bytes": 16573
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
"path": "skills/model-lifecycle-management/references/model-comparison.md",
|
|
@@ -182,18 +182,18 @@
|
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
"path": "skills/model-lifecycle-management/references/packaging.md",
|
|
185
|
-
"sha256": "sha256:
|
|
186
|
-
"size_bytes":
|
|
185
|
+
"sha256": "sha256:da2bde7db390643efbf7a3c8dbb668b0ffe69a1d62ec2dd6436cec5b05cc14ec",
|
|
186
|
+
"size_bytes": 9921
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
"path": "skills/model-lifecycle-management/references/training-contracts.md",
|
|
190
|
-
"sha256": "sha256:
|
|
191
|
-
"size_bytes":
|
|
190
|
+
"sha256": "sha256:45568961f933b134e912a20b4ad37d435616d44c3159f302fbccaff2ffe16389",
|
|
191
|
+
"size_bytes": 27143
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
"path": "skills/model-lifecycle-management/references/training.md",
|
|
195
|
-
"sha256": "sha256:
|
|
196
|
-
"size_bytes":
|
|
195
|
+
"sha256": "sha256:654d3d45bfae4906f7f37898d09fa1fa84ecaab46346190c439f9f3fc0e17f95",
|
|
196
|
+
"size_bytes": 9691
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
"path": "skills/model-lifecycle-management/references/zero-shot-inference.md",
|
|
@@ -202,8 +202,8 @@
|
|
|
202
202
|
},
|
|
203
203
|
{
|
|
204
204
|
"path": "skills/prediction-report-management/SKILL.md",
|
|
205
|
-
"sha256": "sha256:
|
|
206
|
-
"size_bytes":
|
|
205
|
+
"sha256": "sha256:fb1f8c579d392cc4d030d41b5c3b06cd59ab0c488de343bae93cbc87a27dd650",
|
|
206
|
+
"size_bytes": 8707
|
|
207
207
|
},
|
|
208
208
|
{
|
|
209
209
|
"path": "skills/prediction-report-management/agents/openai.yaml",
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"entrypoint": "src/cli.js",
|
|
14
14
|
"name": "ml-platform",
|
|
15
15
|
"path": "runtime/business-client",
|
|
16
|
-
"sha256": "sha256:
|
|
17
|
-
"version": "0.8.
|
|
16
|
+
"sha256": "sha256:6b8adc18d15ead31da78bd6e4274d17bb7277edcb0d76a9b476ae77a3be22812",
|
|
17
|
+
"version": "0.8.7"
|
|
18
18
|
},
|
|
19
19
|
"policy_sha256": "sha256:02fbde0134696b78c0c365d6e0b76c9814227208da5b2b3d4e5dbd82dc4a3606",
|
|
20
20
|
"public_schema_versions": [
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"ml_data_platform.feature_set/v1",
|
|
26
26
|
"ml_data_platform.dataset_manifest/v1"
|
|
27
27
|
],
|
|
28
|
-
"release_version": "0.2.
|
|
28
|
+
"release_version": "0.2.9",
|
|
29
29
|
"runtime_requirements": {
|
|
30
30
|
"node": ">=18",
|
|
31
31
|
"os": [
|
|
@@ -37,34 +37,34 @@
|
|
|
37
37
|
"skills": {
|
|
38
38
|
"data-source-exploration": {
|
|
39
39
|
"path": "skills/data-source-exploration",
|
|
40
|
-
"requires_cli": ">=0.8.
|
|
41
|
-
"revision": "0.2.
|
|
40
|
+
"requires_cli": ">=0.8.7 <0.9.0",
|
|
41
|
+
"revision": "0.2.9",
|
|
42
42
|
"sha256": "sha256:6cb1cad3e9531e5098582c532288edd9838c2acab2b324794ec99d2550418f32"
|
|
43
43
|
},
|
|
44
44
|
"feature-management": {
|
|
45
45
|
"path": "skills/feature-management",
|
|
46
|
-
"requires_cli": ">=0.8.
|
|
47
|
-
"revision": "0.2.
|
|
48
|
-
"sha256": "sha256:
|
|
46
|
+
"requires_cli": ">=0.8.7 <0.9.0",
|
|
47
|
+
"revision": "0.2.9",
|
|
48
|
+
"sha256": "sha256:2bd8e19ffe43e414fe74a269a75ccfdb88e66c7f2a52df6230a929321dbb54ea"
|
|
49
49
|
},
|
|
50
50
|
"model-deployment-management": {
|
|
51
51
|
"path": "skills/model-deployment-management",
|
|
52
|
-
"requires_cli": ">=0.8.
|
|
53
|
-
"revision": "0.2.
|
|
54
|
-
"sha256": "sha256:
|
|
52
|
+
"requires_cli": ">=0.8.7 <0.9.0",
|
|
53
|
+
"revision": "0.2.9",
|
|
54
|
+
"sha256": "sha256:26cbe95ca26371f3b34a4b83a1f185eeb879f64f099d9dbc30e4cc4ec81d9c64"
|
|
55
55
|
},
|
|
56
56
|
"model-lifecycle-management": {
|
|
57
57
|
"path": "skills/model-lifecycle-management",
|
|
58
|
-
"requires_cli": ">=0.8.
|
|
59
|
-
"revision": "0.2.
|
|
60
|
-
"sha256": "sha256:
|
|
58
|
+
"requires_cli": ">=0.8.7 <0.9.0",
|
|
59
|
+
"revision": "0.2.9",
|
|
60
|
+
"sha256": "sha256:40d8b5eef72a937c208c3607b41666015ac143f604fb0521e0953659306182b2"
|
|
61
61
|
},
|
|
62
62
|
"prediction-report-management": {
|
|
63
63
|
"path": "skills/prediction-report-management",
|
|
64
|
-
"requires_cli": ">=0.8.
|
|
65
|
-
"revision": "0.2.
|
|
66
|
-
"sha256": "sha256:
|
|
64
|
+
"requires_cli": ">=0.8.7 <0.9.0",
|
|
65
|
+
"revision": "0.2.9",
|
|
66
|
+
"sha256": "sha256:0aee744970236f0357aa9e264e31e144b3cb6fab4a3ea6a01f184dcdaef7e846"
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
|
-
"source_commit": "
|
|
69
|
+
"source_commit": "e0c33cf9f1d052d2a24a574393fdf804438a4543"
|
|
70
70
|
}
|
|
@@ -81,8 +81,10 @@ ml-platform get-deployment <deployment-id>
|
|
|
81
81
|
ml-platform get-deployment-events <deployment-id> --limit 100 --offset 0
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
`list-deployments`
|
|
85
|
-
|
|
84
|
+
`list-deployments` 支持 `--service` 对 service 名称做不区分大小写的子串匹配(忽略首尾空白),
|
|
85
|
+
也支持按 `environment`、`state` 和跨字段自由文本 `q` 过滤,返回平台
|
|
86
|
+
Deployment Controller 管理的部署,不把模型中心回写的 delivery 状态当作平台部署。列表支持
|
|
87
|
+
`--sort-by created_at|updated_at` 和 `--sort-order asc|desc`,默认按 `deployment_id` 升序。
|
|
86
88
|
|
|
87
89
|
部署业务分类由平台控制面管理。领域固定为 `power`(电力)、`blast_furnace`(高炉)和
|
|
88
90
|
`other`(其他),场景可按领域自定义:
|
|
@@ -126,7 +128,11 @@ ml-platform list-deployments --business-domain-id power --scenario-id energy_sto
|
|
|
126
128
|
"values": {
|
|
127
129
|
"source": "predictions", "type": "array",
|
|
128
130
|
"items": {"fields": {
|
|
129
|
-
"
|
|
131
|
+
"date": {"source": "target_time", "format": "%Y-%m-%d", "timezone": "Asia/Shanghai",
|
|
132
|
+
"midnight_as_24": true, "type": "string"},
|
|
133
|
+
"time": {"source": "target_time", "format": "%H:%M", "timezone": "Asia/Shanghai",
|
|
134
|
+
"midnight_as_24": true, "type": "string"},
|
|
135
|
+
"timestamp": {"source": "target_time", "type": "string"},
|
|
130
136
|
"value": {"source": "value", "type": "number", "scale": 1.0, "offset": 0.0}
|
|
131
137
|
}}
|
|
132
138
|
}
|
|
@@ -158,6 +164,11 @@ ml-platform list-deployments --business-domain-id power --scenario-id energy_sto
|
|
|
158
164
|
`target_time`。`source` 引用平台标准预测结果中的字段;`fields` 生成嵌套对象,`items` 将数组逐项转换,
|
|
159
165
|
`constant` 写入固定值,`template` 可拼接已存在的字符串字段。`format` 可格式化时间;需要按业务时区格式化时,
|
|
160
166
|
同时设置 `timezone`(IANA 名称),例如 `2026-09-10T16:00:00Z` 配置 `Asia/Shanghai` 后得到 `2026-09-11`;
|
|
167
|
+
如果目标接口把本地日界的午夜表达为前一日的 `24:00`,在业务日期和时间两个字段上均设置
|
|
168
|
+
`midnight_as_24: true` 和 `timezone: "Asia/Shanghai"`:`%Y-%m-%d` 输出前一日,`%H:%M` 输出 `24:00`。
|
|
169
|
+
例如 `2026-10-31T00:00:00+08:00` 对应 `date=2026-10-30`、`time=24:00`,其他时刻保持不变。
|
|
170
|
+
`timestamp` 直接引用 `target_time`,MW 数值直接引用 `value`,不添加格式化或数值换算即可保持原值。
|
|
171
|
+
表示起报本地日期的 `origin_date` 不设置 `midnight_as_24`;
|
|
161
172
|
`scale` 和 `offset` 可执行受控数值换算,`type` 校验最终类型。`token_path`、业务成功码和
|
|
162
173
|
`/forecast/report` 只是示例,必须按目标接口的真实响应和路径配置。
|
|
163
174
|
映射字段缺失或类型不匹配时不会发送请求,Run 会记录 `payload_mapping_failed`。若响应提供有效期,可设置 `expires_in_path` 与 `expires_in_unit`;否则服务端默认按 7200 秒缓存,
|
|
@@ -301,10 +312,62 @@ Training Run and Job validation also return `effective_trainer_parameters`;
|
|
|
301
312
|
for Job preflight it must match `job_spec.trainer_parameters` and represents
|
|
302
313
|
the complete mapping delivered to the Worker after defaults are resolved.
|
|
303
314
|
|
|
315
|
+
## Artifact file downloads
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
ml-platform download-model-artifact <artifact_id> --out-dir /tmp/model [--file runtime.json] [--project <project>]
|
|
319
|
+
ml-platform download-evaluation-result-artifact <result_id> --out-dir /tmp/eval [--file predictions.parquet]
|
|
320
|
+
ml-platform download-dataset-artifact <dataset_id> <artifact_manifest_hash> --project <project> --out-dir /tmp/ds
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
The lifecycle commands read the registry file index (`get-model-artifact` /
|
|
324
|
+
`get-evaluation-result-artifacts`), download every indexed file or each
|
|
325
|
+
repeated `--file`, and keep a file only when its sha256 equals the registry
|
|
326
|
+
record; a mismatch removes the output directory. Names outside the index fail
|
|
327
|
+
before any file request. They call the read-only
|
|
328
|
+
`GET /model-artifacts/{id}/files/{path}` and
|
|
329
|
+
`GET /evaluation-results/{id}/files/{path}` routes, which verify the sha256
|
|
330
|
+
server-side, accept an optional `project` scope, and refuse files above
|
|
331
|
+
`DATA_PLATFORM_LIFECYCLE_ARTIFACT_FILE_MAX_BYTES` (default 2 GiB).
|
|
332
|
+
|
|
333
|
+
A DatasetArtifact is keyed by the artifact manifest hash, not the registered
|
|
334
|
+
manifest hash. `build-dataset --wait`, `build-registered-dataset --wait` and
|
|
335
|
+
`wait-job` add an `artifact` object (`project`, `dataset_id`,
|
|
336
|
+
`dataset_version`, `manifest_hash`, `artifact_uri`, `row_count`) to a succeeded
|
|
337
|
+
build Job; use `artifact.manifest_hash` for downloads and dataset bindings. The
|
|
338
|
+
Job's top-level `manifest_hash` is unchanged.
|
|
339
|
+
|
|
340
|
+
`--idempotency-key` on `create-project`, `register-training-run`,
|
|
341
|
+
`submit-training-job`, `register-evaluation-config`, `submit-evaluation` and
|
|
342
|
+
`create-model-package` is sent as the `Idempotency-Key` header, which the
|
|
343
|
+
current server does not enforce. Inspect platform state before retrying an
|
|
344
|
+
uncertain submission.
|
|
345
|
+
|
|
304
346
|
Maintainers can export the current API, including uncommitted schema changes,
|
|
305
347
|
with `uv run python scripts/generate_openapi_schema.py --working-tree` from the
|
|
306
348
|
repository root; use `--check` with it to verify the committed snapshot.
|
|
307
349
|
|
|
350
|
+
## Connection handling
|
|
351
|
+
|
|
352
|
+
Every request is sent with `Connection: close`, so the CLI never writes a new
|
|
353
|
+
request onto a pooled keep-alive socket that the API (uvicorn closes idle
|
|
354
|
+
sockets after 5 s) or a slow VPN path has already dropped. Connection errors
|
|
355
|
+
report the underlying cause, for example
|
|
356
|
+
`fetch failed (UND_ERR_SOCKET: other side closed)` or
|
|
357
|
+
`request timed out after 30s (...)`.
|
|
358
|
+
|
|
359
|
+
Automatic retry (at most 3 retries, 0.5 s / 1 s / 2 s backoff, each attempt
|
|
360
|
+
with its own `--timeout`) happens only before response headers arrive:
|
|
361
|
+
|
|
362
|
+
- `GET`/`HEAD` (including `wait-job` polling and downloads): connection
|
|
363
|
+
errors such as `ECONNREFUSED`, `ECONNRESET`, `UND_ERR_SOCKET`,
|
|
364
|
+
`UND_ERR_CONNECT_TIMEOUT`, and the request timeout.
|
|
365
|
+
- `POST`/`PUT`/`PATCH`/`DELETE`: only `ECONNREFUSED` and
|
|
366
|
+
`UND_ERR_CONNECT_TIMEOUT`, where no TCP connection existed and the request
|
|
367
|
+
cannot have reached the server. Any other failure is reported once with
|
|
368
|
+
`not retried because the server may have received it`; inspect platform
|
|
369
|
+
state before resubmitting.
|
|
370
|
+
|
|
308
371
|
## Agent 宿主接入
|
|
309
372
|
|
|
310
373
|
Coding agent 加载已安装的 Skills 后,通过原生 shell/exec 直接运行普通 `ml-platform` 命令。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhuoyuezs/ml-platform-business-client",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@zhuoyuezs/ml-platform-business-client",
|
|
9
|
-
"version": "0.8.
|
|
9
|
+
"version": "0.8.7",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"bin": {
|
|
12
12
|
"ml-platform": "src/cli.js"
|
|
@@ -454,11 +454,27 @@ function canonical(value) { if (Array.isArray(value))
|
|
|
454
454
|
return value.map(canonical); if (value && typeof value === "object")
|
|
455
455
|
return Object.fromEntries(Object.keys(value).sort().map((name) => [name, canonical(value[name])])); return value; }
|
|
456
456
|
function equal(left, right) { return JSON.stringify(canonical(left)) === JSON.stringify(canonical(right)); }
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
457
|
+
function immutableError(label, assetKey) { const error = new Error(`${label} version is immutable: ${assetKey}`); error.code = "EEXIST"; return error; }
|
|
458
|
+
function assertImmutable(label, assetKey, candidate, existing) { if (existing && !equal(candidate, existing))
|
|
459
|
+
throw immutableError(label, assetKey); }
|
|
460
|
+
// A registered DatasetManifest carries server-filled defaults, including nested ones, that a catalog may
|
|
461
|
+
// omit. A raw mismatch is therefore settled by the server's normalized contract hash; servers without
|
|
462
|
+
// /datasets/validate keep the raw-comparison conflict.
|
|
463
|
+
async function assertDatasetImmutable(client, assetKey, candidate, existing) {
|
|
464
|
+
if (!existing || equal(candidate, existing))
|
|
465
|
+
return;
|
|
466
|
+
let result;
|
|
467
|
+
try {
|
|
468
|
+
result = await client.post("/datasets/validate", candidate);
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
if (/HTTP 40[45]\b/.test(String(error.message)))
|
|
472
|
+
throw immutableError("datasets", assetKey);
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
if (!result?.registered_manifest_hash || result.manifest_hash !== result.registered_manifest_hash)
|
|
476
|
+
throw immutableError("datasets", assetKey);
|
|
477
|
+
}
|
|
462
478
|
function sha256File(file) { return `sha256:${crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex")}`; }
|
|
463
479
|
async function applyCatalog(catalog, client, dryRun, targetProject) {
|
|
464
480
|
validateCatalogProject(catalog, targetProject);
|
|
@@ -483,7 +499,10 @@ async function applyCatalog(catalog, client, dryRun, targetProject) {
|
|
|
483
499
|
}
|
|
484
500
|
const plain = async (assets, existing, label, endpoint, keyFn = key, params) => { for (const asset of assets) {
|
|
485
501
|
const assetKey = keyFn(asset);
|
|
486
|
-
|
|
502
|
+
if (label === "datasets")
|
|
503
|
+
await assertDatasetImmutable(client, assetKey, asset, existing.get(assetKey));
|
|
504
|
+
else
|
|
505
|
+
assertImmutable(label, assetKey, asset, existing.get(assetKey));
|
|
487
506
|
if (existing.has(assetKey))
|
|
488
507
|
summary[label].unchanged += 1;
|
|
489
508
|
else if (dryRun)
|
|
@@ -25,7 +25,7 @@ const BUSINESS_COMMANDS = new Set([
|
|
|
25
25
|
"get-parameter", "get-feature", "get-feature-set", "get-operator",
|
|
26
26
|
"delete-parameter", "delete-feature", "delete-feature-set",
|
|
27
27
|
"restore-parameter", "restore-feature", "restore-feature-set",
|
|
28
|
-
"get-parameter-lifecycle", "get-feature-lifecycle", "get-feature-set-lifecycle",
|
|
28
|
+
"get-parameter-lifecycle", "get-feature-lifecycle", "get-feature-set-lifecycle", "get-parameter-freshness",
|
|
29
29
|
"list-parameter-references", "list-feature-references", "list-feature-set-references",
|
|
30
30
|
"list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions",
|
|
31
31
|
"add-parameter", "add-feature", "add-feature-set", "add-operator", "publish-operator",
|
|
@@ -38,6 +38,7 @@ const BUSINESS_COMMANDS = new Set([
|
|
|
38
38
|
"register-executable-package", "verify-executable-package", "publish-executable-package",
|
|
39
39
|
"validate-evaluation-run", "submit-evaluation", "get-evaluation-run", "get-evaluation-job", "retry-evaluation-job", "cancel-evaluation-job", "list-evaluation-attempts",
|
|
40
40
|
"get-evaluation-result", "get-evaluation-result-metrics", "get-evaluation-result-artifacts", "summarize-evaluation-run", "get-evaluation-summary",
|
|
41
|
+
"download-model-artifact", "download-evaluation-result-artifact",
|
|
41
42
|
"create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact", "register-model-artifact",
|
|
42
43
|
"list-business-domains", "list-scenarios", "create-scenario", "put-deployment-metadata",
|
|
43
44
|
"list-deployment-targets", "validate-deployment", "create-deployment", "list-deployments",
|
|
@@ -91,6 +92,7 @@ const COMMAND_USAGE = {
|
|
|
91
92
|
"restore-feature": "restore-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
92
93
|
"restore-feature-set": "restore-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
93
94
|
"get-parameter-lifecycle": "get-parameter-lifecycle NAME VERSION [--project PROJECT]",
|
|
95
|
+
"get-parameter-freshness": "get-parameter-freshness NAME VERSION [--project PROJECT] [--lookback DURATION] [--end TIMESTAMP] [--online]",
|
|
94
96
|
"get-feature-lifecycle": "get-feature-lifecycle NAME VERSION [--project PROJECT]",
|
|
95
97
|
"get-feature-set-lifecycle": "get-feature-set-lifecycle NAME VERSION [--project PROJECT]",
|
|
96
98
|
"list-parameter-references": "list-parameter-references NAME VERSION [--project PROJECT] [--direct]",
|
|
@@ -159,6 +161,8 @@ const COMMAND_USAGE = {
|
|
|
159
161
|
"get-evaluation-result": "get-evaluation-result RESULT_ID",
|
|
160
162
|
"get-evaluation-result-metrics": "get-evaluation-result-metrics RESULT_ID",
|
|
161
163
|
"get-evaluation-result-artifacts": "get-evaluation-result-artifacts RESULT_ID",
|
|
164
|
+
"download-model-artifact": "download-model-artifact ARTIFACT_ID --out-dir PATH [--file NAME] [--project PROJECT] [--force]",
|
|
165
|
+
"download-evaluation-result-artifact": "download-evaluation-result-artifact RESULT_ID --out-dir PATH [--file NAME] [--project PROJECT] [--force]",
|
|
162
166
|
"summarize-evaluation-run": "summarize-evaluation-run RUN_ID",
|
|
163
167
|
"get-evaluation-summary": "get-evaluation-summary SUMMARY_ID",
|
|
164
168
|
"create-model-package": "create-model-package REQUEST_JSON [--idempotency-key KEY]",
|
|
@@ -173,7 +177,7 @@ const COMMAND_USAGE = {
|
|
|
173
177
|
"list-deployment-targets": "list-deployment-targets",
|
|
174
178
|
"validate-deployment": "validate-deployment SPEC_JSON --evaluation-id ID",
|
|
175
179
|
"create-deployment": "create-deployment SPEC_JSON --evaluation-id ID",
|
|
176
|
-
"list-deployments": "list-deployments [--service
|
|
180
|
+
"list-deployments": "list-deployments [--service TEXT] [--environment ENV] [--package-id ID] [--package-version VERSION] [--model-artifact-id ID] [--state STATE] [--business-domain-id ID] [--scenario-id ID] [-q QUERY] [--sort-by deployment_id|created_at|updated_at] [--sort-order asc|desc] [--limit N] [--offset N]",
|
|
177
181
|
"get-deployment": "get-deployment DEPLOYMENT_ID",
|
|
178
182
|
"wait-deployment": "wait-deployment DEPLOYMENT_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
179
183
|
"reconcile-deployment": "reconcile-deployment DEPLOYMENT_ID",
|
|
@@ -200,10 +204,22 @@ const COMMAND_USAGE = {
|
|
|
200
204
|
"get-prediction-report-run": "get-prediction-report-run RUN_ID",
|
|
201
205
|
apply: "apply CATALOG_DIRECTORY [--dry-run] [--project PROJECT]",
|
|
202
206
|
};
|
|
207
|
+
const IDEMPOTENCY_HEADER_NOTE = "--idempotency-key is sent as the Idempotency-Key header; the current server does not enforce it, so inspect platform state before retrying an uncertain submission.";
|
|
208
|
+
const BUILD_WAIT_NOTE = "With --wait, a succeeded result adds artifact.manifest_hash: the DatasetArtifact key for get/download-dataset-artifact and training/evaluation dataset bindings. The top-level manifest_hash identifies the submitted manifest and is not an artifact key.";
|
|
209
|
+
// Help-only notes. Keep them out of COMMAND_USAGE, which is also the argv grammar.
|
|
210
|
+
const COMMAND_NOTES = {
|
|
211
|
+
"download-dataset-artifact": "MANIFEST_HASH is the artifact manifest hash (list-dataset-artifacts, or artifact.manifest_hash from build-registered-dataset --wait), not the registered manifest hash.",
|
|
212
|
+
"build-dataset": BUILD_WAIT_NOTE,
|
|
213
|
+
"build-registered-dataset": BUILD_WAIT_NOTE,
|
|
214
|
+
"wait-job": "A succeeded build Job result adds artifact.manifest_hash, the DatasetArtifact key.",
|
|
215
|
+
"download-model-artifact": "Downloads every indexed ModelArtifact file, or only each --file NAME, and verifies each sha256 against the registry record. --project fails closed when the artifact belongs to another project.",
|
|
216
|
+
"download-evaluation-result-artifact": "Downloads every indexed EvaluationResult file (for example predictions.parquet, metrics.json, runtime.json), or only each --file NAME, and verifies each sha256 against the registry record.",
|
|
217
|
+
...Object.fromEntries(["create-project", "register-training-run", "submit-training-job", "register-evaluation-config", "submit-evaluation", "create-model-package"].map((name) => [name, IDEMPOTENCY_HEADER_NOTE])),
|
|
218
|
+
};
|
|
203
219
|
function usage(command) {
|
|
204
220
|
const prefix = "ml-platform [--profile server] [--api-url URL] [--request-timeout-seconds SECONDS]";
|
|
205
221
|
if (command)
|
|
206
|
-
return `usage: ${prefix} ${COMMAND_USAGE[command]}\n`;
|
|
222
|
+
return `usage: ${prefix} ${COMMAND_USAGE[command]}\n${COMMAND_NOTES[command] ? `\n${COMMAND_NOTES[command]}\n` : ""}`;
|
|
207
223
|
return `usage: ${prefix} COMMAND [ARGS]\n\ncommands:\n ${[...BUSINESS_COMMANDS].join("\n ")}\n`;
|
|
208
224
|
}
|
|
209
225
|
function parse(args) {
|
|
@@ -363,12 +379,33 @@ async function submitBuild(api, manifest, build) {
|
|
|
363
379
|
if (build.wait) {
|
|
364
380
|
if (!result || typeof result !== "object" || !result.job_id)
|
|
365
381
|
throw new Error("server build did not return a job_id to wait for");
|
|
366
|
-
result = await waitForJob(api, String(result.job_id), build.pollInterval, build.waitTimeout);
|
|
382
|
+
result = withBuildArtifact(await waitForJob(api, String(result.job_id), build.pollInterval, build.waitTimeout));
|
|
367
383
|
if (["failed", "cancelled"].includes(result.status))
|
|
368
384
|
exitCode = 1;
|
|
369
385
|
}
|
|
370
386
|
return { result, exitCode };
|
|
371
387
|
}
|
|
388
|
+
// A build Job's top-level manifest_hash identifies the submitted (registered)
|
|
389
|
+
// manifest. The published DatasetArtifact is keyed by the resolved contract hash,
|
|
390
|
+
// so surface it additively as `artifact` for downloads and dataset bindings.
|
|
391
|
+
function withBuildArtifact(job) {
|
|
392
|
+
if (!job || typeof job !== "object" || Array.isArray(job) || job.status !== "succeeded")
|
|
393
|
+
return job;
|
|
394
|
+
const resolved = job.resolved_manifest;
|
|
395
|
+
if (!resolved || typeof resolved !== "object" || !resolved.manifest_hash)
|
|
396
|
+
return job;
|
|
397
|
+
return {
|
|
398
|
+
...job,
|
|
399
|
+
artifact: {
|
|
400
|
+
project: resolved.project ?? job.manifest?.project ?? "default",
|
|
401
|
+
dataset_id: resolved.dataset_id ?? job.manifest?.dataset_id ?? null,
|
|
402
|
+
dataset_version: resolved.dataset_version ?? job.manifest?.dataset_version ?? null,
|
|
403
|
+
manifest_hash: resolved.manifest_hash,
|
|
404
|
+
artifact_uri: resolved.artifact_uri ?? null,
|
|
405
|
+
row_count: resolved.row_count ?? null,
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
372
409
|
async function waitForJob(api, jobId, pollInterval, timeoutSeconds) {
|
|
373
410
|
if (!(pollInterval > 0))
|
|
374
411
|
throw new Error("job poll interval must be positive");
|
|
@@ -559,6 +596,21 @@ async function runBusinessCli(argv, output = { write: text => process.stdout.wri
|
|
|
559
596
|
actor: take(rest, "--actor") || "anonymous", reason: take(rest, "--reason", ""), idempotency_key: take(rest, "--idempotency-key"),
|
|
560
597
|
});
|
|
561
598
|
}
|
|
599
|
+
else if (options.command === "get-parameter-freshness") {
|
|
600
|
+
const name = positional(rest, "parameter name");
|
|
601
|
+
const version = positional(rest, "parameter version");
|
|
602
|
+
const project = take(rest, "--project", "default");
|
|
603
|
+
const params = {};
|
|
604
|
+
const lookback = take(rest, "--lookback");
|
|
605
|
+
const end = take(rest, "--end");
|
|
606
|
+
if (lookback !== undefined)
|
|
607
|
+
params.lookback = lookback;
|
|
608
|
+
if (end !== undefined)
|
|
609
|
+
params.end = end;
|
|
610
|
+
if (boolean(rest, "--online"))
|
|
611
|
+
params.online = true;
|
|
612
|
+
result = await client(options).get(`/parameters/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}/freshness`, params);
|
|
613
|
+
}
|
|
562
614
|
else if (/^get-(parameter|feature|feature-set)-lifecycle$/.test(options.command)) {
|
|
563
615
|
const name = positional(rest, "resource name");
|
|
564
616
|
const version = positional(rest, "resource version");
|
|
@@ -730,7 +782,7 @@ async function runBusinessCli(argv, output = { write: text => process.stdout.wri
|
|
|
730
782
|
const jobId = positional(rest, "job id");
|
|
731
783
|
const poll = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
|
|
732
784
|
const rawTimeout = take(rest, "--wait-timeout-seconds");
|
|
733
|
-
result = await waitForJob(client(options), jobId, poll, rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds"));
|
|
785
|
+
result = withBuildArtifact(await waitForJob(client(options), jobId, poll, rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds")));
|
|
734
786
|
if (["failed", "cancelled"].includes(result.status))
|
|
735
787
|
exitCode = 1;
|
|
736
788
|
}
|
|
@@ -868,6 +920,8 @@ async function runBusinessCli(argv, output = { write: text => process.stdout.wri
|
|
|
868
920
|
business_domain_id: take(rest, "--business-domain-id"),
|
|
869
921
|
scenario_id: take(rest, "--scenario-id"),
|
|
870
922
|
q: take(rest, "--q", take(rest, "-q")),
|
|
923
|
+
sort_by: take(rest, "--sort-by"),
|
|
924
|
+
sort_order: take(rest, "--sort-order"),
|
|
871
925
|
};
|
|
872
926
|
boolean(rest, "--all");
|
|
873
927
|
result = await listCollection(client(options), "/deployments", params, rest);
|
|
@@ -1074,6 +1128,36 @@ async function runBusinessCli(argv, output = { write: text => process.stdout.wri
|
|
|
1074
1128
|
const suffix = options.command.endsWith("-metrics") ? "/metrics" : options.command.endsWith("-artifacts") ? "/artifacts" : "";
|
|
1075
1129
|
result = await client(options).get(`/evaluation-results/${encodeURIComponent(resultId)}${suffix}`);
|
|
1076
1130
|
}
|
|
1131
|
+
else if (options.command === "download-model-artifact" || options.command === "download-evaluation-result-artifact") {
|
|
1132
|
+
const model = options.command === "download-model-artifact";
|
|
1133
|
+
const resourceId = positional(rest, model ? "model artifact id" : "evaluation result id");
|
|
1134
|
+
const outDir = take(rest, "--out-dir");
|
|
1135
|
+
const project = take(rest, "--project");
|
|
1136
|
+
const force = boolean(rest, "--force");
|
|
1137
|
+
if (!outDir)
|
|
1138
|
+
throw new Error("--out-dir is required");
|
|
1139
|
+
const files = [];
|
|
1140
|
+
while (rest.includes("--file"))
|
|
1141
|
+
files.push(take(rest, "--file"));
|
|
1142
|
+
const api = client(options);
|
|
1143
|
+
let index;
|
|
1144
|
+
let owner = project;
|
|
1145
|
+
if (model) {
|
|
1146
|
+
const record = await api.get(`/model-artifacts/${encodeURIComponent(resourceId)}`);
|
|
1147
|
+
if (project !== undefined && record.project !== project)
|
|
1148
|
+
throw new Error(`model artifact ${resourceId} belongs to project ${JSON.stringify(record.project)}, not ${JSON.stringify(project)}`);
|
|
1149
|
+
index = record.files;
|
|
1150
|
+
owner = record.project;
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
const listing = await api.get(`/evaluation-results/${encodeURIComponent(resourceId)}/artifacts`);
|
|
1154
|
+
if (!Array.isArray(listing.items))
|
|
1155
|
+
throw new Error("evaluation result artifact listing has no items");
|
|
1156
|
+
index = Object.fromEntries(listing.items.map((item) => [String(item.name), String(item.sha256)]));
|
|
1157
|
+
}
|
|
1158
|
+
const downloaded = await api.downloadLifecycleArtifactFiles(model ? "model-artifacts" : "evaluation-results", resourceId, index, files, resolvedPath(outDir), { force, project });
|
|
1159
|
+
result = { [model ? "model_artifact_id" : "result_id"]: resourceId, ...downloaded, project: owner ?? null };
|
|
1160
|
+
}
|
|
1077
1161
|
else if (/^(get-evaluation-config|get-metric-definition)$/.test(options.command)) {
|
|
1078
1162
|
const name = positional(rest, "resource name");
|
|
1079
1163
|
const version = positional(rest, "resource version");
|
|
@@ -1137,7 +1221,7 @@ async function listCollection(api, endpoint, params, rest) {
|
|
|
1137
1221
|
throw new Error("offset must be >= 0");
|
|
1138
1222
|
return api.get(endpoint, { ...params, limit, offset });
|
|
1139
1223
|
}
|
|
1140
|
-
module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForDeployment, waitForJob };
|
|
1224
|
+
module.exports = { BUSINESS_COMMANDS, COMMAND_NOTES, withBuildArtifact, fetchAll, listCollection, parse, runBusinessCli, usage, waitForDeployment, waitForJob };
|
|
1141
1225
|
if (require.main === module) {
|
|
1142
1226
|
runBusinessCli(process.argv.slice(2)).then((code) => { process.exitCode = code; }).catch((error) => {
|
|
1143
1227
|
process.stderr.write(`error: ${error.message}\n`);
|