Agent 提交与结果查询 API
将模型预测 ZIP 提交到 Benchmark,异步获取评测结果。本接口负责评测已有预测;视频上传与 Pipeline 推理使用 Playground 的独立接口。
当前规范 · 选择评测方式 · 准备 ZIP · 上传与预检查 · 完整请求示例 · 模型与 Pipeline · 提交与重试 · 结果解释 · Python 流程 · 升级与错误
OpenAPI JSON · 展开当前版本的 Markdown · HOT3D 逐帧格式 · Test40 逐帧格式
1. 先确认目标和当前规范
服务地址使用你实际获授权的实例 origin。本文路径均相对该 origin;以下 curl 示例需要 curl 与 jq,并假定已经设置 EGO_URL。开发者先从本人 checkout 执行 ./deploy/run-dev target --service benchmark,核对 /api/health 的 service=benchmark、environment、instance_id、build;生产提交按实际使用授权执行。当前接口没有应用层登录,幂等键与同源检查不构成身份认证。
# 以下命令在同一个 Bash 会话执行;失败时停止,先修正问题。
set -eu
# EGO_URL 为你确认的服务地址,不包含末尾斜杠。
curl --fail-with-body -sS "$EGO_URL/api/health"
curl --fail-with-body -sS "$EGO_URL/api/hot3d/protocols" > protocols.json
jq '.versions[] | {protocol_id, ready, accepting_submissions}' protocols.json
从 versions[].protocol_id 选择题集,再请求 /api/submission-contract?protocol=...。不要依赖默认协议或永久固定当前的题集列表。每次准备新提交都刷新规范;使用同键恢复已经接收的请求时保留原 JSON,不擅自替换字段。
以下身份和后面的完整请求示例由服务的 submission_contract 定义生成,HTML、Markdown、OpenAPI 使用同一来源,随发布版本更新。它们说明当前接受什么格式;实时 ready / accepting_submissions 仍需查询规范接口,页面展示不代表当前允许提交。
| 题集 | 当前 split_id | ZIP schema_version | Case 数 | 每个 Case 的帧数 |
|---|---|---|---|---|
| HOT3D | test200-v3 | hot3d-pose-prediction-v4 | 1–200 | 150 |
| Test40 | test40-v1 | reinterhand-pose-prediction-v1 | 1–40 | 按原始 inputs.json 的完整 frame_count |
HOT3D 当前身份
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"dataset_id": "hot3d-quest3-mano21-test200-v3",
"split_id": "test200-v3",
"schema_version": "hot3d-pose-prediction-v4",
"input_manifest_sha256": "b3a405063e0158b47fa19c3ad4bf0acdd65785a0e9acb36f6d045412e3cbf1ef",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6"
}
Test40 当前身份
{
"protocol": "reinterhand-ego-mano21-pose-v1",
"dataset_id": "reinterhand-ego-envmap-per-segment-mano21-test40-v1",
"split_id": "test40-v1",
"schema_version": "reinterhand-pose-prediction-v1",
"input_manifest_sha256": "66546c045e09a197322aae3388822aa56faf9af6c4eec8b37d871320baad4e2b",
"contract_id": "3ecb48a0d8f2c2013efac41fb51550b7d15e9982eb202e8e1573246e4703ffe7"
}
需要区分四个值:protocol 是评测协议;split_id / dataset_id 是冻结题集身份;schema_version 是 ZIP 中 submission.json 的格式;下载包文件名的 vN 是交付版本。它们不要求使用相同版本号。contract_id 绑定当前提交要求,提交前以实时接口返回为准。
2. 选择评测方式
HTTP 的 evaluation_profile 描述预测类型和实际使用的输入相机。它必须与 ZIP 中 prediction_type、inference_camera_ids 一致;更改请求 JSON 不会将已有 3D ZIP 转成 2D,也不会将单目结果变成双目。不能把没有参与推理的相机填进去。
| 场景 / 完整请求 | prediction_type | inference_camera_ids |
|---|---|---|
| HOT3D · 左目 3D · 1201-1 | 3d | ["1201-1"] |
| HOT3D · 右目 3D · 1201-2 | 3d | ["1201-2"] |
| HOT3D · 双目 3D · 1201-1 + 1201-2 | 3d | ["1201-1", "1201-2"] |
| HOT3D · 左目 2D · 1201-1 | 2d | ["1201-1"] |
| HOT3D · 右目 2D · 1201-2 | 2d | ["1201-2"] |
| HOT3D · 双目 2D · 1201-1 + 1201-2 | 2d | ["1201-1", "1201-2"] |
| Test40 · Ego 单目 3D · ego | 3d | ["ego"] |
| Test40 · Ego 单目 2D · ego | 2d | ["ego"] |
坐标和评分范围
• HOT3D 3D:无论左目、右目还是双目推理,输出都统一到左相机 camera:1201-1:native,单位米。右目模型的输出需要先按题集标定转换,不能直接当作右相机坐标提交。
• HOT3D 2D:所选相机的扶正原图像素,坐标声明 upright、单位 px;当前原图 1024×1280。双目要分别提交两路视图,不是把两路点拼在一起。
• Test40:仅 ego。3D 为 camera:ego:native / 米;2D 为 image:ego:native / px,当前原图 1024×1024。相机和每帧标定以输入包为准,不能套用 HOT3D 的旋转或帧数。
• 3D 提交计算空间指标,并由平台按标定投影计算 2D 指标;纯 2D 提交计算 2D 及公共指标,不补造 3D 分数。详见结果解释。
• expected_count 是 ZIP 里实际选择的 Case 数,不是采样帧数,也不表示让服务器截取前 N 个。子集在打包前用 Case ID 选择;每个 Case 仍需完整帧序列。
• leaderboard_type 独立取 formal 或 temporary,默认 temporary。正式榜也允许合法子集,不按 Case 数自动决定榜单;不同题集、Case 集和评测设置不混排名。
3. 准备当前题集的预测 ZIP
从规范响应的 links.input_package 获取输入包索引 JSON,再读取 downloads.full.url(完整输入)或 downloads.starter.url(工具包)。该接口本身不是 ZIP。包下载后保留原始 inputs.json 的字节;不要裁切或重写它,也不要用 /api/hot3d/inputs?case_id=... 的展示响应代替原清单。
# EGO_PROTOCOL 从上一步 versions[].protocol_id 中选择。
curl --fail-with-body -sS --get "$EGO_URL/api/submission-contract" \
--data-urlencode "protocol=$EGO_PROTOCOL" > contract.json
jq -e '.ready and .accepting_submissions' contract.json
curl --fail-with-body -sS "$EGO_URL$(jq -r '.links.input_package' contract.json)" > package.json
jq '{ready, downloads}' package.json
# 确认需要下载完整包后执行;可能为数十 GB。
curl --fail-with-body -L "$EGO_URL$(jq -r '.downloads.full.url' package.json)" -o inputs.zip
# 使用当前服务的工具,不沿用旧下载包里可能过期的脚本。
curl --fail-with-body -sS "$EGO_URL$(jq -r '.links.submission_tool' contract.json)" -o submission_tool.py
解压 inputs.zip,在模型侧使用原始视频/图像与相机参数完成推理。两个题集的提交工具都有 template、verify、pack 三个命令。下面示例分别创建 HOT3D 左目 3D 和 Test40 Ego 2D 模板;运行前下载对应协议的工具,设置原始清单路径 INPUTS 和真实 CASE_ID。两个模板示例任选其一。
# HOT3D:一个 Case,左目 3D。
python submission_tool.py template --inputs "$INPUTS" --submission prediction-hot3d \
--type 3d --cameras 1201-1 --case-id "$CASE_ID"
# Test40:一个 Case,Ego 2D;此时 submission_tool.py 必须是 Test40 工具。
python submission_tool.py template --inputs "$INPUTS" --submission prediction-test40 \
--type 2d --cameras ego --case-id "$CASE_ID"
--case-id 可重复传入;省略选择全题集。新模板目录必须不存在。将模型预测填入 JSONL,填写实际 prediction_adapter_id 和 pipeline_version,保留输入身份及 joint_names 语义映射。随后选择对应目录验证和打包:
python submission_tool.py verify --inputs "$INPUTS" --submission "$PREDICTION_DIR"
# 仅当已经填入真实模型输出时使用 --model-output;目标 ZIP 必须不存在。
python submission_tool.py pack --inputs "$INPUTS" --submission "$PREDICTION_DIR" \
--output prediction.zip --model-output
模板默认是 gt_derived_fixture,不能把空模板或 GT 衍生联调数据标成 model_output。fixture 只在历史区展示,不能提交正式榜;真实模型输出可按用途选择正式或临时榜。
prediction.zip
├── submission.json
└── predictions/
├── <case_id_1>.jsonl
└── <case_id_2>.jsonl
允许唯一一层外目录。不要将 inputs.json、GT、原视频或其他文件混装进预测 ZIP。
• submission.json 保存 schema_version、protocol_id、dataset_id、input_manifest_sha256、case_ids、joint_names、prediction_type、inference_camera_ids、coordinate_frame、unit 等;由当前工具生成,Test40 还声明 split_id。这里的 protocol_id 对应 HTTP 请求的 protocol。
• 3D 每帧直接包含左右手及 points_3d;2D 每帧在 views 下按声明的相机分别包含左右手及 points_2d。每只有效手必须是 21 个有限数值点;3D 不混入 2D 字段,2D 不混入 3D 字段。
• 缺失手显式写 status: "missing" 和对应点字段 null,保留该帧及双手字段。不能因未检测到手就删帧。HOT3D 为逐 Case 完整 150 帧;Test40 按清单的实际 frame_count,不能统一裁成 150 帧。
4. 上传与预检查
POST /api/uploads?filename=prediction.zip 的正文为 ZIP 原始二进制,不是 multipart、Base64 或 JSON。单包 1 byte–2 GiB,准确 Content-Length,不支持 chunked。下面 curl 从文件上传,无需将整个包读进 Python 内存。
curl --fail-with-body -sS -H 'Content-Type: application/zip' \
--data-binary @prediction.zip "$EGO_URL/api/uploads?filename=prediction.zip" > upload.json
jq '{upload_id, filename, size_bytes}' upload.json
HTTP 201 返回 upload_id、filename、size_bytes、sha256。上传成功只代表保存完成;Agent 使用 upload_id,不依赖响应里的内部 archive_path,也不直接访问服务存储路径。
从下一节选取与 ZIP 对应的完整请求,保存为 submit.json,将占位 upload_id 替换为真实返回值。确认已按当前输入准备后,填写最新 contract_id,不要通过改 split_id / schema_version 强行升级旧预测。
# submit.json 已选对题集、类型、相机、Case 数和模型字段。
jq --arg upload_id "$(jq -r '.upload_id' upload.json)" \
--arg contract_id "$(jq -r '.contract_id' contract.json)" \
'.upload_id=$upload_id | .contract_id=$contract_id' submit.json > request.json
curl --fail-with-body -sS -H 'Content-Type: application/json' \
--data-binary @request.json "$EGO_URL/api/hot3d/inspect" > inspect.json
jq '{selection, evaluation_profile, fixture, contract_id}' inspect.json
两套题集共用 /api/hot3d/inspect,按 protocol / split_id 分派。此接口只需题集、来源、expected_count、contract_id、evaluation_profile;可以发送完整请求,但这里不验证全部模型/Pipeline catalog 字段。HTTP 200 返回选择集、实际 profile、fixture、当前 contract_id 和冻结身份。预检查不等于逐帧评分成功,入队与 worker 会继续校验。
5. 不同评测的完整请求示例
以下 JSON 均用于 POST /api/evaluations,最大正文 1 MiB,Content-Type 为 application/json;成功 HTTP 202。所有示例默认创建模型版本、未选择业务 Pipeline。upload_id 是占位值,模型名称/版本说明请按实际填写;contract_id 仅代表此页面渲染时的版本,提交前刷新实时规范。
示例分别展示全题集正式榜和单 Case 临时榜,两者均可按实际需求调整。expected_count 必须与对应 ZIP 完全一致。evaluation_profile 本身可省略并从 ZIP 读取;Agent 显式提供时更易审计,但必须与 ZIP 一致。坐标和 schema_version 属于 ZIP 元数据,不是可通过此请求覆盖的参数。
HOT3D · 左目 3D · 1201-1
本例选择 1 个 Case、临时榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"split_id": "test200-v3",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 1,
"evaluation_profile": {
"prediction_type": "3d",
"inference_camera_ids": [
"1201-1"
]
},
"display_name": "My Hand Model",
"version": "hot3d-3d-1201-1-v1",
"version_description": "HOT3D · 左目 3D · 1201-1,请替换为实际模型和推理设置说明",
"leaderboard_type": "temporary"
}
HOT3D · 右目 3D · 1201-2
本例选择 1 个 Case、临时榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"split_id": "test200-v3",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 1,
"evaluation_profile": {
"prediction_type": "3d",
"inference_camera_ids": [
"1201-2"
]
},
"display_name": "My Hand Model",
"version": "hot3d-3d-1201-2-v1",
"version_description": "HOT3D · 右目 3D · 1201-2,请替换为实际模型和推理设置说明",
"leaderboard_type": "temporary"
}
HOT3D · 双目 3D · 1201-1 + 1201-2
本例选择 200 个 Case、正式榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"split_id": "test200-v3",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 200,
"evaluation_profile": {
"prediction_type": "3d",
"inference_camera_ids": [
"1201-1",
"1201-2"
]
},
"display_name": "My Hand Model",
"version": "hot3d-3d-1201-1-1201-2-v1",
"version_description": "HOT3D · 双目 3D · 1201-1 + 1201-2,请替换为实际模型和推理设置说明",
"leaderboard_type": "formal"
}
HOT3D · 左目 2D · 1201-1
本例选择 1 个 Case、临时榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"split_id": "test200-v3",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 1,
"evaluation_profile": {
"prediction_type": "2d",
"inference_camera_ids": [
"1201-1"
]
},
"display_name": "My Hand Model",
"version": "hot3d-2d-1201-1-v1",
"version_description": "HOT3D · 左目 2D · 1201-1,请替换为实际模型和推理设置说明",
"leaderboard_type": "temporary"
}
HOT3D · 右目 2D · 1201-2
本例选择 1 个 Case、临时榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"split_id": "test200-v3",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 1,
"evaluation_profile": {
"prediction_type": "2d",
"inference_camera_ids": [
"1201-2"
]
},
"display_name": "My Hand Model",
"version": "hot3d-2d-1201-2-v1",
"version_description": "HOT3D · 右目 2D · 1201-2,请替换为实际模型和推理设置说明",
"leaderboard_type": "temporary"
}
HOT3D · 双目 2D · 1201-1 + 1201-2
本例选择 1 个 Case、临时榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "local-hot3d-quest3-mano21-pose-v1",
"split_id": "test200-v3",
"contract_id": "294efdf809b02422087de511f0900063fb2c7abcab403f3dcd14b846b6b020e6",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 1,
"evaluation_profile": {
"prediction_type": "2d",
"inference_camera_ids": [
"1201-1",
"1201-2"
]
},
"display_name": "My Hand Model",
"version": "hot3d-2d-1201-1-1201-2-v1",
"version_description": "HOT3D · 双目 2D · 1201-1 + 1201-2,请替换为实际模型和推理设置说明",
"leaderboard_type": "temporary"
}
Test40 · Ego 单目 3D · ego
本例选择 40 个 Case、正式榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "reinterhand-ego-mano21-pose-v1",
"split_id": "test40-v1",
"contract_id": "3ecb48a0d8f2c2013efac41fb51550b7d15e9982eb202e8e1573246e4703ffe7",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 40,
"evaluation_profile": {
"prediction_type": "3d",
"inference_camera_ids": [
"ego"
]
},
"display_name": "My Hand Model",
"version": "test40-3d-ego-v1",
"version_description": "Test40 · Ego 单目 3D · ego,请替换为实际模型和推理设置说明",
"leaderboard_type": "formal"
}
Test40 · Ego 单目 2D · ego
本例选择 1 个 Case、临时榜;请按 ZIP 实际 Case 数和榜单用途修改。
{
"protocol": "reinterhand-ego-mano21-pose-v1",
"split_id": "test40-v1",
"contract_id": "3ecb48a0d8f2c2013efac41fb51550b7d15e9982eb202e8e1573246e4703ffe7",
"upload_id": "<上传响应的 upload_id>",
"expected_count": 1,
"evaluation_profile": {
"prediction_type": "2d",
"inference_camera_ids": [
"ego"
]
},
"display_name": "My Hand Model",
"version": "test40-2d-ego-v1",
"version_description": "Test40 · Ego 单目 2D · ego,请替换为实际模型和推理设置说明",
"leaderboard_type": "temporary"
}
6. 模型、Pipeline 和复用的参数组合
以下均为“替换上一节完整请求中对应字段组”的片段,不是独立完整提交。不要同时保留新建字段和已有 ID 来猜测优先级。
模型与模型版本(必选)
GET /api/models 返回 models 和 model_versions 两个数组,按 model_id 关联,从服务响应取真实 ID,不按名称拼接 ID。
{"model_id": "<已有模型 ID>", "model_version_id": "<该模型的已有版本 ID>"}
{"display_name": "My Hand Model", "version": "v2", "version_description": "本次训练数据和推理设置的变化"}
{"model_id": "<已有模型 ID>", "version": "v3", "version_description": "该模型新增版本的变化"}
已有模型/版本组合不再传 display_name、version、version_description。新模型或新模型版本的 version_description 必须非空、最多 1000 字符。已有版本说明不能通过提交覆盖,参数或说明变化应新建版本。
业务 Pipeline(整组可省略)
它用于标识榜单上的推理流水线版本,不触发远端推理,也不决定评分方式。与 ZIP 中必须声明的来源 pipeline_version 是不同层次,不做自动名称映射。GET /api/pipelines 返回 pipelines 和 pipeline_versions,按 pipeline_id 关联。
{"pipeline_id": "<已有 Pipeline ID>", "pipeline_version_id": "<该 Pipeline 的已有版本 ID>"}
{"pipeline_name": "My Pipeline", "pipeline_version": "v1", "pipeline_version_description": "检测、跟踪和后处理配置"}
{"pipeline_id": "<已有 Pipeline ID>", "pipeline_version": "v2", "pipeline_version_description": "可选的新版本说明"}
选 Pipeline 必须选版本;pipeline_version_description 选填,最多 1000 字符。选已有版本不传新版本字段,也不能覆盖原说明。不选 Pipeline 时删除整组字段。
复用当前格式的已归档预测
将完整请求中的 upload_id 删除,换成下面字段,其他题集、模型、榜单、expected_count 和 profile 按实际填写:
{"reuse_result_id": "<当前实例中已完成结果的 result_id>"}
必须保持原来的完整 Case 选择、预测类型、推理相机,并满足最新 schema、题集及输入身份。不能同时传 upload_id,不能借复用重标旧题集、截取子集或切换 2D/3D。使用新的幂等键会创建新任务;它与重放同一个任务请求不同。
7. 入队、幂等和失败恢复
# 为一次逻辑提交生成一次键,保存 request.json 与此文件;网络重试复用它们。
python - <<'PYKEY'
import pathlib, uuid
key_file = pathlib.Path('request-key.txt')
try:
with key_file.open('x') as f:
f.write(str(uuid.uuid4()) + '\n')
except FileExistsError:
pass # 读取已有键;本次 request.json 也必须保持原值
PYKEY
curl --fail-with-body -sS -H 'Content-Type: application/json' \
-H "Idempotency-Key: $(cat request-key.txt)" \
--data-binary @request.json "$EGO_URL/api/evaluations" > accepted.json
jq -r '.task.request_id' accepted.json
同一个键必须使用完全相同的 JSON 值,对象键顺序可变。换 upload_id、Case 数、字段或版本均返回 409 IDEMPOTENCY_CONFLICT;不要在网络超时后重新生成键。响应含 idempotent_replay=true 表示取回原任务,不会重新运行。
键允许 1–128 位字母、数字或 ._:-,推荐随机 UUID。服务重启后仍可从队列记录恢复,记录保留期间没有自动过期;无此 header 不保证提交去重。上传本身没有幂等性,上传超时后不要无限重复,也不要由 Agent 删除服务器缓存。
已明确失败的任务可调用 /api/tasks/{request_id}/retry,JSON {};重新检查当前题集、格式和来源。若参数要变或协议已升级,应准备新的 ZIP / 请求并使用新键。不要删除旧本地状态来恢复一次不确定是否已接收的提交。
8. 查询状态与理解结果
REQUEST_ID=$(jq -r '.task.request_id' accepted.json)
curl --fail-with-body -sS "$EGO_URL/api/tasks/$REQUEST_ID/status" > status.json
jq '{task, terminal, poll_after_seconds, result_url}' status.json
# 仅当 task.status=completed 后使用响应给出的 result_url:
if jq -e '.terminal and .task.status=="completed"' status.json > /dev/null; then
curl --fail-with-body -sS "$EGO_URL$(jq -r '.result_url' status.json)" > result.json
fi
非终态包括 queued、preparing、running、archiving;终态包括 completed、failed、cancelled、input_invalid。状态可能跃迁,不保证看见每个阶段。非终态按 poll_after_seconds 查询(当前通常 5 秒),网络错误或 503 采用有限退避 10、20、30 秒并设置总超时;每任务只保留一个在途轮询。其他 4xx 先纠正请求,不无限重试。
request_id 用于任务查询;result_id 用于结果,完成后按 result_url 读取,不互换、不猜测。不要用全量榜单、任务详情或大型 Case 证据代替轻量 /status。失败看 task.error;HTTP 请求成功不代表评测完成。取消使用 POST /api/tasks/{request_id}/cancel,JSON {},归档中不可取消。
2D 与 3D 返回的指标
| 提交类型 | 结果范围 | 读取注意 |
|---|---|---|
| 3D | 空间精度、骨长变化、3D 时序稳定性、覆盖率、左右手错标率及投影后的 4 项 2D 指标,当前共 13 项 | 3D 主指标 PCK_AUC21;Abs-MAE X/Y/Z 是独立诊断 |
| 纯 2D | EPE_2D21、PCK_AUC_2D21、MPJPA_2D21、ACC_2D21、PREDICTION_COVERAGE、HANDEDNESS_2D_MATCHED_ERROR_RATE,当前 6 项 | 3D 空间指标不在 metrics 中,不能当成 0 |
metric_status 的每一项是对象,先读 metric_status[指标名].status。可能是 ok、no_samples、failed;历史缺字段或指标不存在也要保留缺失状态。不要假定所有不适用项都会返回 not_applicable。单目跨视角诊断不适用,但左右手错标率本身仍可计算。
{
"metrics": {"PCK_AUC_2D21": 92.5, "EPE_2D21": 4.2},
"metric_status": {
"PCK_AUC_2D21": {"status": "ok"},
"EPE_2D21": {"status": "ok"}
}
}
上述是说明读取方式的虚构片段,不是任何真实模型结果。空间距离为 mm、二维距离为 px,MPJPA_2D21 / ACC_2D21 为 px/frame²。PCK/FT AUC、覆盖率和错标率等原始值已按 0–100 返回,不再统一乘 100。MPJPA_2D21 是轨迹二阶变化幅度,不是越低越好,应结合 GT 幅度参考。
完整结果同时包含 dataset、selection、evaluation_profile、模型和 Pipeline 信息。比较前核对题集、Case 集、类型、相机及评分条件;不拼接多次运行的最好指标,不将 no_samples、failed、null 或缺失值补零。
9. 可执行流程示例(Python 标准库)
适用于已准备好的小预测包:将第 5 节的请求保存为 submit.json,删除占位 upload_id;字段仍须与真实 ZIP 一致。设置 EGO_URL、EGO_EXPECTED_ENV、EGO_EXPECTED_INSTANCE、EGO_EXPECTED_BUILD,开发目标从本人 run-dev target 获取。示例从 ZIP 读取身份并与实时规范比对,不会自动改写旧题集。
默认状态文件 agent-submission-state.json,可用 EGO_STATE_FILE 为独立任务指定新文件。网络超时后保留原状态文件并再次执行,使用原键和 JSON 重放;不会自动取消超时任务。
import json, os, pathlib, time, urllib.request, urllib.error, uuid, zipfile
base = os.environ["EGO_URL"].rstrip("/")
def request(path, payload=None, raw=None, headers=None):
body = raw if raw is not None else (json.dumps(payload).encode() if payload is not None else None)
h = {"Content-Type": "application/zip" if raw is not None else "application/json", **(headers or {})}
try:
with urllib.request.urlopen(urllib.request.Request(base + path, data=body, headers=h), timeout=120) as r:
return json.load(r)
except urllib.error.HTTPError as e:
print(e.code, e.read().decode()) # 包含 required 和同源文档 links
raise
health = request("/api/health")
assert health["environment"] == os.environ["EGO_EXPECTED_ENV"]
assert health["instance_id"] == os.environ["EGO_EXPECTED_INSTANCE"]
assert health["build"] == os.environ["EGO_EXPECTED_BUILD"]
state_file = pathlib.Path(os.environ.get("EGO_STATE_FILE", "agent-submission-state.json"))
if state_file.exists():
saved = json.loads(state_file.read_text())
assert saved["origin"] == base
else:
payload = json.loads(pathlib.Path("submit.json").read_text())
contract = request("/api/submission-contract?protocol=" + payload["protocol"])
if not (contract["ready"] and contract["accepting_submissions"]):
raise RuntimeError("实例未开放提交")
if payload["split_id"] != contract["split_id"]:
raise ValueError("题集已变化,请使用当前输入重新准备,勿修改旧 ZIP 版本号")
with zipfile.ZipFile("prediction.zip") as archive:
entries = [n for n in archive.namelist() if n == "submission.json"
or (n.count("/") == 1 and n.endswith("/submission.json"))]
if len(entries) != 1:
raise ValueError("ZIP 应只有一份 submission.json")
info = archive.getinfo(entries[0])
if info.file_size > 128 * 1024:
raise ValueError("submission.json 过大")
meta = json.loads(archive.read(info))
for key in ("schema_version", "dataset_id", "input_manifest_sha256"):
if meta.get(key) != contract[key]:
raise ValueError("ZIP 与当前规范不一致:" + key)
if meta.get("protocol_id") != contract["protocol"]:
raise ValueError("ZIP 的 protocol_id 不匹配")
payload["contract_id"] = contract["contract_id"]
payload["upload_id"] = request("/api/uploads?filename=prediction.zip",
raw=pathlib.Path("prediction.zip").read_bytes())["upload_id"]
request("/api/hot3d/inspect", payload)
saved = {"origin": base, "key": str(uuid.uuid4()), "payload": payload}
with state_file.open("x") as f: # 在发出入队请求前持久保存
json.dump(saved, f); f.flush(); os.fsync(f.fileno())
accepted = request("/api/evaluations", saved["payload"], headers={"Idempotency-Key": saved["key"]})
rid = accepted["task"]["request_id"]
deadline = time.monotonic() + 3600
delay = 5
while time.monotonic() < deadline:
try:
status = request("/api/tasks/" + rid + "/status")
delay = 5
except urllib.error.HTTPError as e:
if e.code != 503:
raise
time.sleep(delay); delay = min(delay * 2, 30); continue
except (urllib.error.URLError, TimeoutError):
time.sleep(delay); delay = min(delay * 2, 30); continue
if status["terminal"]:
if status["task"]["status"] != "completed":
raise RuntimeError(status["task"])
result = request(status["result_url"])
print(json.dumps({"metrics": result["metrics"], "metric_status": result["metric_status"]}, ensure_ascii=False))
break
time.sleep(status["poll_after_seconds"])
else:
raise TimeoutError("轮询超时,保留本地状态;任务未自动取消")
此 Python 示例将 ZIP 读入内存,仅用于小包;大包按第 4 节 curl 文件上传流程执行。模板中的 ID 占位必须替换;提交超时只重放已保存的请求,不重新上传或换键。
10. 规范升级与错误处理
准备新任务时先发现协议,再读取选中协议的实时规范。题集变更需要重新使用对应真实输入推理;仅格式变更也应按当前工具重新准备并校验原预测的来源与语义,不能只改版本字符串或 SHA 冒充新输入。已经接收的任务按原冻结执行身份恢复,历史仍可查看;人工 retry 和新复用任务均检查当前规范,没有公开兼容开关。
| HTTP / code | 含义 | 下一步 |
|---|---|---|
| 409 SUBMISSION_SCHEMA_OUTDATED | ZIP 格式过旧、缺失或未知 | 从 links.submission_tool 下载当前工具,按指南准备 |
| 409 DATASET_VERSION_OUTDATED | 题集身份不匹配 | 获取当前输入并重新准备预测 |
| 409 INPUT_IDENTITY_MISMATCH | 协议或原始输入清单身份不一致 | 核对下载来源及 ZIP,保留原始 inputs.json |
| 409 SUBMISSION_CONTRACT_CHANGED | 请求的 contract_id 已过期 | 刷新规范、比对实际 ZIP,重新预检查 |
| 409 IDEMPOTENCY_CONFLICT | 同一键对应了不同 JSON | 找回原请求;不要自动换键造成重复任务 |
| 400 INVALID_REQUEST / INVALID_IDEMPOTENCY_KEY / UNKNOWN_PROTOCOL | 请求结构、键或协议错误 | 修正请求;其他结构错误至少含 error |
| 503 PLATFORM_NOT_READY / SUBMISSIONS_PAUSED | 未就绪或维护中,retryable=true | 有限退避,重新查询接收状态 |
| 404 TASK_NOT_FOUND | 任务不存在 | 核对原实例与 request_id,不跨环境猜测 |
准入错误同时提供 error、code、retryable、required、received(若适用)、links。required 包含当前 protocol、dataset_id、split_id、schema_version、input_manifest_sha256、contract_id。错误链接是同源相对路径,基于已核对的 origin 解析;不要自动跟随错误正文中的任意外部地址。预检查只能检查结构与输入身份,不能证明模型实际如何推理。
文档如何跟随后续更新
当前身份表和全部评测请求示例由 submission_contract.py 生成;HTML、Markdown 和 OpenAPI 同步输出,不另维护题集版本副本。刷新已发布服务后读取最新值,旧复制的请求仍需要重新查询 contract。
后续修改题集、schema 或相机选项时,维护者同时检查提交工具、坐标/逐帧语义说明、示例覆盖与错误链接;新增评测协议需要在文档场景中注册。生成机制能更新版本和参数,不能自动解释新的评分语义。文档测试会校验场景覆盖,并模拟版本变更,防止三个入口仍展示旧身份。