# AI 处理与流水线

一次调用就能转写、打标签或转格式，结果按内容缓存；也可以让落进某个文件夹的每个文件自动处理，支持条件、串联和现成模板。

**动作**（action）对文件的某一个版本做一件事。服务器自带三个，[插件](/docs/plugins)可以加更多。

| 动作 | 做什么 | 写到哪里 | 服务器需要的设置 |
|---|---|---|---|
| `side.transcribe` | 把说的话转成带时间的文字，可以搜索 | `transcript` 附加信息 | 装 ffmpeg；`SIDESPACE_TRANSCRIBE_URL` 指向任何兼容 OpenAI `/audio/transcriptions` 的服务（OpenAI、Groq、本地 whisper.cpp 或 faster-whisper），加 `SIDESPACE_TRANSCRIBE_KEY` |
| `side.tags` | 视觉模型看几帧关键画面，给出描述、标签、景别、场景和情绪 | `ai.tags` 附加信息 | 默认用 Claude（`SIDESPACE_ANTHROPIC_API_KEY`），或本地模型：`SIDESPACE_TAGS_PROVIDER=openai`、`SIDESPACE_TAGS_URL`、`SIDESPACE_TAGS_MODEL` |
| `side.convert` | ffmpeg 预设：`mp4`、`webm`、`prores-proxy`、`wav`、`mp3`、`jpg`、`png`、`webp`、`gif` | 在原文件旁边新建一个文件，带 `derived` 附加信息 | 装 ffmpeg |

`GET /v1/spaces/{sid}/actions` 列出这台服务器能做什么。没配好的动作标为 `available: false` 并写明缺哪个设置，每个动作还附带它的 `params_schema`。

## 运行

```sh tab=CLI
sidespace actions "Film A"
sidespace run "Film A" /Ep01/a.mov side.transcribe --param language=zh
sidespace run "Film A" /Ep01/a.mov side.convert --param preset=mp4 --param max=720
sidespace runs "Film A" /Ep01/a.mov
```

```ts tab=TypeScript
// 等待结果。失败时抛出 SideSpaceError（code 为 run_failed）。
const run = await side.run("Film A", "/Ep01/a.mov", "side.transcribe", { language: "zh" });
console.log(run.cached, run.result?.sidecars?.transcript);
```

```python tab=Python
run = side.run("Film A", "/Ep01/a.mov", "side.transcribe", {"language": "zh"})
print(run["cached"], run["result"]["sidecars"]["transcript"]["text"])
```

```sh tab=curl
curl -X POST "https://side.example.com/v1/spaces/$SPACE/runs" \
  -H "Authorization: Bearer $SIDESPACE_TOKEN" -H "Content-Type: application/json" \
  -d '{"action": "side.tags", "path": "/Ep01/a.mov"}'
# 然后 GET /v1/spaces/$SPACE/runs/$RUN，直到 state 变成 done 或 failed
```

一次运行（run）依次经过 `queued`、`running`，最后是 `done`、`failed` 或 `canceled`，过程中 `progress` 从 0 到 1。编辑者可以发起，令牌需要 `runs:write`。`POST …/runs/{rid}/cancel` 可以取消。SDK 里用 `startRun`/`start_run` 和 `waitRun`/`wait_run` 把发起和等待分开。

### 按内容缓存

同样的字节、同样的参数只处理一次。重新上传、复制或恢复旧版本都不会再调用模型，运行会立刻返回并带 `cached: true`。只写附加信息的结果可以跨空间复用；生成新文件或预览文件的结果只在同一空间内复用。想重做时传 `"fresh": true`（CLI 用 `--fresh`）。

> [!TIP]
> 运行之前先读一下文件的附加信息：流水线可能已经做过转写或标签了。

### 失败与重试

可能过一会儿就好的失败（超时、模型服务返回 5xx）会在 30 秒、3 分钟后重试，共 3 次；不会好的失败（密钥被拒、文件没有声音）立刻失败，原因写在 `error` 里。

运行做出的改动记在发起人名下，`actor_kind` 是 `plugin`，`actor_via` 是动作名，所以在历史里能看出是 AI 做的。

## 流水线

流水线（pipeline）是空间的常设规则：新保存的文件只要符合路径 glob 和 MIME 前缀，就自动跑某个动作。只有空间管理员能建，令牌需要 `pipelines:manage`。

