# Llmer / H3-Local：Agent 参考图上传与结果下载

更新：2026-10-14。服务根地址 https://ry.live；接口路径包含 /v1。

## 现有 Agent 如何接入

- 能读取本地文件并执行 Python/HTTP 的 Agent：可立即使用 [Python 示例](https://ry.live/docs/examples/llmer_media.py)，API 密钥由运行环境注入 `LLMER_API_KEY`，服务地址变量 `LLMER_BASE_URL=https://ry.live`。
- 具有自定义函数工具执行器的 Agent：加载 [工具定义 JSON](https://ry.live/docs/media-tools.json)，按下方绑定方法注册并执行工具。JSON 只描述工具，不会自动安装执行器，也不是 MCP 服务地址。
- 只有聊天 Base URL 和模型配置的 Agent：还需接入文件读取及工具执行器。已经缓存旧文档或固定了工具列表的 Agent，需刷新文档并重新加载工具；网站更新不会自动改写第三方 Agent。

用户将本地参考图片作为附件交给 Agent；Agent 读取附件在其运行环境中的真实路径。Llmer 服务无法读取客户端本地路径，必须由 Agent 上传文件字节。现有用户 API 密钥即可鉴权；生成仍受原模型权限与余额约束。

## 上传到哪里

两种上传方式最终都存入 SvrZs 的私有 MinIO，无需用户开通外部 OSS 或提供 MinIO 密钥。

| 方式 | 数据路径 | 使用场景 |
| --- | --- | --- |
| 授权直传 MinIO（Python 示例默认） | Agent 向 Llmer 申请授权 → Agent 经 HTTPS 媒体入口直接 PUT 至 MinIO → Llmer 校验完成 | Agent 自动处理全部步骤，文件字节不经 Llmer 应用转发 |
| 经 Llmer 上传至 MinIO（multipart） | Agent → Llmer `/v1/assets` → MinIO | 只支持 multipart 上传的现有工具，一次请求得到资产 |

“普通上传”只是 multipart 接口的称呼，不是另一套存储。直传使用 ry.live 的 HTTPS 签名地址，MinIO 内部端口和存储凭据不对用户开放。

## Agent 操作顺序与完成条件

1. 有本地参考图：执行 `prepare_h3`，首尾帧传 `first` / `last`；多参考传有序 `references`。工具自动直传 MinIO，得到含资产 ID 的请求对象即准备完成。若只需上传或复用图片，使用 `upload_reference`，得到 `status=ready` 和 `id` 即完成。
2. 用户已授权生成后，调用 `submit_once(body, receipt)`；保存返回的任务 `id`。准备请求本身不会生成。提交结果不确定时核对任务记录，不自动再提交。
3. 使用 `get_video_status(task_id)` 查询状态，通常间隔 5 秒；直到 `completed` 或 `failed`。失败报告任务错误，不下载未完成成品。
4. 已有成功任务调用 `download(task_id, output)`；图片成品先从 `GET /v1/tasks/{id}/artifacts` 取得 artifact key，再传 `artifact`。以本地文件大小和 SHA-256 返回值确认下载完成。
5. 下载中断时保留 `.part` 与 `.part.json`，重新执行同一下载命令；只续签和下载已有结果。若提示 publishing，稍后重试下载；签名失效不应触发重新生成。

### 首尾帧与多参考命令

```bash
python3 llmer_media.py prepare --prompt "固定机位，主体自然转动" \
  --first-frame ./first.png --last-frame ./last.png --output request.json
# 多参考模式，按给定顺序，和首尾帧互斥
python3 llmer_media.py prepare --prompt "保持商品外观，展示细节" \
  --reference ./front.png --reference ./side.png --output references.json
# 以下 submit 会生成并计费；每次新任务使用新的 receipt 文件
python3 llmer_media.py submit request.json --receipt task.json
python3 llmer_media.py status task_ID
python3 llmer_media.py download task_ID --output video.mp4
python3 llmer_media.py download task_ID --artifact image_1 --output image.png
```

H3 示例固定默认 8 步、768P、5 秒；`prepare --resolution 480P|768P|1080P` 可选，1080P 为超分输出。服务四路并发及按需清理策略不因媒体工具改变。

### 函数工具绑定

将 `llmer_media.py` 和 `media-tools.json` 放在 Agent 执行环境。JSON 是 Chat Completions 风格的函数定义；其它框架按其工具注册接口转换包装层，保留名称和参数。运行环境从密钥存储注入 `LLMER_API_KEY`，密钥不作为模型工具参数。

```python
import json
from pathlib import Path
from llmer_media import LlmerMedia

client = LlmerMedia()
tools = json.loads(Path('media-tools.json').read_text())
handlers = {
    'upload_reference': client.upload_reference,
    'prepare_h3': client.prepare_h3,
    'submit_once': client.submit_once,
    'get_video_status': client.get_video_status,
    'download': client.download,
}
# 将 tools 注册到 Agent。收到工具调用后：
# 1. 校验 name 在 handlers 中，arguments 符合对应 JSON Schema；
# 2. 文件路径限制在用户已授权的附件/输出目录；
# 3. submit_once 仅执行用户已授权的生成意图；
# 4. result = handlers[name](**arguments)，将结果回传 Agent。
# 无需 eval、动态导入或拼接 shell 命令。
```

宿主必须实际执行工具并回传结果；只把工具定义放进提示词并不会上传文件。已有 Agent 若固定接收远程 URL，应在其本地文件入口调用 `upload_reference`，把返回 `id` 映射到下列 H3 字段，不再要求用户手动上传外部 OSS。

## HTTP 调用契约

Llmer API 请求携带 `Authorization: Bearer $LLMER_API_KEY`；JSON 请求使用 `Content-Type: application/json`。访问返回的 MinIO 签名 URL 只使用返回的专用 headers，不能附加 Llmer API Key，也不要修改 URL 查询参数。签名 URL 不应写入公开日志或长期作为资产身份保存。

### 授权直传 MinIO

1. `POST /v1/assets/uploads`，请求 `{"size_bytes":实际文件字节数,"mime_type":"image/png"}`（JPEG 用 `image/jpeg`）。响应字段为 `upload_id`、`upload_url`、`method`（PUT）、`headers`、`expires_at`、`max_bytes`。
2. 对 `upload_url` 执行 PUT，body 是原始图片字节，准确使用 `headers` 中的 Content-Type、Content-Length。这里不是 multipart，不携带 Llmer Bearer。收到 HTTP 200 后继续。
3. `POST /v1/assets/uploads/{upload_id}/complete`，JSON body `{}`。幂等校验后返回资产信息：`id`、`status=ready`、`mime_type`、`size_bytes`、`sha256`、`width`、`height`、`created_at`、`expires_at`。只有 complete 成功的 `id` 才可用于生成。

上传签名有效 15 分钟；若超时先尝试 complete 检查已上传结果，未成功再申请新上传，不触发生成任务。

### 经 Llmer 上传至 MinIO

```bash
curl --fail-with-body -sS https://ry.live/v1/assets \
  -H "Authorization: Bearer ${LLMER_API_KEY}" \
  -F 'file=@./reference.png'
```

仅使用名为 `file` 的 multipart 文件字段，Content-Type 交给客户端自动生成 boundary。成功响应与 complete 的资产信息相同；上传成功后直接使用 `id`，不用再调用 complete。

### 用资产 ID 提交 H3

`POST /v1/videos`，例如：

```json
{
  "model": "minimax-h3-local/image-to-video",
  "prompt": "保持主体外观，自然转动",
  "seconds": 5,
  "steps": 8,
  "resolution": "768P",
  "first_frame_asset_id": "asset_REPLACE_WITH_REAL_ID",
  "last_frame_asset_id": "asset_REPLACE_WITH_REAL_ID"
}
```

单首帧或单尾帧只保留对应字段；多参考模式用 `reference_asset_ids: ["asset_...", "asset_..."]` 替换首尾帧字段。资产字段当前仅适用 `minimax-h3-local/image-to-video`；与旧 first_frame、last_frame、input_reference、reference_images 字节字段互斥。不要把资产 ID 当 URL、base64 或本地路径。

限制：PNG/JPEG，单张 ≤10 MiB、≤16 百万像素，合计 ≤20 MiB，多参考 1–4 张；按传入顺序保留。参考资产有效 24 小时、仅同一用户可用。提交前过期则重新上传，已被模型接收的任务使用解析后的图片。

其它图片模型按其接口使用 multipart 或 `POST /v1/assets/{id}/download-url` 返回的授权图片 URL；如果模型只接受 base64，Agent 在本地读取图片编码。不要将 H3 的资产字段直接套用到其它模型，也不要将普通远程 URL 传给只接受字节的 H3 输入字段。

### 查询和下载

| API | 结果 |
| --- | --- |
| `GET /v1/assets/{id}` | 资产信息 |
| `GET /v1/videos/{id}` | 视频任务状态 |
| `GET /v1/tasks/{id}/artifacts` | 成品列表与 artifact key |
| `POST /v1/assets/{id}/download-url` | 参考图读取授权 |
| `POST /v1/videos/{id}/download-url` | 视频下载授权 |
| `POST /v1/tasks/{id}/artifacts/{key}/download-url` | 图片/视频成品下载授权 |

下载授权 POST body `{}`。HTTP 200 且 `artifact_status=ready` 时返回 `url`、`expires_at`、`size_bytes`、`mime_type`。GET 该 URL 下载；`Range: bytes=N-` 用于续传，206 追加，200 必须重写完整文件而非追加。URL 默认 15 分钟有效，参考资产接近到期时更短。旧 `/content` 入口继续可用，由 Nginx/MinIO 传输。

| 情况 | Agent 操作 |
| --- | --- |
| 下载授权 202 / publishing | 按 Retry-After 重试授权，等待已有结果归档 |
| 签名 URL 403 | 重新申请同一资产/成品的下载地址 |
| 上传 400 | 检查实际格式、大小；不循环重试无效内容 |
| API 401 / 403 | 检查用户密钥与权限，停止盲目重试 |
| 资产 404 / invalid_asset_reference | 检查所属用户与有效期，必要时重新上传本地原图 |
| 429 / 503 | 按 Retry-After（如有）有限退避重试上传/查询/授权；生成提交不自动重试 |

## 自动等待与重试

Python Agent 工具在上传会话申请、图片 PUT、完成确认、状态查询和下载授权遇到 429 时自动等待重试。优先使用 `Retry-After`（秒数或 HTTP 日期）；缺失或无效时等 60 秒，再加入最多 0.5 秒随机错峰。每个可重试步骤最多 5 次尝试（含首次），服务器要求等待超过 300 秒时停止并提示稍后继续，不提前重试。

- 图片 PUT、幂等 complete、查询与下载授权：网络中断及 408/500/502/503/504 可有限重试；无 Retry-After 时按 1、2、4、8 秒退避。complete 重试使用原上传 ID，不重新上传已完成的图片。
- 新建上传会话：只有明确返回 429 才自动重试；连接中断或其它不确定响应不自动重复创建会话。
- 已有成品下载：202 publishing 自动等待归档；媒体地址 403 时重新授权同一 task/artifact，429 或暂时故障等待后续传。保存 `.part` 与 `.part.json`；耗尽次数后下次运行同一 download 命令继续。下载流程不会调用生成接口。
- 生成提交：`submit_once` 在任何错误（包括 429、503、超时）下均只发送一次，保留未确认 receipt。先核对任务记录；禁止删除 receipt 或改名来自动重发。retry 的提示不代表需要重新生成。
- 400/401/一般权限 403 等永久错误直接报告，不盲目重试。MinIO 签名链接 403 属于上述续签分支。

已有 Agent 请更新执行环境中的 llmer_media.py 并重载工具；只刷新工具 JSON 不会更新旧的 Python 代码。

## 无需生成的接入验收

用已有 PNG/JPEG 调用 upload_reference，确认 ready、字节数及 SHA-256；用首尾帧和多参考调用 prepare_h3，核对字段、顺序及默认值，不调用 submit_once。用已有成功任务调用 download，中断后续传并核对原文件 SHA-256。上述检查不启动视频生成。

## ComfyUI

[ComfyUI 节点安装与工作流](https://ry.live/docs/comfyui)提供本地文件直传 MinIO、首尾帧和多参考准备、已有结果下载及重试。仅配置 API 地址不会自动安装节点，需安装包并重启 ComfyUI。
