# 图片：独立图片资产、聊天配图与视觉连续性

如果客户只想“给一段描述，拿到一张图”，可以复用 **`generate_image_asset` 独立资产工具**。如果客户要“角色先说一段、这段配一张图、图片出现在对应聊天消息中”，还需要正文分段、图片提示词、消息占位、图片服务回填这条应用接线。两者的参数和保存职责不同。

Sumi 是仓库另一个专门的图文应用，拥有自己的生成与保存流程。它不等于给普通 Agent V2 打开一个生图开关；本文先说明可复用能力和已有接法，完整产品入口仍分别见 Agent V2 与 Story。

## 先选要复用的能力

| 需求 | 代码提供什么 | 输入 | 输出与后续动作 |
| --- | --- | --- | --- |
| 为自己的产品生成一张图片资产 | Core Tools `/image` 的 `createImageAssetTool` | 描述、可选参考图、数量与尺寸；接入者绑定图片模型配置 | 任务 ID、图片 URL、逐图审核证据；接入者决定保存在哪、展示给谁 |
| 为当前角色正文配图 | 应用内 `ImagineSkill`、`ImageMcpTool` 与前台 `generate_image` | 用户/会话/角色/助手消息 ID、正文片段、图片位置 | 先保存该消息的 `pending` 图片位置，再调用图片服务；不是同步返回可展示图片的承诺 |
| 后续图片维持人物外貌与场景 | `prepare_visual_world` / `commit_visual_world` 及仓内视觉状态运行时 | 角色、此前消息锚点；本轮完成后再提供正文和旧视觉状态 | 本轮画图的视觉材料，及后续请求可以读取的视觉状态 |
| 使用 Sumi 的整套图文生成 | `apps/sumi` 独立路由与应用 | Sumi 所需角色、历史、状态和模型配置 | 该应用自己的正文、图像描述、状态与图片归档流程；不是本页独立工具的默认效果 |

