# Actor：生成正文与前台工具编排

**业务用途：**让角色根据当前输入和资料说话、行动；需要图文节奏时，把一轮正文拆成合适片段，再安排图片。例如用户说“让守卫接过钥匙，并配一张图”，Actor 负责写守卫的反应，图片能力负责生成相应画面。

**形态与接入位置：**当前 Actor 是 `apps/emochi/actor/roleplay-runtime.ts` 的应用正文运行时。`actor` 是前台工具循环中的函数名；Actor 的 `beforeHooks` 是执行前改写参数的接入点。Actor 本身不是 Hook，也不是在这个循环里选择其他工具的主控模型。

它是已有应用接线，不是 SDK 根入口可直接 import 的公共 `Actor` 类。复用完整能力需要沿用应用的 Router/Axon/模型/聊天交付依赖，或自己实现这些适配；只安装 `@flowgpt/roleplay-harness` 不会自动得到这条业务链路。

## 两种实际运行方式

| 方式 | 谁决定执行 | 拿什么输入 | 返回什么 |
| --- | --- | --- | --- |
| 直接生成 | 程序调用 `generate`；没有 `functionLoop` 时进入 `generateOnce` | 已准备的角色 Prompt、聊天材料、模型参数、本轮输入 | 流式正文、最终文本、模型使用信息 |
| 前台工具循环 | 前台控制模型每次选择一个动作，程序执行并把回执交回 | 控制模型先读工具政策与本轮用户输入；实际 Actor 使用自己准备的正文上下文 | 多个正文片段组成一轮回复；图片/DIO 返回调度回执 |

控制模型的系统提示明确要求：通过函数控制一轮 RP，不直接写用户可见正文；读实际工具结果再选下一步，不按固定顺序机械调用。代码变量叫 `planner`，这里实施的是逐步选工具的循环，不需要先生成一个完整长期计划。它和普通 V2 后台的 Orchestrator 是两个不同接线。

这里有三个层次：**前台控制模型选 `actor` → 程序执行正文运行时 → 正文模型写回复**。另外，Harness 的 `options.actor.beforeHooks` 发生在创建和执行 Actor 节点之前，用来准备或覆盖参数；当前前台循环没有把这些 Hook 当作主模型可以任意选择的菜单。

[是否开启前台循环](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L305) · [控制提示词与逐步调用](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L344)

## 模型真正可以调用什么

| 工具 | 参数 | 程序执行与回执 | 结果何时使用 |
| --- | --- | --- | --- |
| `actor` | `responseMode` 可省略，默认 `complete`；也可为 `visual_beat` | 执行原有正文生成器，返回片段与消耗等信息 | 正文本轮流式交付；控制模型据回执决定继续或结束 |
| `generate_image` | `{}` | 使用最新 Actor 片段安排图片，返回 `scheduled` 等调度信息 | 图片完成后回填聊天；调度成功不是图片已生成 |
| `schedule_dio` | `{}` | 宿主将用户原始长期要求交给 DIO，返回 `accepted` | 外部编译完成且后续请求读到有效 Prompt 后才影响回复 |
| `over` | `{}` | 在 Actor 至少执行过一次后结束前台循环 | 不生成正文，不保存角色状态 |

菜单至少包含 Actor 与结束；图片、DIO 只有相应执行能力接好才提供。循环最多 12 步，有 DIO 接线时额外允许 1 步。所有 Actor 片段共享有效 ModelConfig 的整轮输出额度。旧分支 `actor({maxTokens:...})` 的接口不是当前主干参数。

整轮 Actor 额度读取有效参数 `max_tokens`，缺失时使用 2,048。控制模型调用本身另有成本，不能把这个额度理解为所有模型的总 token 上限。某个 Actor 片段没有返回输出 token 计量时，程序按当时剩余额度全部消耗记账。正文是否能逐 token 显示还受整轮 after-hook、正则后处理与 `visual_beat` 缓冲影响；不能承诺所有配置都即时逐 token 透出。