```sh tab=CLI
sidespace pipeline create "Film A" side.tags --path "/dailies/**" --mime video/ --name "给样片打标签"
sidespace pipeline create "Film A" side.transcribe --mime audio/ --backfill    # 连同已有文件一起
sidespace pipeline list "Film A"
sidespace pipeline enable "Film A" pip_… --off
```

```ts tab=TypeScript
await side.createPipeline("Film A", { name: "给样片打标签", action: "side.tags", path_glob: "/dailies/**", mime_prefix: "video/" });
```

```python tab=Python
side.create_pipeline("Film A", "给样片打标签", "side.tags", path_glob="/dailies/**", mime_prefix="video/")
```

流水线和 Webhook 一样，各自按变更日志往前走，从建好的那一刻开始；`--backfill`（或 `POST …/pipelines/{pid}/backfill`）补上已有的文件。由运行生成的新版本不会再触发流水线，所以“转成 mp4”的流水线不会循环。

### 条件

`conditions` 让流水线只处理同时满足这些条件的文件：`min_size`、`max_size`（字节），以及媒体探测得到的 `min_duration_s`、`max_duration_s`、`min_width`、`min_height`、`has_audio`。文件没有这项数值就算不满足（图片没有时长）；不认识的键会被拒绝，免得拼错一个字就把所有文件都匹配上。新版本还在探测时，流水线会等探测结果，最多等 15 分钟。

```sh tab=CLI
# 只给 4K 母版出审片小样
sidespace pipeline create "Film A" side.convert --mime video/ --param preset=mp4 --param max=1920 --when min_width=2560
# 只转写有声音、长于 10 秒的视频
sidespace pipeline create "Film A" side.transcribe --mime video/ --when has_audio=true --when min_duration_s=10
```

```sh tab=curl
curl -X POST "https://side.example.com/v1/spaces/$SPACE/pipelines" \
  -H "Authorization: Bearer $SIDESPACE_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "转写采访", "action": "side.transcribe", "mime_prefix": "video/", "conditions": {"has_audio": true, "min_duration_s": 10}}'
```

### 串联

`after: <动作>` 让流水线在那个动作对匹配的文件跑完时开始，而不是在文件保存时开始。适合依赖前一步结果的处理，比如视频转写完之后再打标签。运行按版本、动作和参数去重，所以就算串成一个环，也只会走一轮；串联流水线补处理已有文件时，只处理它跟随的动作已经做完的文件。

```sh
sidespace pipeline create "Film A" side.tags --mime video/ --after side.transcribe
```

### 模板

模板是常见任务的现成流水线组合。它们写在服务器代码里，服务器升级时模板也跟着变好。

| 模板 | 做什么 |
|---|---|
| `video-insights` | 新视频自动用 AI 打标签，有声音的顺便转写 |
| `review-copies` | 宽于 2560 像素的新视频，旁边生成一份 1080p H.264 MP4 |
| `interviews` | 转写新的音频，以及有声音的视频 |
| `tag-images` | 描述新图片并打标签 |

```sh tab=CLI
sidespace pipeline templates "Film A"
sidespace pipeline template "Film A" video-insights --path "/Ep01/**" --backfill
```

```sh tab=curl
curl "https://side.example.com/v1/spaces/$SPACE/pipeline-templates" -H "Authorization: Bearer $SIDESPACE_TOKEN"
curl -X POST "https://side.example.com/v1/spaces/$SPACE/pipeline-templates/video-insights" \
  -H "Authorization: Bearer $SIDESPACE_TOKEN" -H "Content-Type: application/json" \
  -d '{"path_glob": "/Ep01/**", "backfill": true}'
# → {"items": [新建的流水线], "runs": 对已有文件发起的运行数}
```

开启模板就是新建普通的流水线，之后可以像其他流水线一样列出、暂停或删除。服务器还没配好的动作，模板照样列出；它的流水线会等服务器配好再处理。

网页版里，空间管理员在文件夹工具栏的**自动处理**里开启模板，可以只对这个文件夹或对整个空间，也在那里关闭。

## 给 AI agent

通过 [MCP](/docs/mcp)，`list_actions` 查看能做什么，`run_action` 发起并等待结果（默认等 60 秒，最多 300 秒），`get_run` 查进度。
