# 下一句建议：给用户三个可选择的回应

**用途：**角色回复后，为用户提供三条“我接下来可以说什么／做什么”的候选。例如守卫问“手令呢？”，可以返回“我翻找随身的手令”“我问他去哪里补办”“我先退到旁边等候”。这些是用户可选择的输入建议，不是角色再回复三次，也不会自动替用户发送。

**交付形态：**当前是 Harness 应用里的 Auto Reply 生成流程，已有普通 Agent V2 和 Story 两种接线；不是 `@flowgpt/agent-core-tools` 导出的独立工具，也不是默认交给 Orchestrator 选择的专家。接完整产品时走现成 `AUTO_REPLY` 请求；单独二开需复用应用实现并接通模型、Prompt 与对应的状态／消息读取依赖。

[调用输入与普通V2实现](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/auto-reply.ts#L27) · [Story实现](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/story/auto-reply.ts#L47) · [产品请求分流和返回](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L613)

## 用户看到什么，业务拿回什么

业务在一条已生成的角色回复下请求建议。程序以这条回复的 `assistantMessageId` 为锚点，组织材料，调用专门模型，解析成三条不同的短文本，交还业务显示。

应用函数返回：

```json
{
  "options": [
    "我翻找随身的手令。",
    "如果没带手令，我该去哪里补办？",
    "我先退到旁边等候。"
  ],
  "usage": {"inputTokens": 800, "outputTokens": 60, "totalTokens": 860}
}
```

这里是结构示例，具体文案与 token 数由模型实际返回。现成 Agent Router 把三个选项转为单个结果中的编号文本：

```text
1: 我翻找随身的手令。
2: 如果没带手令，我该去哪里补办？
3: 我先退到旁边等候。
```

**生成建议时不写用户聊天消息、不更新 V2 状态、不结算 Story 剧情。** 业务方决定把它们做成按钮、填入输入框或保存为建议记录；只有用户实际选择／编辑并发送后，业务才按正常聊天流程保存那一句并发起下一轮。普通 V2 的调试记录可能记录建议文本，但调试记录不是用户已说出口的聊天历史。

[调试记录与options返回](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/auto-reply.ts#L184) · [编号文本、空artifacts与stateVersion0](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L677)

## 普通 Agent V2 怎样生成

| 步骤 | 具体做什么 | 输入／结果 |
|---|---|---|
| 1. 定位那一条回复 | 在请求给出的非空 user/assistant 消息里找到目标 assistant ID | 取该回复及它前面最多 6 条可见消息；之后已经发生的消息不会加入此次建议 |
| 2. 取得对应轮次的指导材料 | 调用 Axon `roleplay.state.get`，在 pending 或 planning_settlements 中查找同一个 source_message_id | 必须有非空 `auto_reply_guidance`；不能随便拿当前最新 state 顶替 |
| 3. 准备模型输入 | 角色名称／描述、认证上下文中的输出语言、最近用户输入、上述指导文字、最近可见对话 | 系统提示词来自单独 Auto Reply ModelConfig 关联的 PromptManager |
| 4. 生成候选 | 调用模型，要求三个不同方向 | 1 可以顺着当前剧情，2 关注情绪／关系／好奇，3 保留其他或中立方向；这是提示词指导，不是三个硬编码答案 |
| 5. 解析和检查 | 清理编号／空白、去重、最多取 3 条、每条最多 160 字符；部分语言有文字检查 | 凑足 3 条且通过检查才返回，否则尝试重新生成 |

### 等待的是什么状态

普通 V2 的后台开始工作时，会把**这一轮开始时读到的状态及其版本**，连同目标回复 ID、轮次和阶段，通过 `markPlanning` 发给 Axon。Auto Reply 随后读取状态接口里与这个回复 ID 匹配的 `auto_reply_guidance`。

所以它等待的是“这条回复所属轮次的指导快照可读”，**不是必须等六位专家都执行完，也不是必须等 Director 把本轮新状态保存成功**。指导记录可以在 pending 中，也可以在 planning_settlements 中。Harness 没有在这里现生成一份摘要；具体指导文字由外部状态服务提供，当前两个仓库不能证明其内部转换算法。

默认最多等待窗口为 **30,000ms**，两次读取之间等 **250ms**；配置范围 0–120,000ms。设为 0 仍会读取一次，不表示完全跳过检查。该数值控制查快照的轮询窗口，不是包括网络请求与模型生成的完整响应时限。

后台阶段记录是旁路写入，失败不阻塞 RP 正文；因此可能出现“角色回复已经成功，但建议因指导快照未就绪而失败”。这种情况不应伪造三条静态建议作为成功结果。

[轮初快照随阶段写入](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L593) · [mark_planning传输字段](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/axon/client.ts#L783) · [按回复ID轮询指导](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/auto-reply.ts#L253)

## Story 怎样生成

Story 的建议流程不读取普通 V2 的 pending／planning_settlements，也不等待旧后台快照。

| 步骤 | 具体做什么 | 业务意义 |
|---|---|---|
| 1. 检查回复锚点 | 请求中必须有且只有一条目标 ID 的 assistant 消息，前面还要有用户消息 | 防止把其他轮次的结果混进建议 |
| 2. 读取真正保存的正文 | 用可信 userId、conversationId、assistantMessageId 调 Axon `conversationMessage` | 正文以聊天服务保存的内容为准，不以请求中可能过时或截断的文本为准 |
| 3. 组织上下文 | 使用目标回复前最多 6 条非空可见消息，加入查回的完整正文、角色信息和指定语言 | 目标回复之后的消息不加入；不读取 Story 整个世界／叙事状态快照 |
| 4. 生成三种选择 | 提示词要求三个适合当前时刻的不同策略，允许暂停、澄清或换方向 | 不固定“选项1推进、2情绪、3其他”的分槽，也不替用户宣布行动成功或其他角色反应 |
| 5. 严格检查结果 | JSON 中必须恰好 3 条，非空、不同、每条不超过 160 字符，符合相关语言文字要求 | 失败时可带格式修正要求再次调用；不把超长条目直接截断采用 |

例如请求里缓存的是“守卫让你通过”，但聊天服务保存的正式正文是“守卫伸手索要手令”，Story 会按后者生成建议。如果正式正文尚未保存、查不到、角色不对或为空，流程失败；它不会退回请求里的旧文本继续生成，也没有在此实现等待正文落库的轮询。

源码函数允许额外提供 `pendingStage`，但它只是可选的未完成阶段建议，提示词明确不能把它当成已经发生的事实。**当前统一 Router 调用没有传入这个字段**，不能写成 Story 默认会把剧情计划一起交给 Auto Reply。

[唯一锚点与正式正文读取](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/story/auto-reply.ts#L53) · [输入与用户自主权要求](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/story/auto-reply.ts#L74) · [严格结果解析](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/story/auto-reply.ts#L17)

## 如何在现成产品里开启和调用

### 先配置专用模型和提示词

在已有完整场景配置的 `agenticV2.modelConfigs` 中补 `autoReply`，普通 V2 可同时调整 `autoReplyWaitMs`：

```json
{
  "agenticV2": {
    "modelConfigs": {
      "autoReply": "your-auto-reply-model-config-id"
    },
    "autoReplyWaitMs": 30000
  }
}
```

这是**合入现有配置的字段片段**，不能用它替换整个 agenticV2 配置；原有 actor、orchestrator、specialists、director 配置仍需保留。没有 `autoReply` ModelConfig 时，现成 Router 拒绝 Auto Reply 请求；它不自动借用 Actor 模型。

这个 ModelConfig 关联的 PromptManager 必须有名为 `auto_reply` 的系统提示词，而且必须包含 `{{getvar::language}}`。程序会填入输出语言和 `characterName`；缺失模板或语言变量会失败。不能把一段 `auto_reply` 提示词直接塞进 scenario，就当成替代配置中心接线。

Story 仍通过同一个 `agenticV2.modelConfigs.autoReply` 指定建议模型；程序根据场景是否存在 `storyV1` 选择 Story 实现。`autoReplyWaitMs` 虽会作为通用输入传入，但 Story 当前路径不使用这个等待值。

[autoReply模型字段读取](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L124) · [等待范围校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L170) · [auto_reply模板要求](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/prompt-manager.ts#L160) · [普通V2/Story分流](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L651)

### 再由业务发起 AUTO_REPLY 请求

现成产品入口是 Agent Router 的 `ChatRequest`，`chatType` 设为 `AUTO_REPLY`。必要材料包括：

| 字段 | 谁提供、用于什么 |
|---|---|
| `conversationId`／`promptId` | 已有会话和角色／作品身份 |
| `extensions.scenario_id` | 服务器已允许且已经配置 autoReply 的场景 |
| `extensions.turn_context.user_id` | 上游认证后得到的用户身份；不能由不可信前端任意代填 |
| `extensions.turn_context.assistant_message_id` | 为哪条角色回复生成建议，必须与 messages 内目标 ID 一致 |
| `extensions.turn_context.language` | 输出语言，来自可信业务上下文；不是让模型从用户上一句猜语言 |
| `messages` | 带 ID 和角色的消息序列，含目标 assistant 消息及此前用户内容 |
| `modelConfigId`、项目／协议字段 | 沿用已接通的 Agent Router 请求契约，不是直接把建议模型 ID 当作任意上游模型绕过场景 |

下例基于**已有且完整有效的 ChatRequest**修改本次用途； `request`、`runtimeConfig`、`userId` 和 `signal` 由可信宿主准备，其他项目与协议字段不在此重复展开：

```ts
import { ChatType } from '@flowgpt/agent-router-sdk/agent_pb.js';

request.chatType = ChatType.AUTO_REPLY;
request.extensions = {
  ...request.extensions,
  scenario_id: 'your-enabled-v2-or-story-scenario',
  turn_context: {
    user_id: userId,
    model_alias: 'your-model-alias',
    language: 'zh-CN',
    assistant_message_id: 'assistant-7',
    extra_set_vars: {},
    template_set_vars: {},
  },
};
// request.messages 需要包含目标 assistant-7 和它之前的用户消息。
// 通过现有 Agent Router client 发给 Harness；不要再发一条普通聊天来代替。
```

Router 返回编号文本与 usage。此专用途径提前返回，不再运行正常 Actor 正文生成或其后的记忆写入流程。它仍需要上游正常鉴权、消息来源与产品交付处理。Sumi 入口明确拒绝这里的 `auto_reply`，要求走 Chat Service 自己的独立路径；不能由这个实现推导出 Sumi 已复用同一套建议流程。

[真实AUTO_REPLY请求构造示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/test/agent-router.test.ts#L1220) · [提前返回分支](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L804) · [Sumi调用边界](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/sumi/runtime.ts#L41)

<details>
<summary>单独二开、返回校验、重试、费用与失败细节</summary>

**源码入口。** 普通 V2 为 `apps/emochi/planning/auto-reply.ts` 的 `generateAgenticAutoReplies`；Story 为 `apps/emochi/story/auto-reply.ts` 的 `generateStoryAutoReplies`。这是应用源码导出，不在两个公共 npm 包的导出表里。若从源码调用，输入要提供 requestId、parentRequestId、userId、conversationId、promptId、assistantMessageId、scenarioId、modelConfigId、waitMs、language、角色名称／描述和带 ID 的 messages；另有可选 temperature、trace、usage 要求。依赖对象是 `{axon, model, observeAutoReply?}`。

模型接口 `model.complete` 和 Axon 不是纯“一个 Prompt”能代替：普通路径依赖状态指导、ModelConfig／Prompt 解析与可选 token 计数；Story 依赖正式消息读取、Prompt 解析与可选 token 计数。若希望发布为客户独立可安装的能力，还需做这一层依赖封装。

| 参数或行为 | 普通 Agent V2 | Story |
|---|---|---|
| 默认温度 | 应用请求未指定时，显式覆盖为 0.7 | 未指定时不覆盖，让 ModelConfig 决定 |
| 每次模型调用超时 | 180,000ms | 180,000ms |
| 本地生成尝试 | 最多 3 次；取消、内容过滤或已尝试模型恢复等情况可能提前结束 | 最多 3 次；无效结构会把先前输出和修正要求带到下一次；同样可能提前结束 |
| 普通输出解析 | 支持数组、`options` 对象及 `{content}` 选项；去重、清理、截为每条最多160字符，再取前三条 | 必须恰好3条；超长、空、重复即无效；不会截断超长内容后当成功 |
| 语言检查 | 中文要求含汉字，日文含假名，韩文含韩文；其他语言当前没有同等文字校验 | 同类检查；不等于完整语义语言质量验证 |
| usage | Router 要求 usage；上游未提供时用 token 计数补算；多次模型结果可累计 | 同样累计；缺失时计数补算，计数失败可向上抛错 |
| 保存 | 可写 best-effort 调试节点与日志；不写聊天／V2事实状态 | 完成观察日志；不写聊天／Story状态 |

这里的“最多 3 次”是 Auto Reply 自己的调用循环；宿主模型适配器若配置了独立模型恢复，还可能在单次调用内尝试恢复。不能把它作为整体所有网络请求数的硬上限。

| 失败 | 用户业务如何理解 | 当前处理 |
|---|---|---|
| 场景未配置 autoReply 模型，或没传目标回复 ID | 接入材料不完整 | Router `agent.invalid_argument` |
| 角色 Prompt 不存在或没有名称 | 没有足够角色材料生成 | `router.unavailable`，可重试 |
| 普通 V2 指导未就绪／对应用户或回复文字缺失 | 暂不能为这条回复提供建议 | `turn_start_snapshot_not_ready`；Router 映射为可重试 `router.unavailable` |
| Story 正式正文未保存或查不到 | 缺少权威依据 | `auto_reply_generation_failed`；不回退请求侧正文，不等旧状态 |
| 三条结果仍无法通过检查 | 本次没有有效建议 | `agentic_auto_reply.failed`；不补固定兜底文案 |
| 用户取消 | 不再继续该请求 | 取消向依赖调用传播；业务不用显示过期建议 |

普通 V2 最多等待指导快照的时间、单次模型超时、Router／Story 请求总超时是不同限制，不能把 `autoReplyWaitMs=30000` 描述成“所有建议必在30秒内返回”。

此外，main 里还保留 Pioneer API 包装：它先查已保存最近历史，支持请求体 `enabled`，返回 `{enabled, options, fallback:false, parentRequestId, usage?}`，有自己的 JWT／来源和模型配置。这个入口的字段不能与统一 Router 混用；统一 Router 没有要求请求体 `enabled:true`。本能力页以 Router 的普通 V2／Story 接法为主。

[普通重试与usage](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/auto-reply.ts#L114) · [普通解析和语言检查](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/auto-reply.ts#L310) · [Story重试和修正](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/story/auto-reply.ts#L98) · [Pioneer独立包装](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/pioneer/api.ts#L129) · [SDK公共导出范围](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/package.json#L9) · [工具包公共导出范围](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/package.json#L12)

</details>

## 二开验收应看到的结果

先保存一条角色回复，再对它请求建议：得到三个文本，并确认聊天里没有因此多出用户消息。接着让用户选择其中一条，验证它才按正常聊天入口成为下一轮输入。

普通 V2 再测同一回复指导尚未就绪、目标回复之后已有新消息这两种情况；Story 再测请求侧旧正文与已保存正文不一致、正式正文未保存这两种情况。检查候选建议依据了正确的一轮，且失败时没有从其他轮次捡一个状态继续。

基线为 Harness `fa41d220d48327e58d994f936517b23f2f15f658`；本章区分调用源码、公共包导出与产品接线。配置样例不是所有线上场景已开启的证明。