`visual_beat` 是 Actor 参数，不是另一项工具。片段形成合格的完整视觉情节且循环还有下一步时，程序可以自动安排图片；已经配过图的片段不能重复调度。正好用尽步骤时，不能保证还有一次自动排图机会。

[工具定义与参数解析](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L86) · [执行、步骤与预算](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L381)

## 怎样接入和配置

应用接入使用 Scenario 中的 `modelConfigRef`、`presetRef` 等选择正文模型与模板；真实角色、会话、消息、历史等由业务请求和 Axon 准备。它们不是模型自己可以随意修改的工具参数。

配置 `preActorDirector` 后，当前 dispatch 装配前台 `functionLoop`。其 `modelConfigId` 是前台控制模型，`skills` 含已授权预设；配置含 `afterActorTools: ["generate_image"]` 的图片预设、`imagePromptProducerPrompt`、图片后端依赖，以及本轮 `assistantMessageId`，才形成当前图片调度接线。DIO 也需要独立服务接线。不能只向模型展示工具名，却不给程序实际执行器。

<details>
<summary>宿主配置字段与 Hook 的准确位置</summary>

| 位置 | 字段或接口 | 改变什么 |
| --- | --- | --- |
| Scenario | `preActorDirector.modelConfigId` | 选择前台控制模型，不替换 Actor 模型 |
| Scenario | `preActorDirector.skills` | 已授权技能预设；当前 dispatch 从其中识别图片接线 |
| Scenario | `preActorDirector.imagePromptProducerPrompt` | 图片描述生产的系统提示；不是 Scenario 根级字段 |
| Actor 宿主参数 | `options.actor.parameters` | 宿主选择的模型、Prompt 与变量覆盖，仍需通过实际允许范围校验 |
| Actor 执行前 | `options.actor.beforeHooks` | 程序依次运行 Hook，将返回字段合入 Actor 参数 |
| 仓内另一可复用实现 | `selectPreActorSkill` / `createPreActorDirectorHook` | 提供 `actor({skill})` 的单次预选方案；不是当前 dispatch 使用的前台循环协议 |

Story 配置明确排斥 `preActorDirector`；Sumi 和专用 Worldbook 场景也有组合限制。需要跨产品组合时应开发和验证对应接线，不能把多个字段堆在同一 Scenario 里。

[图片预设与前台循环装配](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L957) · [预设选择接口](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/pre-actor-director.ts#L7) · [Actor 参数与 beforeHooks](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/harness.ts#L235) · [组合限制](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L761)

</details>

## 保存、失败与下一轮

Actor 生成结果经整轮后处理交给聊天服务，聊天正文是否被采用和落库由业务链路负责。Actor 工具本身不自动创建 NPC 卡片、发布世界书或更新普通 V2 的持久状态。前台控制在没有生成可见正文之前失败时，代码可以回退到直接 Actor 路径；已有正文后的失败需按当前运行时的规则保留或收束已生成片段。

具体而言：控制模型报错、返回非法工具请求或用尽步骤，而没有任何可用 Actor 结果时，进入直接生成回退；已有结果时结束循环并后处理已有片段。后续 Actor 调用发生空回复或超时、且前面已有完整片段时，会把失败回执交给控制模型继续决定。首次 Actor 执行失败、其他不可恢复错误或外部取消会传播；不能统一理解成“所有失败都返回已有正文”。已被外部图片或 DIO 服务接受的任务，也不因这段文字生成后来失败而自动撤销。

**二开验证例子：**输入“守卫接过钥匙，并配一张图”，分别检查正文是否先可用、是否只安排了一张图、图片结果是否绑定到正确消息、图片失败后用户看到什么；再发下一句，确认读取的是业务已保存的正文。不要用“模型调用了 actor”代替上述交付检查。

[控制失败与正文回退](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L413) · [整轮后处理](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L1078)