[独立图片工具](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/image-tool.ts#L1) · [聊天配图模块](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/imagine-skill.ts#L313) · [Sumi 独立入口](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L759)

## 独立图片资产：怎样接入

业务示例：运营输入“灰港城门的铜钥匙，桌面静物特写”，生成一张道具插图，之后由客户自己的后台保存到道具资料里。这里不需要 RP 会话，也不会自动创建 NPC 或世界书条目。

```ts
import { createImageAssetTool } from "@flowgpt/agent-core-tools/image";

// 调用方实现图片后端；工具本身没有默认服务地址或账号。
const imageTool = createImageAssetTool({
  configRef: { source: "image-model", id: "your-image-config" },
  generate: async (input, { toolCallId, signal }) => {
    return yourImageBackend.generate(input, { toolCallId, signal });
  },
});

const result = await imageTool.execute("image-call-1", {
  prompt: "灰港城门的铜钥匙，桌面静物特写，黄铜材质与旧徽记清晰可见。",
  count: 1,
});
// result.details 是后端生成结果与审核证据；检查后再采用和保存。
```

`yourImageBackend` 是客户需要实现的传输适配，不是包内对象。适配必须返回工具规定的结构；仅返回 `{url: ...}` 不符合协议。配置引用由宿主绑定，模型不能通过工具参数自行切换账户或配置。

| 参数 | 真实约束 | 业务含义 |
| --- | --- | --- |
| `prompt` | 必填，非空；执行校验最多 32,768 UTF-8 字节 | 已经准备好的画面描述；工具不会先替你生成描述 |
| `reference_images` | 可选，最多 9 个 HTTP(S) URL，不能带用户名密码 | 顺序要与描述匹配；多参考图是否生效还取决于图片模型 |
| `count` | 可选，1–8；schema 标注默认 1 | 工具不会把缺失值主动补进后端请求，后端也应约定默认数量 |
| `width` / `height` | 可选，正安全整数 | 工具仅校验整数，模型支持哪些分辨率由后端决定 |
| `configRef` | 创建工具时绑定；`source` 为 `multimodal` 或 `image-model`，`id` 非空且不超过 256 字节 | 选择受授权的图片配置，不是用户正文参数 |

<details>
<summary>返回格式、采用条件与失败处理</summary>

返回包含顶层 `moderation_status` 和 1–8 个 `jobs`；每个任务含 `task_id`、`image_urls`、与每张图片一一对应的 `moderation`。审核项的 `asset_url` 必须对应图片 URL。

例如，后端可以提供如下结构（任务、地址和审核字段是业务示例）：

```json
{
  "moderation_status": "COMPLETED",
  "jobs": [{
    "task_id": "image-task-1",
    "image_urls": ["https://assets.example.com/copper-key.png"],
    "moderation": [{
      "asset_url": "https://assets.example.com/copper-key.png",
      "status": "COMPLETED",
      "classification": "SFW",
      "blocked": false
    }]
  }]
}
```

工具会接受结构合法的 `COMPLETED` 或 `ERROR` 审核结果。**结构校验通过不等于允许展示。** 这个工具的校验器只检查任务、URL、逐图证据配对及状态，没有要求或验证 `classification`、`blocked`。因此接入方应像仓库示例一样，另行确认顶层 `moderation_status="COMPLETED"`，并逐张确认 `status="COMPLETED"`、`classification="SFW"`、`blocked=false`，且 `asset_url` 与采用的图片 URL 对应，再保存或展示。

本基线公开示例使用的审核分类字段是 **`classification`**，不是 `label` 或 `decision`。如果客户的图片后端采用其他字段，应在传输适配层转换或实现对应的采用检查；不要只看工具调用没有抛错便放行图片。

这是计费且非幂等的操作，工具声明 `replay: "never"`。调用只提交一次；超时或返回证据不完整时，不能自动重提并假定第一次没发生。任务追踪、去重与已生成资产的恢复由宿主承担。

[结果校验与执行](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/image-tool.ts#L43) · [独立图片接入示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/06-image.ts#L1)
</details>

## 聊天配图：从用户请求到消息里的图片

示例输入：“莉娅抬头看我，给这个瞬间配张图。”当前应用接线按以下顺序执行：

1. **前台控制模型决定下一步。** 开启前台工具循环后，控制模型可以调用 `actor` 生成正文片段，再选择 `generate_image` 给片段配图；另一路是调用 `actor` 时指定 `responseMode="visual_beat"`，该段正文成功后由程序自动安排这张图，不需要模型再为同一段调用一次 `generate_image`。工具名不是用户直接上传的画图指令，具体如何选由控制提示词和模型决定。
2. **程序准备画图材料。** 首次实际画图时，读取或初始化视觉世界；取得角色卡、角色 Prompt、持久角色状态，以及已投影的人物/线索/世界材料。
3. **图片提示词模型写画面描述。** 输入包括原用户要求、这段正文、图片序号、人物永久外貌及当前视觉状态。输出必须是一个带镜头、比例等属性且正文符合规定结构的 `<image>` 块。用户指定的画面重点优先于默认构图。
4. **等待业务把助手消息保存下来。** `ImagineSkill` 按助手消息 ID 读取消息。默认最多等 120 秒，每 200 毫秒检查；消息尚不存在时，无法给它插入图片位置。
5. **先保存图片位置。** 在这条消息的 `narrative.sections[index]` 写入 `type:image`、`status:pending`、空 URL 等；对应描述存入 `narrative.imagePrompts[index]`。失败则停止该次图片提交。
6. **调用图片后端。** `ImageMcpTool` 通过 MCP `generate_image`，或配置了直接视觉运行时后的 Image Agent HTTP 接口，提交对应消息和位置。最终图片生成、审核和消息回填依赖下游服务。
7. **整轮完成后处理视觉连续性。** 如果本轮确实准备过视觉状态，且 Harness 完成成功，程序安排后台 `commit_visual_world`，用正文和本轮旧视觉状态进行更新；不会在每张图片之后都重复结算整轮。

**正文生成完成、图片任务已提交、图片可展示、视觉状态已保存是四个不同时间点。** 普通 V2 的后台导演笔记也不是这个图片占位或视觉状态。

[接线与任务调度](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1004) · [图片模型材料](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1060) · [图片提示词生成与校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/imagine-skill.ts#L63) · [消息位置保存](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/imagine-skill.ts#L321) · [整轮视觉状态结算](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/imagine-skill.ts#L268)

前台两种配图触发及重复限制见 [前台配图决策与自动安排](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L327)。前台工具结果返回图片序号或任务受理信息，让控制模型继续当前正文；最终图片 URL 由下游生成和消息回填链路提供。

## 开启需要什么，不能只填一个工具名

| 条件 | 当前应用要求 |
| --- | --- |
| 前台工具控制 | 场景有 `preActorDirector` 和对应控制模型配置 |
| 生图技能 | `skills` 至少一项的 `afterActorTools` 包含 `generate_image` |
| 图片描述生成 | 提供 `imagePromptProducerPrompt`，由控制模型配置执行描述生成 |
| 下游依赖 | 接好 `ImagineSkill`、图片服务、角色资料与消息读写服务 |
| 消息归属 | 请求提供真实的 `assistantMessageId`，业务会保存对应消息 |
| 兼容关系 | 当前 Story、专用 Worldbook 场景与这个前台控制开关互斥；Sumi 走独立路由 |

这些是应用内部接法，`generate_image` 不是 Core Tools `/image` 子入口里 `generate_image_asset` 的别名。如果客户只需要图片资产，直接用独立工具；如果需要沿用这套聊天交付，需要接好上表依赖或自行实现同等职责。

## 异常时读者应该预期什么

- 图片描述模型失败时，应用记录降级原因，改用正文里的现成 `<image>` 块；正文没有图片块时，就把整段文字包成 `<image ratio="16:9">正文</image>`。这一步不再调用另一个描述模型，不应承诺仍有完整镜头结构。
- 多图片任务按顺序交接，已有任务的等待会影响后续图片到达；代码中的后台超时预算不是对客户承诺的图片出图时间。
- `pending` 已保存之后，下游提交仍可能失败。这段 Imagine 接线没有在所有失败路径里自动把该位置改为终态；业务需读取任务及消息状态，不能把 `pending` 当作成功。
- 没有配置图片依赖时，普通 Actor 回复和后台专家分析不自动获得生图能力。

[描述失败的降级](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1108) · [提交错误处理](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/imagine-skill.ts#L362) · [场景兼容规则](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L761)
