在 Python 算法里,main.py 中的装饰器和 Params 数据类就是接口,文件里其余的部分只是具体工作。把两者分开,平台才能不运行你的代码,就在编辑器里显示端口、检查工作流的连接、建议版本号。
保存时,平台静态读取接口,并随版本冻结为 algorithm.yaml。运行时,SDK 会按装饰器重新构建接口,如果和冻结的不一致就拒绝启动(ALGORITHM_MANIFEST_DRIFT)。
装饰器参数
| 参数 | 必填 | 规则 |
|---|---|---|
name、title | 是 | name 由小写字母、数字和 - 组成,长 2 到 63,以字母开头,在算法的整个生命周期内不变。团队算法名称不能以 ef- 或 embodiflow- 开头。 |
description | 否 | 显示在算法库中。 |
level | 否 | check 和 enrich:recording(默认)或 episode。 |
reads | 录制级 check | raw 或 recording(默认)。 |
inputs | 否 | 额外的 features 输入:Input(requires=[…], optional=False)。名称由小写字母、数字和 _ 组成,最长 32,且不能是数据类型名、默认端口名或 run 的参数名。 |
features | enrich | 至少一个 Feature(...)。 |
checks | check;任何会写出质检结果的类别 | Check(id, title, description="")。 |
labels | segment | 任务片段携带的标签键:{键: 语义类型}。 |
requires | 否 | 数据必须具备的事实,见下文。 |
params | 否 | 一个数据类,每个字段都是 param(default, …)。 |
resources | 否 | 见下文。 |
requirements | 否 | pip 依赖字符串,只用二进制 wheel,保存时锁定。 |
retries、cache | 否 | 默认:暂时性错误重试一次,开启缓存。 |
services | 否 | 仅内置算法:["vlm"]。 |
privacy | 否 | 仅内置算法:"redacts" 或 "numbers_only"。 |
代码里不写 version。保存时,平台会根据接口的变化建议下一个版本。
数据类型
端口带有八种数据类型之一,只有类型相同的端口才能连接。
| 类型 | 含义 |
|---|---|
raw | 一条录制上传的文件。 |
recording | 标准录制:LeRobot v3 目录树加 meta/embodiflow/recording.json。 |
features | 关于录制或其任务片段的新数值。 |
checks | 判断结果,每条带一个状态。 |
episodes | 带任务文字的时间段。 |
version | 一个数据集版本。 |
selection | 逐个任务片段的决定:保留、划分、任务。 |
artifact | 可下载的文件。 |
Feature
特征的声明方式是 Feature(name, type, per, dtype=…, shape=…, unit=…, frame=…, names=…, source=…, describe=…)。
| 字段 | 含义 |
|---|---|
name | 短名称。平台会以团队专属的名称保存。 |
type | 下表中的语义类型。 |
per | frame、recording、episode 或 file。 |
dtype | float32(默认)、float64、int8、int16、int32、int64、uint8 或 uint16。 |
shape | 单个值的形状。数组类型有固定的默认形状。 |
unit、frame | 几何类型必填,按 file 保存的除外。 |
names | 各分量的名称,例如关节名。 |
source | recorded、imported、estimated、computed(默认)或 retargeted。 |
describe | 补充说明,例如重定向动作的 {"targetRig": "tron2av3"}。 |
参数
参数是工作流不改代码就能调整的设置。Params 的每个字段都是 param(default, minimum=…, maximum=…, enum=…, title=…, description=…, widget=…),类型来自注解:int、float、str、bool,或者字符串、整数、浮点数的列表。
列表字段用字面量列表作默认值,还可以使用 items_enum、min_items 和 max_items。控件 tags 接受自由字符串,multiselect 需要 items_enum。工作流在 with 里设置取值,平台会按这些限制检查。
语义类型
特征的语义类型告诉下一步这些数字是什么意思,编辑器正是靠它判断哪些输出可以连到要求 keypoints3d.hand21 之类类型的输入。注册表是封闭的,团队按原样使用这些类型。
| 类型 | 含义 | 形状或取值 |
|---|---|---|
scalar | 一个数 | [1] |
vector | 一组数 | [n] |
label.bool | 是否标签 | bool |
label.category | 声明集合中的一个 | 字符串;describe.values |
label.text | 自由文本 | 字符串 |
spans | 任务片段或录制内的时间段 | { startS, endS, label } 列表 |
embedding | 嵌入向量 | [n];describe.model |
pose.se3 | 位置加四元数 | [7]:x y z qx qy qz qw;unit、frame |
keypoints3d.hand21 | 3D 手部 21 个关键点,MediaPipe 顺序 | [63];unit、frame |
skeleton.hand21 | 21 个手部关节,各带 6 自由度位姿,MediaPipe 顺序 | [147] = 21 x (x y z qx qy qz qw);unit、frame |
skeleton.body22 | 22 个身体关节,各带 6 自由度位姿,SMPL 顺序(骨盆在前) | [154] = 22 x (x y z qx qy qz qw);unit、frame |
keypoints2d | 2D 图像关键点 | [2k];frame 为相机 |
confidence | 每个样本 0 到 1 的置信度 | [n] |
calibration.stereo | 校正后的双目外参 | { R[9], T[3] } |
calibration.cam_imu | 相机到 IMU 的变换和时间偏移 | { R[9], T[3], dtS } |
depth.video | 16 位深度视频 | 文件 |
mask.video | 逐帧掩码视频 | 文件 |
video.preview | 单路相机视频的轻量浏览器预览副本,帧不变 | 文件,embodiflow-preview/v1 |
ros2.mcap | ROS 2 MCAP 文件(TF 树、图像、点云、标记) | 文件 |
action.joint | 关节空间动作 | [n];names |
action.ee_pose | 末端执行器动作 | [7],带夹爪时为 [8] |
json | 任意小型 JSON | 对象,最大 1 MiB;requires 只能用 name:<特征名> 匹配 |
事实与 requires
事实是平台对数据的了解,全部从数据本身推导,不由人填写。你的代码通过 ctx.facts 读取它们,requires 则用它们判断算法到底适不适用。
| 事实 | 类型 | 来源 |
|---|---|---|
kind | robot 或 human | 项目 |
format | 字符串:mcap、lerobot、video 或 x-<自有格式> | 上传 |
privacy_required | bool | 录制档案 |
duration_s | 数字 | 先看上传摘要,再看录制,最后看任务片段窗口 |
cameras | 整数 | 先看上传摘要,再看录制 |
stereo | bool | 录制:带内参的双目对 |
imu | bool | 录制:IMU 列 |
action | bool | 录制:存在 action 特征 |
recorded_hand_pose | bool | 录制:录制得到的手部关键点 |
gripper_width | bool | 录制:夹爪宽度列 |
episodes | 整数 | 数据集版本:任务片段数量 |
requires 把事实映射到 true、false、与取值相等的字符串,或 ">=2"、"<600" 这样的比较式。只要有一个条件不成立,步骤就以「不适用」结束:显示为灰色,不算失败。未知的事实永远不满足条件,所以步骤会是「不适用」,而不是靠猜测去运行。你的代码也可以在运行时用 ctx.not_applicable(code, message) 自行决定。
资源
| 键 | 默认值 | 含义 |
|---|---|---|
cpu | 4 | CPU 核数。 |
memory | 8Gi | 内存,例如 8Gi 或 16Gi。 |
disk | 10Gi | ctx.tmp 使用的临时空间。 |
gpu | None | None,或 {"count": 1, "memoryGb": 16}。 |
timeout | 30m | 时长,例如 20m,或 {"perInputMinute": …, "min": …, "max": …}。 |
每个步骤至少获得 4 个 CPU 核和 8Gi 内存,申请 GPU 时至少 8 GB 显存;写得更小也按这个下限运行。资源必须能放进一台机器,申请的 GPU 超过所有资源池时,会被 ALGORITHM_GPU_UNSATISFIABLE 拒绝。
申请 GPU 的算法会自动运行在 CUDA SDK 基础镜像上,已预装 PyTorch 和 CUDA 13,你不需要自己选基础镜像。它的试跑同样使用 GPU,等 GPU 有空余时才开始;没有 GPU 的部署会以 ALGORITHM_SAMPLE_RUN_GPU_UNSUPPORTED 拒绝。
代码如何结束
代码以什么方式结束,就告诉了平台发生了什么,不同的方式会让步骤以不同的状态结束。镜像算法用退出码表达同样的结果。
| Python | 退出码(镜像) | 含义 | 步骤结束为 |
|---|---|---|---|
| return | 0 | 完成。 | 成功 |
ctx.not_applicable(code, msg) | 85 | 这份数据不归我处理。 | 不适用 |
ctx.input_unusable(code, msg) | 87 | 数据已损坏。 | 失败;录制被隔离 |
raise TransientError(msg) | 88 | 请重试:网络问题、GPU 忙。 | 重试,然后失败 |
| 其他任何异常 | 1 | 程序错误。 | 失败;回溯在日志里 |
code 由大写字母、数字和 _ 组成,长 3 到 64。步骤信息的格式是 <code>: <message>。
版本与引用
工作流用 embodiflow/<name>@<major> 或 team/<name>@<major> 引用算法,每次运行都使用该主版本下已保存的最高版本。这样,修复会在下一次运行时自动生效,而破坏性的改动要等你切换到新的主版本。
端口、level、requires、声明的特征、质检项或标签,或者参数的类型发生变化,都属于破坏性改动,需要升主版本;按次版本保存会被 ALGORITHM_INTERFACE_CHANGED 拒绝。已保存的版本永远不变。
run 如何被调用
run 每条录制调用一次;任务片段级每个任务片段调用一次;整理和导出则每个数据集版本调用一次。参数按名称绑定,需要哪个就写哪个,顺序随意。
| 参数 | 得到 |
|---|---|
raw、recording、version | 主输入。 |
episode | 仅任务片段级:正在处理的任务片段。 |
| 额外输入的名称 | 一个 Features 对象;输入可选且缺席时为 None。 |
out | 该类别的写入器。 |
ctx | 上下文:params、facts、recording_id、run_id、step_id、tmp、log、progress(done, total, message)、not_applicable 和 input_unusable。 |
state | setup(ctx) 的返回值。 |
用 setup(ctx) 只加载一次模型。目前平台每个任务调用它一次;以后的版本可能在一个任务里处理许多条目,仍然只调用一次 setup,你的代码不需要任何改动。正因为如此,run 不能在全局变量里保存随条目变化的状态。