浏览器里的 Python 能满足大多数需求,但有时你最好的代码是 C++、Rust,或者自带一整套环境的模型。镜像算法让你把它装进容器带进来。平台把它当作普通步骤对待:同样的类别、同样的端口、对输出同样的检查,只是打包方式不同。
约定很简单:容器从固定的目录读取任务文件和输入,把输出写到固定的目录,然后以一个退出码结束。因为没有装饰器可读,接口要你手写成 algorithm.yaml,embodiflow algorithm push 会把它写进镜像标签 io.embodiflow.algorithm。
容器要做什么
- 读取
/embodiflow/config/job.json。 - 从
/embodiflow/input/<port>/读取输入,向/embodiflow/output/<port>/写出输出,目录布局见下表。 - 可选:在
/embodiflow/output/progress.json里报告进度:{ "done", "total", "message" }。 - 需要拒绝时,写
/embodiflow/output/error.json:{ "code", "message" },code 用大写字母。 - 临时空间用
/embodiflow/tmp/。 - 以下表中的某个退出码结束。
job.json
任务文件告诉容器它正在处理什么:运行、条目、参数、事实,以及每个端口在哪里。下面是一个任务片段级增强步骤的例子。
{
"contract": "embodiflow-runtime/v1",
"category": "enrich", "level": "episode",
"runId": "<uuid>", "stepId": "success", "recordingId": "<uuid>",
"item": { "episodeIndex": 3, "startS": 120.4, "endS": 161.0, "task": "pick up the cup" },
"params": { "threshold": 0.5 },
"facts": { "kind": "human", "stereo": true, "imu": true, "privacy_required": true, "duration_s": 40.6 },
"inputs": { "recording": { "type": "recording", "root": "/embodiflow/input/recording" },
"episodes": { "type": "episodes", "root": "/embodiflow/input/episodes" } },
"outputs": { "features": { "type": "features", "root": "/embodiflow/output/features" } }
}缺席的可选输入不会出现在 inputs 里。
端口根目录下的布局
| 类型 | 布局 | 步骤结束时的校验 |
|---|---|---|
raw | 文件按上传时的相对路径存放。 | 不检查。 |
recording | LeRobot v3 目录树加 meta/embodiflow/recording.json。 | 标准规则 R1 到 R7 和 embodiflow-recording/v1。 |
features | features.json、frames.parquet、episodes.parquet 和 files/**。 | embodiflow-features/v1 |
checks | checks.json 和 evidence/**。 | embodiflow-checks/v1 |
episodes | episodes.json。 | embodiflow-episodes/v1 |
version | version.json 和 urls.json(预签名链接)。 | 仅作输入。 |
selection | selection.json。 | embodiflow-selection/v1 |
artifact | artifact.json 和文件。 | embodiflow-artifact/v1 |
逐帧特征在 frames.parquet 里必须每帧恰好一行。任务片段级的值以 episodeIndex 为键,也就是它在所连接的 episodes 文档中的位置。checks.json 和 episodes.json 里的时间是录制时间线上的秒数。
退出码
| 退出码 | 含义 | 步骤结束为 |
|---|---|---|
| 0 | 完成。 | 成功 |
| 85 | 这份数据不归我处理。 | 不适用 |
| 87 | 数据已损坏。 | 失败;录制被隔离 |
| 88 | 请重试:网络问题、GPU 忙。 | 重试,然后失败 |
| 1 | 程序错误。 | 失败;保留日志 |
以 85 或 87 退出之前,先把 code 和 message 写进 error.json,步骤信息就会显示为 <code>: <message>。
algorithm.yaml
这个文件和 Python 装饰器生成的是同一种文档,模式为 embodiflow-algorithm/v1。下面把第一个算法里的运动强度算法声明成镜像算法:
apiVersion: embodiflow-algorithm/v1
name: motion-energy
title: Motion energy
category: enrich
features:
- name: motion_energy
type: scalar
per: frame
dtype: float32
unit: none
params:
step: { type: integer, default: 1, minimum: 1, title: Frame step }
resources: { cpu: 4, memory: 8Gi, timeout: 30m }先在本地测试,再登记
你不必从空目录开始。embodiflow algorithm init <dir> --category <类别> --lang image 会写出 algorithm.yaml、一个使用 exec 形式 ENTRYPOINT 的 Dockerfile,以及一个已经能写出该类别输出的入口脚本。之后的流程是这样的:
# write my-algo/algorithm.yaml (above) and a Dockerfile by hand
embodiflow algorithm validate my-algo
embodiflow algorithm test my-algo --build --input recording=./rec
docker build -t my-algo my-algo && docker save my-algo -o my-algo.tar
embodiflow algorithm push my-algo --archive my-algo.tar --registry <registry-host>validate 用平台使用的同一个解析器检查 algorithm.yaml。test 在你的机器上运行容器,并按平台的规则检查输出。push 把镜像归档导入团队的镜像仓库,并用固定到摘要的镜像引用登记算法。