完整产品 展开本章接口与细节
本章定位 完整产品 / 持续角色对话
Agent V2|持续角色对话与后台连续性维护
Agent V2 用于持续的文字角色互动:用户说一句,角色先接住当前对话;后台同时判断人物关系、知识边界、剧情、承诺和场景是否需要整理,把有必要延续的信息保存下来,供后续回复使用。它可以支撑角色陪伴、角色扮演和开放式互动故事;具体题材、人物及表达方式由角色资料和模型提示词决定。
这里的完整产品是仓库已经接线的普通 Agentic V2 应用:角色回复 + 每轮后台判断 + 按需咨询专家 + 按需更新持续状态 。六位专家不是六个默认都要运行的步骤;图片、世界书、日记记忆和剧情 Story 也不因启用 V2 就全部自动开启。
本章核对的是 roleplay-harness 主干固定源码 fa41d220d48327e58d994f936517b23f2f15f658。已确认应用实现和配置入口;未取得目标生产环境的有效配置与运行记录,因此不把某个模型名、图片能力或可选扩展写成所有线上 V2 的默认配置。普通 V2 与 Story 在入口使用互斥选择:场景启用 storyV1 时进入 Story 接线,不再同时启动本文的普通 V2 专家后台。应用装配与分流
先看一次真实机制下的两轮演示 #
下面是用于解释输入、输出和保存关系的业务示例,文字由文档拟写,不是线上模型实测结果。工具是否被模型选中也属于示例分支;程序强制触发的条件在后文单独列出。
角色卡描述“守卫莉娅谨慎,负责旧城城门”;已保存的历史记录说明“用户借了一把铜钥匙,答应天黑前归还”。目前仍在城门前。
最关键的时间关系:本轮 Actor 不等后台才开始写,后台也不自动拿到它同时生成的新正文。 因此,后台可以根据“用户递出钥匙”整理信息,却不能提前把 Actor 稍后才写出的“莉娅接过钥匙、答应明早见面”当成已经看到的事实。到了下一次请求,只有业务已经保存并传回这些文字,后台才会在新的可见历史中得到它们。运行时对 Actor 的完成记录用于观测,没有把该正文追加进本次已启动的后台输入。轮初材料 · 前后台同时启动 · Actor 完成后的处理
如果用户很快发出下一句,版本 13 尚未保存,下一轮就先使用实际读到的旧版本 12,加上当时能取得的聊天历史。普通 V2 不会为了等上一轮后台而把下一句整体排队。
一轮内部怎样运转 #
1. 程序先确认这次请求应进入普通 V2 #
接入方提交本轮消息、会话、用户、角色和场景标识;服务从已允许的场景表找配置。只有选中的场景配置了 agenticV2,并走服务准备上下文的闭环路径,才有本文的后台处理。roleplay-harness/v2 是请求协议版本,agenticV2 是产品的后台配置,二者不是同一个开关。
服务创建三类对象:读取与保存状态的适配器、后台 Planner、安排前后台的 Coordinator,再包装普通的回复运行时。实际代码分别位于 dispatch.ts、planning/runtime.ts、planning/agentic-v2.ts,不是通过一个叫“Agent V2”的文件自动包含整个仓库功能。场景校验 · 普通 V2 装配
2. 分别准备回复模型和后台的输入 #
不是“把同一个完整 Prompt 发给两个模型”。两边共用轮初读取的状态,但其他材料各自组装。
上述字符数是应用主动截取的材料长度,不等于模型的 token 窗口。后台历史与输入 · 专家输入 · 前台模板和预算来源
3. Actor 写回复时,后台先判断要不要调用工具 #
普通 V2 在每次成功进入 generate() 的逻辑轮安排一次后台,协调器防止同一轮重复安排。这里没有“累计 X 轮才启动后台”的总开关 。前置准备失败、尚未进入生成时,自然不会启动该次后台;自动回复建议也有自己的入口,不应混算成普通 RP 生成。
后台第一阶段会给 Orchestrator:它自己的系统提示词、上表中的业务材料、当前允许使用的工具定义。请求设置 toolChoice: auto 和允许并行工具调用。它可以返回工具调用,也可以不调用工具。工具调用形如:
{
"name": "manage_callbacks",
"arguments": {
"reason": "用户递还借用物品并提出新的会面约定",
"focus": "区分已经发生的递出动作、待确认的接收动作和新的承诺"
}
}
这是工具名与参数的业务展示;实际模型消息使用 tool_calls 包装,function.arguments 是 JSON 字符串。程序按工具名到已注册能力中查找,解析参数,调用该专家的文本模型。参数不是直接执行的脚本,也不是模型自己访问数据库。
普通 V2 注册表固定装配六位专家与 update_director_state;场景 enabledTools 从这七个名字中选择。它没有在这条后台默认装上生图、世界书查找、独立 NPC 卡保存、任意 HTTP 请求等工具。公共 SDK 能扩展 Registry,不等于普通 V2 的场景 JSON 可以直接填任意新工具名。工具注册 · 模型请求 · 参数解析与执行
4. 程序有少量强制分析规则 #
因此“每轮后台都会跑”不等于“每轮六位专家都会跑”,也不等于“每轮都保存一个新状态”。强制规则与名字识别 · 第二阶段补状态调用
5. 六位专家究竟分别做什么 #
专家是“带岗位提示词、用各自 ModelConfig 执行一次分析的文本模型工具”。以下输出是解释用途的示例,不是固定 JSON 字段,也不代表自动写库。
所有成功专家先返回 { ok: true, tool, result, usage? };result 是分析文本。常规模式最多保留 5,000 字符。开启严格上下文治理后,结果超出配置长度会成为失败结果,不静默截成一份貌似完整的建议。工具职责与 schema · 模型执行与结果限制
6. 第二阶段决定是否把建议变成持续状态 #
已选中的专家并行执行。程序把工具调用与结果重新交给 Orchestrator;这一阶段只提供 update_director_state 一个工具,模型选择更新或结束。这是看完分析后的再次决策,并没有无限的“再叫专家、再分析”循环。
如果第一阶段没有咨询专家,但直接请求了状态更新,程序可以直接运行 Director,省略第二阶段;如果什么都不需要,保留旧状态。如果第一阶段同时请求专家和 Director,Director 被延后,不能在还没得到专家建议时与专家一起写状态。第一阶段执行和第二阶段工具范围 · 延后 Director 的消息
7. Director 生成整份笔记,程序校验后保存 #
Director 接收角色与开场白、用户 Persona、旧状态、近期历史、本轮用户输入、更新理由,以及已取得的专家结果。它输出一整份 Agentic RP State 文本 ,不是补丁,也不是用户看的回复。
这份持续笔记包含固定栏目:当前场景、角色生活与近期目标、柔性主线、未来发展备选、人物关系和代词归属、知识边界、位置与进入限制、新人物安排、时间与场外生活、待兑现承诺与物品、场面重心、角色主动程度、必须保持的事实、用户拒绝过的方向等。它允许提出后续方向,但这些方向不是已经发生的故事事实,也不是必须照做的工作流。
程序检查候选是否为空、是否至少 500 字符、是否超过配置上限(最多 8,000 字符)、规定栏目是否存在且顺序正确、是否混入内部推理标签,并比较新旧文本是否真的变化。这是结构和写入检查,不是另一个模型逐条核实新状态真实性的独立复审。 Director 的输入提示要求保留未变事实、只采用材料支持的变化,但不能把这条提示当成事实一致性已经被程序证明。Director 输入与输出要求 · 程序校验
保存什么、何时保存、下次取回什么 #
持续状态按 用户 + 会话 + 场景 读取和提交。正常提交携带“我基于版本 12 生成”这样的预期版本。存储服务若认为已经过时,返回冲突;本次 Planner 记为 stale_write_rejected,不会换成另一个版本再自行拼接重写。该仓库调用 Axon 接口,实际底层表、事务及跨实例仲裁由 Axon 实现,不能从客户端代码推断出未读过的数据库保证。状态读取与提交适配器 · RPC 字段
下次读取返回的是“版本信息 + 状态文本 + 账目”,不是模型的一段隐藏思考。以下展示应用内部转换后的数据形态,state 内容只节选了两行用于阅读,不能拿这个节选当作满足 Director 校验的完整状态:
{
"found": true,
"revision": 13,
"sourceTurn": 8,
"sourceMessageId": "assistant-turn-8",
"historyRevision": 16,
"state": "[Agentic RP State]\nCurrent scene: 旧城城门;用户已递出铜钥匙,表示明早再来。\nOpen callbacks, promises, items, injuries, and deadlines: 核对钥匙交接;用户表达了明早再来的意向。",
"toolsCalled": ["manage_callbacks", "update_director_state"],
"ledger": [
{
"type": "callback",
"key": "manage_callbacks:turn:8",
"status": "active",
"payload": {
"analysis": "区分用户递出、对方接收与双方同意会面,不能把未看到的回应补成事实。",
"sourceTool": "manage_callbacks",
"sourceTurn": 8,
"userQuery": "我把铜钥匙递给莉娅,告诉她明早再来。"
},
"sourceTool": "manage_callbacks",
"sourceTurn": 8
}
]
}
程序会把 state 和选中的账目拼成供模型读取的文字,再放进相应消息;并非把整个数据库对象直接给模型。Actor 这段状态材料的编译上限为 maxStateChars + 2000 字符,后台常规调用使用 6,500 字符;最终仍分别受各自模型输入预算约束。
账目按 event / npc / knowledge / world / callback / camera / agency 分类。下一次编译上下文时,不会把所有历史账目无限堆给模型:代码选最近 60 个活动项,每类最多再取 6 条、每条内容最多 900 字符,最后受总字符上限约束。专家成功但未执行 Director、候选没变化、候选无效或提交冲突,都不能直接等同于这些专家结果已经成为下一轮的持续资料。账目形成 · 读回时的账目选择
首次没有状态时,程序给一份保守默认笔记:从角色开场和可见对话自然继续、不凭空确定未出现的人物、秘密和场外事件、保留用户自主决定。提供默认笔记不代表已写库;仍需后续完成有效状态提交。初始状态
模型开始工作前,系统到底给了什么提示词 #
普通 V2 没有一条写死在 Planner 文件内、覆盖所有角色的“主 Agent 完整 system prompt” 。当前服务从 Axon 获取各 ModelConfig 关联的 PromptManager,再按岗位名读取模板:
模型调用形式是“系统岗位提示词 + 用户角色的材料文本 + 本阶段工具定义”。运行时会展开语言和角色等模板变量。源码可以证实材料与工具是如何组织的;具体环境里正在生效的整段岗位提示词,要从该环境 ModelConfig/PromptManager 读取 。本文没有把测试里的假提示词、某份旧 Demo 的模板或工具介绍冒充成生产 prompt。模板加载与岗位名称 · 后台首条业务材料
可配置什么,默认值意味着什么 #
这些是服务端场景配置,不是让最终用户随请求任意控制的参数。下表默认值来自构造函数和配置解析器,表示“未覆盖时的程序行为”,不是宣称生产每个场景都采用这些值。
配置解析还要求自动路由有升级模型;Jev 的参数模型不能填写 Orchestrator/升级 Orchestrator 的配置;Actor 引用要与场景一致。这些不匹配会在配置解析或依赖就绪检查时失败。默认值 · 配置范围与校验 · V2 与 Compact 组合条件
另外,仓库一些 JSON 留有 actorControlEnabled,但本基线的 AgenticV2Config 和解析返回值没有采用这个字段;它不能作为“关闭后台控制”的有效开关。前台模型选 Actor/图片/DIO 的工具循环由另一个配置 preActorDirector 控制,需在对应原子能力中说明,不能混进后台专家开关。实际解析返回值 · 独立前台循环接线
上下文窗口能不能改 #
可以改,但不在 maxStateChars 上改。Actor 的窗口上限来自有效 ModelConfig 的 context_limit,或服务接受的 runtime_decision.context_limit;模型的输出预算来自生成参数。历史实际保留多少还受 PromptManager 的历史插入规则、Compact 和最终预算计算影响。旧路径会保留系统消息,按剩余空间从较近消息向前选择;严格治理路径在最终请求上计数和检查。
后台又有自己的截取规则:最近 9 条可见消息、状态材料和专家结果长度限制。只把 Actor 窗口调大,不会自动取消后台最近 9 条的规则。 需要更长后台上下文时,应修改/扩展对应编译逻辑,或使用已实现的 Compact 严格接线;不能对客户承诺“换成长上下文模型就自动看完所有历史”。Actor 窗口来源 · 旧 V2 补状态与滑动历史
怎么接入这个完整产品 #
普通 V2 当前是 apps/emochi 下的应用实现,已经通过 Agent Router 服务接入;不能只安装 SDK 后调用一个未提供的 createAgentV2() 就获得上述全部行为。
接完整产品的路径是:业务聊天服务 → 已配置的 Agent Router 场景 → Harness 应用服务 → Axon 资料/状态与 Kaon 模型 → 流式事件与结果返回业务 。接入方继续负责用户权限、额度、聊天消息保存、最终展示和自身的计费/通知;本应用承担本文明确接管的上下文准备和 V2 状态处理。
接入要准备的数据和服务 #
服务配置片段 如下,只说明字段之间的关系。占位 ModelConfig 需要由接入环境真正创建;这不是复制即可运行的完整配置,也不是公共 SDK options:
{
"upstreamModel": "<该场景允许的模型路由>",
"defaultTimeoutMs": 580000,
"modelConfigRef": "<actor-model-config>",
"presetRef": "<actor-preset>",
"agenticV2": {
"modelConfigs": {
"actor": "<actor-model-config>",
"orchestrator": "<orchestrator-model-config>",
"specialists": {
"analyze_plot": "<plot-model-config>",
"judge_npc": "<npc-model-config>",
"audit_knowledge": "<knowledge-model-config>",
"simulate_world": "<world-model-config>",
"manage_callbacks": "<callback-model-config>",
"direct_scene": "<scene-model-config>"
},
"director": "<director-model-config>"
}
}
}
请求扩展片段 如下。它应放进 Agent Router 的完整请求中,外层还需要协议要求的项目、版本、会话、角色、消息及 generation.n=1 等字段。这里不把片段冒充为完整 HTTP 请求:
{
"extensions_version": "roleplay-harness/v2",
"extensions": {
"scenario_id": "<服务端已启用的普通-v2-场景>",
"turn_context": {
"user_id": "user-example",
"model_alias": "<业务侧有效模型别名>",
"assistant_message_id": "assistant-next-message",
"language": "zh",
"input_question": "我把铜钥匙递给莉娅,告诉她明早再来。"
}
}
}
实际服务使用 ConnectRPC AgentService/Agent Router 协议,不应把这些内部字段随意塞到普通 OpenAI /chat/completions 就认为能启用该产品。请求扩展解析 · 完整服务接入说明
场景文件存在不等于场景启用:文件方案需要服务的 HARNESS_ENABLED_SCENARIOS 选中,旧配置表 AGENT_ROUTER_SCENARIOS_JSON 也仍然支持。文件与旧表同名时以旧表的整项定义为准。实际生效配置在服务启动时加载,不是每个请求自动重新读取文件。场景启用与合并规则
失败、取消和并发时会发生什么 #
后台模型层有有限失败重试(通常原调用加两次),并对中止、上下文治理错误及明确不应重试的错误提前停止。整个后台任务此处未设置统一超时,但各模型调用有自己的超时;界面状态使用的 timeoutSeconds:900 是规划状态记录字段,不能当成后台运行器一定在 15 分钟停止的保证。模型和保存重试 · 保存 outcome · 后台任务生命周期
二开时哪些可以直接改,哪些需要补接线 #
换模型、岗位提示词和预算 :使用现有配置入口;必须保持岗位模板、状态格式和模型 provider 等要求。
启用世界书或记忆来源 :使用对应应用已有配置和依赖接线;Actor 能读取不代表后台自动拥有同样资料,需要检查实际输入。
为前台增加“写正文/配图/长期修改”的选择 :使用独立前台工具循环配置及相关服务,不是在后台专家名单里加三个名字。
希望新人物成为独立可编辑 NPC 卡 :现有 judge_npc 产出分析,普通 V2 保存的是持续状态和分析账目;还需角色卡 schema、持久化工具、身份归属和读取接入,不能直接承诺已经完成。
希望后台能检索世界书、根据剧情改写设定 :需为该后台提供检索/写入能力、输入与权限策略,再定义哪些结果可以变成事实;当前六专家并不具有这套自动执行链。
希望根据用户是否接受本轮正文来提交状态 :需实现并接通明确的接受边界、来源版本和取消语义;不能只借用“快慢通道”名称便认为已有。
希望把这些模型工具用于别的业务 :原子能力可分别使用;普通 V2 的两阶段、强制 NPC 规则和状态格式属于本产品策略,不是公共 SDK 对每个接入方的硬要求。
代码入口与当前交付核对点 #
源码已经具备普通 V2 应用与可选 Jev 编排适配;仓库场景中未默认开启 Jev。agentic-v2-luna-gemini38-pov-v1 名字带 V2,但文件启用了 Story,不作为本文普通后台的默认配置。Pioneer 等开发分支也不自动进入本文主干行为。对具体交付环境,还需取得有效场景、运行镜像、各岗位 ModelConfig/PromptManager、真实状态读写与取消/并发结果,才能形成“该产品此环境当前运行什么”的交付记录。Jev 接线 · Story 场景文件
完整产品 / 互动剧情
Story|Branches 的互动剧情产品
Story 让角色回复随着用户选择推进故事:保留当前场景,安排已经埋下的场外事件,提供这一段适合推进的剧情目标,再根据聊天里真正保存的正文更新世界和剧情进度。用户可以继续当前事件、回应邀请、改变追求;程序会检查改变是否有已有事件和正文证据支持。
业务归属为 Branches ;实现位于 roleplay-harness / main。本章以 fa41d220d48327e58d994f936517b23f2f15f658 固定源码为准。已确认应用路由、持久化接线及仓库配置样例;线上正在启用的场景、模型与流量应以部署配置为准。
产品类型:程序协调的完整剧情运行流程,内部组合六个 Story 工具与正文生成模型。 storyV1 启用后使用自己的协调器;沿用 Actor 的角色回复与模型恢复能力,但不会再同时运行普通 Agent V2 的 Orchestrator、六位咨询专家和 Director 后台。这一点直接由路由中的 if (storyV1) … else if (agenticV2) 决定。应用接线
1. 用户实际得到什么 #
以“玩家和守卫莉娅在城门核对铜钥匙”为例:
用户说“我把铜钥匙递给莉娅,但先不进城”,正文可以写莉娅核对钥匙、提出下一步,不能代替用户决定进入城内。
先前已确立的“巡逻队即将返回”可以在后续产生影响;程序控制它何时只是远处动静,何时进入当前场景。
剧情目标可以是“让钥匙上的旧徽记得到辨认”,但目标被放进模型输入不等于已经完成。只有后来保存的正文实际表现了辨认结果,才能记入进度。
用户持续改为追查失踪商队时,主线可以调整;已经发生的事实和未完成承诺需要继续承接。
正文仍是用户日常看到的回复。世界快照、剧情目标、计数器和结算证据由程序保存与使用,不要求用户阅读。可选的 Auto Reply 入口提供三条“用户下一句可以怎么说”的建议,建议本身不等于用户已经作出选择。正文约束 · 建议回复输入与输出
2. 一次请求怎样运行 #
新请求:本轮输入 + 带消息 ID 的历史 + 角色与会话身份
↓
读取该用户 / 会话 / Story 场景的已保存状态
↓
有上轮等待确认的正文?
├─ 有且证据匹配:从聊天服务读取正式保存正文,先结算上轮
└─ 首次或历史已改变:按真实角色资料与可见历史初始化 / 重建
↓
理解本轮行动 → 推进场景规则 → 必要时规划剧情目标
↓
选择本轮可推进的目标,组成“场景材料 + 剧情指导”
↓
Actor 结合原有角色提示词与聊天材料,生成本轮正文
↓
Harness 成功后:CAS 保存准备态和本轮消息 ID,等待正文被业务保存
↓
下一次正常请求:读回正式正文,再确认这轮实际发生了什么
本轮正文开始前会等待 Story 准备完成。 初始化、上轮世界结算、行动理解以及必要的剧情规划都可能发生在这里。它不是“先无等待地回复,然后所有剧情工作都扔到后台”。本轮生成完成后的 settle() 主要保存准备态;真正采用正文的世界结算发生在下一次正常请求的准备阶段。准备顺序 · 生成后保存
3. 三轮完整实例:每一步拿什么、产出什么、何时保存 #
以下是说明机制的示例内容,名字、台词和剧情目标不是固定生成结果。消息 ID 用 u1 / a1 等表示;实际由业务系统分配。
第一轮:用户把钥匙递给守卫 #
用户输入 u1: “我把铜钥匙递给莉娅,但先不进城。”
读原始材料。 程序从 Axon 读取角色卡、角色 Prompt、对话长度;从请求取本轮输入、带 ID 的历史。角色材料可能包括“莉娅负责城门登记”“不得泄露内城秘密”;开场消息交代两人站在城门。没有已有 Story 状态时,初始化模型根据这些材料建立世界与叙事快照。
得到内部状态。 世界前景可以是“莉娅正在核验用户提供的钥匙”;主线可以是“查明钥匙对应的旧城设施”,支线可以是“建立与莉娅的合作”。初始化不会把未来目标写成已经发生的事实。
理解用户行动。 pov_prepare_turn 对自由文本调用行动分类模型。这个例子仍留在当前事件,可返回 normal;“先不进城”不授权程序切换场景。
安排可推进目标。 新叙事快照 needPlan=true,程序调用 narrative_plan_cycle 创建一组目标,再调用无需模型的 narrative_deal_beat 选本轮适用目标。例如当前目标为“让旧徽记得到辨认”。
Actor 获得实际材料。 原有角色提示词、历史、本轮输入之外,再增加系统消息 [CURRENT STORY STAGE],其 JSON 包含 stage 和 narrative:当前事件、阶段、应承接的用户动作、可见的场外线索、当前剧情目标、主副线方向与情绪指导。完整隐藏世界状态不会原样交给 Actor。
用户看到 a1。 例如:“莉娅接过钥匙,指尖停在柄端的旧徽记上。‘这是旧档案馆的标记。你可以先在门外等,我去核对登记。’”
此时保存什么。 Harness 成功后,Story 保存世界准备态、叙事快照、pendingActor.messageId=a1、u1 的 ID 与内容哈希、当时给 Actor 的场景投影。状态报告为 waiting_for_acceptance。这里没有把 result.output.text 直接判定为权威剧情事实,也不把整篇正文复制进 Story 状态;正文由业务方保存到聊天消息中。
角色与请求材料 · 初始化提示词 · Actor 新增输入 · 保存内容
第二轮:先采用上一句,再处理新要求 #
用户输入 u2: “不用去核对了,先把钥匙还我。”
读回上轮正式正文。 程序检查历史中最近的 assistant 是否对应 a1,通过 conversationMessage 按用户、会话、消息 ID 读取业务实际保存的 a1;核对上轮用户输入 u1 和更早已接受正文的锚点。
结算 a1。 pov_advance_world 获得保存的 a1 完整正文、u1、当时准备态及截至 a1 的材料。u2 的“还我钥匙”不会被混进对 a1 的结算里。若下游把 a1 改成“莉娅只看了一眼,没有接过钥匙”,结算使用改后的正式正文。
更新世界候选。 记录已经表现的结果、尚未完成的场外事务、已传达的邀请等,增加世界轮数;把 a1 的消息引用与用户行动引用加入当前剧情事件的证据集合。只有事件结束或被打断时,才调用 narrative_settle_event 判断剧情目标和主副线进度,不是每句话都重算完整事件。
处理 u2。 再用新世界和 u2 准备本轮场景。“用户要求归还钥匙”成为应回应的动作,而不是上一轮已经归还。
用户看到 a2。 例如:“莉娅停下脚步,把钥匙递回给你。‘好,我们就在这里谈。’”
保存第二轮准备态。 a2 正文生成成功后,一次 CAS 保存“已采用 a1 后的状态 + 为 a2 准备的状态”。acceptedMessage 指向 a1,pendingActor 改为 a2。若第二轮 Actor 失败,前面算出的 a1 结算仍只在本次内存中,尚未独立持久化;下次可以从旧准备态重新恢复。
采用与切分证据 · 世界采用与事件证据 · 对应回归测试
第三轮:结果怎样真正用于后续 #
用户输入 u3: “我留在原地,问她档案馆为什么被封了。”
程序先读正式保存的 a2,采用其中“归还钥匙”等事实,再准备 u3。Actor 获得的当前事件与写作要求已经承接这些事实;它也会读取当前请求提供的聊天历史。若此前的事件在此时结束或被用户改道打断,事件结算模型读取这个事件累计的完整已接受正文和用户行动,判断哪些目标完成、主副线各自推进到哪里、是否需要更换方向。
这里的“承接”不代表系统自动生成一个通用道具数据库。Story 快照没有内置“铜钥匙持有人”标准字段;此类具体事实首先保留在正式聊天正文中,并进入后续可见历史和故事材料。若业务要可查询的物品栏或 NPC 小卡,需要另外接对应数据能力及写入规则。
例如“确认徽记来源”已有正文证据,就可以完成;“进入档案馆”从未发生,就不能因为原规划里有这项目标而被标记完成。新的主副线进度与下一批目标会参与后续发放;这才是准备结果持续影响正文的路径。事件判定与后续动作
4. 内部有哪些工作,分别由谁决定 #
Story 的工具调用顺序由程序条件控制,不是先把这六个名字交给通用主 Agent 让它任意循环选择。模型参与行动解释、剧情目标、世界语义和事件判定;计时、容量、去重、合法字段和持久化由程序负责。NarrativePlanCycleTool 里的 plan 指“一批剧情目标”,不意味着整段对话采用预写固定剧本。工具调用位置 · 剧情规划规则
5. 世界与剧情到底记录什么 #
世界记录:当前发生什么,场外还有什么 #
Actor 只获得 stage 投影:当前事件、应承接动作、选中的场外影响、最多预算内的隐藏线索及待传达邀请。全部暗线、未来种子和计数器不直接作为角色知识。主角色以实际角色名作为受保护 owner,不能被初始化为隐蔽暗流或种子;伴随角色是否跟随用户转场,必要时另由模型判断。世界结构 · 投影选择 · 角色保护
剧情记录:为什么推进,进展到了哪里 #
main / sub:主副线各保存主题、方向、预期体验、尚未兑现的回报、当前进度 cursor,以及连续多少个已结算事件没有推进 starve。
cycle.pending:当前要尝试推进的目标。每项注明服务主线还是支线、属于人物/关系/冲突/揭示/回报、前置目标、是否完成和优先级。回报类目标必须有明确铺垫依赖。
emotion:目标与已观察的情绪轨迹。三个轴分别描述好坏感受、平静或紧张、视角方掌握主动权的程度;供后续节奏调整,不是修改用户真实情绪。
clock:已结算事件 ID、已完成目标、最近用户方向、本轮已发放内容等,负责恢复与去重。
narrative_settle_event 的模型输出必须区分“目标完成”和“剧情线推进”。两者可以不同;每个完成目标和发生移动的进度游标都需要正文证据。叙事结构 · 结算输出约束
6. 默认什么时候触发,哪些参数能改 #
产品接入配置 storyV1 #
这些是应用配置实际接受的字段;未知字段会报错。配置解析
内部默认规则:不等于都有业务配置开关 #
应用协调器当前直接使用工具默认世界和叙事策略,只在初始化覆盖主角色保护与随机种子。要把这些规则做成客户可配置字段,需要在宿主新增配置接线;不能往现有 storyV1 对象里直接塞 dealEveryTurns 等字段。世界默认值 · 世界规则 · 叙事默认值 · 主副线改向触发
7. 模型上下文如何组装,窗口在哪里控制 #
Story 不把一次请求的全部材料原样交给所有模型。
12,000 / 3,000 / 18 是当前 compileStorySource 的代码裁切值,不是统一模型 token 窗口,也不是当前提供的场景配置项。事件正式正文按消息 ID 另行取回,结算并不使用裁短的历史片段替代完整正文。Actor 的 token 窗口与截断/压缩仍由原 Actor 模型配置和基础上下文准备处理;Story 新增系统材料明确通过 nonCompressibleSystemMessages 注入,会占用同一个可用窗口。材料裁切 · Actor 注入方式 · 完整事件证据读取
世界书的边界也在这里: 当前 Scenario 校验不允许 storyV1 与 worldbook 同开;世界书现成接线只支持专用 V2 场景。StorySource 也没有“本轮命中的世界书原文”专用字段,Story 协调器准备发生在基础 Actor 上下文准备之前。若二开给 Actor 接入世界书,仍需单独把必要条目传给 Story 的规划、事件结算模型,并保留条目与版本依据;只把条目放进 Actor 输入不够。配置限制 · 准备调用顺序
8. 接入完整 Story 产品,需要准备什么 #
服务端装配 #
使用仓库已有闭环 Agent Router;Story 要求扩展协议 roleplay-harness/v2。
为 Story 设置独立 scenario_id,配置角色 Actor 的 modelConfigRef、presetRef、显式 agenticV2 Actor/recovery 配置以及 storyV1。
接好 Axon:角色卡、角色 Prompt、对话长度、按消息 ID 读取正式正文、角色扮演状态读取与 CAS 写入。接好模型运行适配器,能解析 Story 及 Actor 的 ModelConfig。
业务方保存正式聊天消息。下一次请求传入的历史身份应指向真正保留的消息;否则 Story 无法沿用上次待确认状态。
Actor 的输入预设必须以当前 user 消息结束;Story adapter 会检查。
仓库提供完整配置样例 config/scenarios/agentic-v2-luna-gemini38-pov-v1.json。其中保留了 agenticV2.modelConfigs.orchestrator / specialists / director 等字段,但路由不会因为字段存在就执行普通 V2 后台。该样例的 580,000 ms 请求超时和具体模型名称是这个样例的配置,不是所有 Story 接入的固定值。配置样例 · 配置互斥检查
以下是放在完整场景配置里的 Story 片段 ,不是独立可提交的完整配置:
{
"storyV1": {
"enabled": true,
"modelConfigId": "your-story-model-config",
"initializerModelConfigId": "your-story-initializer-config",
"modelCallTimeoutMs": 45000,
"postTurnTimeoutMs": 90000,
"maxStateBytes": 60000,
"seed": 731
}
}
每次业务请求中与 Story 相关的身份 #
字段名称须按所用 Router SDK/协议序列化;上表的 requestId 等是 TypeScript 接收字段,扩展对象内使用代码列出的下划线名称。请求校验 · 闭环扩展解析
与其他能力的组合边界 #
不能同时配置 preActorDirector。 当前 Story 产品使用自己的闭环 Actor 路径,不兼容“前台主控自主选择 Actor/图片/DIO”的另一套协调器。
不能同时配置 sumiV1。 Sumi 由 Router 提前分流到独立图文应用,不能当作 Story 默认内置的一组工具。
普通咨询专家不是 Story 默认调用项。 如果二开需要调用某个专家补充资料,可以复用原子能力,但需要明确调用时机与结果如何进入 Story,现有应用没有自动加这一层。
不能直接与 worldbook 同开。 当前配置校验拒绝该组合;需要世界书时须扩展适配,明确哪些条目进入 Actor,哪些进入 Story 模型。
Compact、记忆等需按各自实际接线处理。 Story 的主副线不是用户长期记忆;不能以“配置了 Story”替代这些模块的输入、保存与启用条件。
互斥条件 · Sumi 独立入口
9. 保存、重生成、失败与恢复 #
Story 状态通过 AxonAgenticStateRuntime 读写,作用域为 userId + conversationId + scenarioId 。状态中有 promptId,读取时要求仍属于同一角色。CAS 使用读取时的 expectedRevision 与来源位置;旧请求不能覆盖新版本。读写适配器
需要分清三种进度:会话来源 sourceTurn 在该应用中按消息位置推进;world.round 在成功采用正文后推进;narrative.clock.eventCount 在一个剧情事件有证据地结算后推进。它们不是同一个“聊天第 X 轮”。
Story 状态保存上限默认 60,000 UTF-8 字节,事件证据采用消息 ID 和哈希,结算时再取完整文本。隔离记录单项上限 64 KiB;代码注明 Axon ledger 有 60 条、512 KiB 的保留界限,因此不是永久审计档案。保存与报告 · 恢复分支 · 事件隔离
10. 可以拿什么二开 #
完整 Story 应用已经接入角色、模型、聊天读取和状态服务,适合沿用现有 Router/Actor 服务体系的产品。要在其他宿主里只使用剧情机制,可以从 @flowgpt/agent-core-tools/story 导入六个工具、快照创建与解析函数;它们接受输入并返回新快照,没有内置业务数据库,也不会自己把其他工具串起来。
二开方可控制世界策略、叙事策略、Narrative 模型提示词、模型实现、消息接受边界及保存机制。现有应用的“下一请求才采用上一正文”是一种具体宿主策略;自己的产品如果能在同一请求明确取得最终被接受正文,可以在当轮调用推进/结算并保存。两种接法不能混写成同一份流程承诺。公开导出 · 独立组合示例
11. 接入验收看什么 #
连续两次正常请求后,能看到上次 pendingActor 被正式保存正文采用,新请求的 pendingActor 接替;读到的正文来自正确会话和消息 ID。
下游实际保存正文与 Harness 原始结果不同,下一次结算依据保存后的版本;下一次用户的新要求不能反向污染上轮事实。
看 story_turn_completed 的 outcome、状态版本、工具调用列表、诊断与 prepare/settle 耗时,区分正文成功与状态成功。
检查重生成、Continue、删改历史、Actor 失败、世界模型失败及 CAS 冲突时是否符合上表。
工具机制测试与真实模型体验分开验收;固定断言可以证明保存顺序与隔离,不能证明实际模型总能合理推进剧情。
本次已阅读源码,并运行固定快照自带编译产物的四组既有测试:story-deferred-review、story-scenario、story-scenario-dispatch、story-settlement-recovery,38 项通过。这不是生产验证,也没有重新编译或修改产品源码。关键测试源
原子能力 / 从需求找入口
原子能力:可以拿什么做二次开发
这里按“要完成的事情”找能力。每项都会说明它是独立包、服务接口、应用内模块,还是配置入口。**下载仓库、安装 SDK、接入已经部署的业务服务,是三种不同的接入动作。**安装 SDK 不会自动连到公司的模型、配置中心、世界书或数据库。
能力目录 #
包、源码与服务怎样对应 #
两个包配置的发布目标都是 https://npm.pkg.github.com,访问级别 restricted,要求 Node.js ≥22。实际安装先取得 registry 读取权限,再确认要用的功能已经进入所安装版本。**主干中新增的 Slow gate 不应仅凭 package.json 仍写 0.3.0 就认定 0.3.0 发布包包含它。**本页按固定源码说明能力;交付具体发布包时须对照该包版本与构建身份。
仓库、包与应用的归属 · SDK 包入口 · Core Tools 子入口与安装条件 · SDK 真实导出
开关写在它控制的能力下面 #
preActorDirector 控制前台工具编排,compact 控制历史压缩,storyV1 选择 Story 产品流程。它们不在“模型可以调用的工具名”里。每个能力页都列出对应字段、默认行为、开启条件、互斥限制和生效时机。
固定流程的 executionGraph 是仓内 ScenarioProfile 的程序契约;现有 Router 的场景配置表不是任意工具图编辑器。需要固定串联业务工具时,按 编排与扩展 的源码接法注册执行器、定义节点,并把结果送入实际模型输入。
二开前至少走通一次:提供实际输入 → 调用能力 → 检查返回 → 按业务规则采用或拒绝 → 保存 → 再发一轮读取验证。某项工具 ok:true,只证明该次工具返回成功,不自动证明正文被接受、图片已展示或状态已持久化。
原子能力 / 应用模块
Actor:生成正文与前台工具编排
**业务用途:**让角色根据当前输入和资料说话、行动;需要图文节奏时,把一轮正文拆成合适片段,再安排图片。例如用户说“让守卫接过钥匙,并配一张图”,Actor 负责写守卫的反应,图片能力负责生成相应画面。
**形态与接入位置:**当前 Actor 是 apps/emochi/actor/roleplay-runtime.ts 的应用正文运行时。actor 是前台工具循环中的函数名;Actor 的 beforeHooks 是执行前改写参数的接入点。Actor 本身不是 Hook,也不是在这个循环里选择其他工具的主控模型。
它是已有应用接线,不是 SDK 根入口可直接 import 的公共 Actor 类。复用完整能力需要沿用应用的 Router/Axon/模型/聊天交付依赖,或自己实现这些适配;只安装 @flowgpt/roleplay-harness 不会自动得到这条业务链路。
两种实际运行方式 #
控制模型的系统提示明确要求:通过函数控制一轮 RP,不直接写用户可见正文;读实际工具结果再选下一步,不按固定顺序机械调用。代码变量叫 planner,这里实施的是逐步选工具的循环,不需要先生成一个完整长期计划。它和普通 V2 后台的 Orchestrator 是两个不同接线。
这里有三个层次:前台控制模型选 actor → 程序执行正文运行时 → 正文模型写回复 。另外,Harness 的 options.actor.beforeHooks 发生在创建和执行 Actor 节点之前,用来准备或覆盖参数;当前前台循环没有把这些 Hook 当作主模型可以任意选择的菜单。
是否开启前台循环 · 控制提示词与逐步调用
模型真正可以调用什么 #
菜单至少包含 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 参数,不是另一项工具。片段形成合格的完整视觉情节且循环还有下一步时,程序可以自动安排图片;已经配过图的片段不能重复调度。正好用尽步骤时,不能保证还有一次自动排图机会。
工具定义与参数解析 · 执行、步骤与预算
怎样接入和配置 #
应用接入使用 Scenario 中的 modelConfigRef、presetRef 等选择正文模型与模板;真实角色、会话、消息、历史等由业务请求和 Axon 准备。它们不是模型自己可以随意修改的工具参数。
配置 preActorDirector 后,当前 dispatch 装配前台 functionLoop。其 modelConfigId 是前台控制模型,skills 含已授权预设;配置含 afterActorTools: ["generate_image"] 的图片预设、imagePromptProducerPrompt、图片后端依赖,以及本轮 assistantMessageId,才形成当前图片调度接线。DIO 也需要独立服务接线。不能只向模型展示工具名,却不给程序实际执行器。
宿主配置字段与 Hook 的准确位置
Story 配置明确排斥 preActorDirector;Sumi 和专用 Worldbook 场景也有组合限制。需要跨产品组合时应开发和验证对应接线,不能把多个字段堆在同一 Scenario 里。
图片预设与前台循环装配 · 预设选择接口 · Actor 参数与 beforeHooks · 组合限制
保存、失败与下一轮 #
Actor 生成结果经整轮后处理交给聊天服务,聊天正文是否被采用和落库由业务链路负责。Actor 工具本身不自动创建 NPC 卡片、发布世界书或更新普通 V2 的持久状态。前台控制在没有生成可见正文之前失败时,代码可以回退到直接 Actor 路径;已有正文后的失败需按当前运行时的规则保留或收束已生成片段。
具体而言:控制模型报错、返回非法工具请求或用尽步骤,而没有任何可用 Actor 结果时,进入直接生成回退;已有结果时结束循环并后处理已有片段。后续 Actor 调用发生空回复或超时、且前面已有完整片段时,会把失败回执交给控制模型继续决定。首次 Actor 执行失败、其他不可恢复错误或外部取消会传播;不能统一理解成“所有失败都返回已有正文”。已被外部图片或 DIO 服务接受的任务,也不因这段文字生成后来失败而自动撤销。
**二开验证例子:**输入“守卫接过钥匙,并配一张图”,分别检查正文是否先可用、是否只安排了一张图、图片结果是否绑定到正确消息、图片失败后用户看到什么;再发下一句,确认读取的是业务已保存的正文。不要用“模型调用了 actor”代替上述交付检查。
控制失败与正文回退 · 整轮后处理
原子能力 / 公共工具包
六类分析工具:给出建议,由业务决定采用
**用途:**当你的产品已经有角色、对话或故事材料,需要额外检查“人物有没有突然知道秘密”“承诺有没有忘记”“场面是否该转换”等问题,可以直接调用对应分析工具,不必先接完整 Agent V2。
交付形态: @flowgpt/agent-core-tools 的公共工具。它们调用宿主提供的模型,产出文本建议和使用信息;全部标记 advisory: true,不会自行修改聊天、人物卡、世界书或数据库。
这里的“公共”指包的可复用导出 API。仓库发布配置是 GitHub Packages、restricted 访问,接入发布包需要相应读取权限,并不代表公共 npm 可匿名安装。
每个工具具体干什么 #
六工具定义 · 公共导出与工厂
调用前究竟要准备什么 #
模型可见参数是 reason(为什么咨询)和可选 focus(这次重点)。角色资料、历史、已有状态、用户身份等由宿主提供,不能指望专家名称自动带来数据库访问。
默认 RP 上下文包含目标角色、开场白、当前用户输入、用户 Persona、已有状态、压缩摘要与最近可见对话。默认历史投影只取过滤后的最后 9 条 user/assistant 消息;Persona 最多 4,000 字符;状态投影使用 6,500 字符预算。这些是字符投影规则,不是模型完整上下文窗口。
如果你的业务需要完整世界书条目、外部事实或更长历史,先由宿主取回,再通过 compileContext 自定义 user message。系统岗位提示词由调用者另行传入。编译器是同步格式化函数,本身不做网络检索。
上下文与模型适配接口 · 默认输入投影
最小接线片段 #
下面是宿主接线片段 ,context、model、systemPrompt 必须由业务准备,不是安装包自动提供。
import { createAgentCoreTool } from '@flowgpt/agent-core-tools';
const tool = createAgentCoreTool({
name: 'audit_knowledge',
modelConfigId: 'your-knowledge-model',
modelCallTimeoutMs: 15_000,
});
const result = await tool.execute({
context, model, prompt: systemPrompt,
arguments: { reason: '核查信息边界', focus: 'Mira 是否看过未打开的信' },
signal,
});
if (!result.ok) throw new Error(result.error);
// result.result 是分析文本。这里交给业务采用,不会自动写数据库。
需要让自己的 Agent 选择工具时,用 /pi 的 toPiTool(tool, prepare) 包装,再加入 Pi 的工具列表。prepare 负责绑定真实业务上下文、模型和提示词;包装层校验 {reason, focus} 参数并把失败转换为工具异常。直接调用适合固定流程,注册后由模型选择适合动态流程。
参数校验由 toPiTool 包装执行,prepare 负责准备业务材料;如果绕过包装直接调用 .execute,应由宿主检查工具参数,不要把暴露给模型的 JSON Schema 当作该直调入口已自动执行的校验。
完整假模型直调示例 · 模型选择工具的完整示例 · Pi 工具适配
配置、保存和失败边界 #
**具体闭环:**业务提交“未打开的信”及现有记录 → audit_knowledge 返回“没有依据得知信中秘密” → 业务把它作为下一次写作约束 → 下一次生成遵守该信息边界。如果只调用工具而丢弃结果,就不会改变产品行为。
重试与失败 · 输出、长度与保存边界
独立工具配置的必填项与治理边界
name、modelConfigId、modelCallTimeoutMs 都是 AgentCoreToolConfig 的必填项。这里没有独立工具默认 180 秒的构造规则;180 秒是普通 V2 应用配置的默认值,客户直调用多少必须自己提供。compileContext、contextGovernance、onDiagnostic、shouldStopRetries、isContextGovernanceFailure 可选。
contextGovernance 若启用,需给出 mode: "shadow" | "enforce"、safetyMarginTokens、maxToolResultChars。工具把输入窗口预算要求传给宿主 model.complete;宿主模型适配器需要实际实现计数与拦截,不能因为传了对象就认定第三方模型端已经受控。工具自己会检查返回文本长度:shadow 仅记录诊断,仍按默认最多 5,000 字符整理;enforce 超配额返回 tool_result_oversized,未超限则保留完整结果。
类导出分别为 PlotTool、CharacterTool、KnowledgeTool、WorldTool、CallbackTool、SceneTool,根入口同时导出 createAgentCoreTool;createAgentCoreToolWithContext 要求提供 compileContext。角色型默认 context 的限制是材料投影,并不限制客户自定义编译器可提供的全部业务字段。
独立工具必填配置 · enforce和shadow输出处理 · 类与工厂导出 · 根入口、Pi适配与发布边界
原子能力 / 公共工具包
Story 原子能力|六个可单独组合的剧情工具
这组能力适合自行开发互动故事、游戏对话或剧情型角色产品:把“当前场景怎么准备”“剧情目标怎么安排”“正式正文发生了什么”拆开调用。它们已在 roleplay-harness / main 导出,入口为 @flowgpt/agent-core-tools/story ;不是六个咨询专家,也不是六个自动注册到所有 Agent 的函数调用菜单。
当前源码包版本为 0.2.1,仓库配置的发布目标是受限 GitHub Packages。宿主需获得包与模型访问权限;此处不把“源码有导出”写成“任意客户已能匿名安装”。固定源码基线:fa41d220d48327e58d994f936517b23f2f15f658。包导出与发布配置 · Story 导出文件
1. 能拿走什么 #
所有工具都返回结果;它们不创建数据库、不自己存会话、不决定业务是否接受正文、不递归执行返回的下一步动作。业务自行安排调用,或采用已有 Story 产品协调器。应用组合入口
2. 公共调用形状 #
import type { AgentCoreModelRuntime } from '@flowgpt/agent-core-tools';
import {
createStoryWorldSnapshot, parseStoryWorldSnapshot,
initNarrativeSnapshot, parseNarrativeSnapshot,
PovPrepareTurnTool, PovAdvanceWorldTool,
NarrativePlanCycleTool, NarrativeDealBeatTool,
NarrativeSettleEventTool, NarrativeRebranchTool,
projectStoryStage, restoreStoryPreparedTurn,
POV_WRITER_STAGE_INSTRUCTIONS,
type StoryContext, type StoryModelConfig,
} from '@flowgpt/agent-core-tools/story';
每次调用需要 context:
需要模型时注入 StoryModelConfig:model 是实现 AgentCoreModelRuntime.complete 的对象,modelConfigId 是宿主可解析的模型配置 ID,timeoutMs 可选,isRecoverableModelError 可选。凭证、供应商连接、模型配置解析由宿主负责。JSON 结果校验不通过最多尝试一次修复,最多两次模型响应;工具返回诊断和用量。调用契约
3. 六个工具:准确参数、结果与使用例子 #
A. 准备本轮场景 PovPrepareTurnTool #
构造: new PovPrepareTurnTool(modelConfig?, hooks?)。
输入: { context, snapshot, action, worldContext? }。snapshot 是 StoryWorldSnapshot;worldContext 是宿主提供的设定与事实材料字符串。action 三选一:
{ type: 'free', text: '我把钥匙给她,但先不进城。' }
{ type: 'intent', text: '我留在城门询问。', intent: { kind: 'normal' } }
{ type: 'choice', index: 0 }
free + 有 modelConfig:调用模型判断 normal(留在当前事件)、cue(主动转向已有事件)或 signal(回应上轮确实传达的邀请);需要时判断同伴是否跟随。
intent:宿主已经判断行动,直接提供结果;targetId 必须匹配可用事件,不能靠任意字符串创建一个目标。
choice:索引来自快照已保存的 choices。
不提供模型且用自由输入时,采用 normal;工具不会自己理解一个自由文本转场意图。
输出 StoryPreparedTurn: snapshot、intent、userText、flags、stage、surfacing、dice、events、diagnostics、usage。给 Writer 的主要是 stage:当前事件与阶段、应承接动作、可见的场外影响、选中的暗流线索、待传达邀请。全快照用于恢复,不宜原样作为角色已知信息。
保存时机: 这是准备候选。宿主应保留它,直到对应正文被接受后交给 Advance;同一 turnId + action 可恢复准备,不重复推进骰子和时钟。不同轮不能在未解决的 pending 上继续 prepare。输入与分支 · 数据结构
B. 根据正式正文推进世界 PovAdvanceWorldTool #
构造: new PovAdvanceWorldTool(modelConfig?, hooks?)。
输入: { context, prepared, writer, worldContext? }。context.turnId 必须匹配 prepared。writer 至少含完整的已接受 prose,也可含 choices 和 nextForeground;这两个字段是结构化 Writer 宿主的可选能力,现有 Story 应用主要交 { prose }。
writer: {
prose: '莉娅把钥匙递还,示意你可以留在门外等消息。',
// 可选 choices: [{ text: '我留在门外等。', expect: '等待', isSignal: false }]
}
输出: { outcome, snapshot, events, diagnostics, usage }。
committed:工具算法已经计算新世界快照;不代表数据库写成功 。
duplicate:同一已完成轮没有再次结算。
pending:没有可靠世界提案,保留原 prepared;宿主保存已接受正文证据并重试同一轮,不能把它当成“无变化成功”。
使用模型时会检查邀请是否实际传达、场外结果、合法后继事件等;模型不能随意改时钟、ID、容量或机械阶段。公开构造允许不传模型,但这时没有模型解释世界内容,只使用已有机械材料和显式 Writer 输出,不能与完整产品的语义结算能力画等号。执行与 outcome
C. 创建剧情目标 NarrativePlanCycleTool #
构造: new NarrativePlanCycleTool(modelConfig, options?, hooks?),options 可含 { policy, prompt }。
输入: { context, snapshot, setting }。snapshot 是 NarrativeSnapshot,setting 是设定和当前已知故事材料字符串。
输出: { snapshot, nextActions, diagnostics, usage }。新增目标写入 snapshot.cycle.pending;包含 id / serves / angle / gist / reward / core / done / priority / prerequisites。例如“让守卫辨认钥匙徽记”是 reveal 目标,“守卫给予通行许可”若作为回报,须引用已经发生或安排中的铺垫目标。
工具只在 needPlan=true 且本轮未规划过时调用模型。默认新增 5–7 项、至少一项服务副线;原未完成事项保留。无效输出修复后仍失败,则仍保持 needPlan,不能假定已有可用计划。实现与验证
D. 选本轮剧情目标 NarrativeDealBeatTool #
构造: new NarrativeDealBeatTool({ policy }?, hooks?)。不接受模型配置,不调用 LLM。
输入: { context, snapshot, phase },phase 为 start / develop / end。
输出: 通用结果加 packet:
{
pending: { id, serves, angle, gist, reward, fresh } /* 或 null */,
emotionGuide: { enabled, valence, arousal, dominance, label, beat },
mainDirection,
subDirection,
bias: { preferBackgroundKind: 'conflict' /* 或 'progress' / null */ }
}
程序按前置目标是否完成、优先级、欠缺回报、情绪匹配与主副线多久没推进来选择。没有满足前置条件的目标就返回 pending:null;不会硬塞一个未铺垫回报。将 packet 与世界 stage 一起交给 Writer,模型才有机会采用它。返回快照也要保存,因为里面记录了本轮选择和轮换计数。选择机制
E. 判断一个事件实际完成了什么 NarrativeSettleEventTool #
构造: new NarrativeSettleEventTool(modelConfig, options?, hooks?),支持 { policy, prompt }。
输入: { context, snapshot, eventId, prose, userActions, interrupted? }。
eventId 是这次事件的稳定身份;不同发生次数不能复用同一个 ID。
prose 是该事件累计的完整已接受正文,不一定只有一条回复。
userActions 是对应用户行动数组;不要把事件结束后的新输入混进来。
interrupted=true 表示事件被打断,不代表已完成所有目标。
输出: 通用结果加 outcome: 'settled' | 'unknown' | 'duplicate'。成功快照更新目标完成、主副线进度、情绪观察及事件计数;nextActions 可能要求 plan-cycle 或 rebranch。未知评估不记作“剧情没有推进”,也不增加未推进计数;宿主要保留证据重试。
例如正式正文只是“守卫看见徽记,但没有认出”,不能把“辨认来源”标完成。模型返回的完成目标、移动游标都必须带证据。事件输入与结果
F. 调整主副线 NarrativeRebranchTool #
构造: new NarrativeRebranchTool(modelConfig, options?, hooks?),支持 { policy, prompt }。
输入: { context, snapshot, setting, scope, direction? }。
scope:'main':必须提供非空 direction。例如用户持续转向“寻找失踪商队”;模型调整主线与相配副线,承接已有进度与承诺。
scope:'sub':只替换副线,主线保持;无需 direction。
输出: 新 snapshot、nextActions、diagnostics、usage;连续性说明放在 narrative_rebranched 诊断中。工具本身不决定“用户是不是该改主线”:现有 Story 协调器依据已结算事件中的连续方向触发,其他宿主也可按自己的显式业务规则调用。rebranch 是剧情方向调整,不是创建 Git 分支或自动复制会话。实现
4. 独立组合:哪些东西一定由二开方提供 #
最小组合顺序:
读已保存 world / narrative
→ PovPrepareTurnTool
→ 如果 needPlan:NarrativePlanCycleTool
→ NarrativeDealBeatTool
→ 把 stage / packet 交给自己的 Writer
→ 业务接受完整正文
→ PovAdvanceWorldTool
→ 事件到达边界:NarrativeSettleEventTool
→ 根据 nextActions 调整主副线或安排下一次规划
→ 校验 outcome 并事务/CAS 保存
已有完整应用把“接受完整正文”推迟到下一请求;公开工具不强制这样做。源码示例 examples/sdk/04-story-turn.ts 演示同一次调用内接受正文并存储,使用假模型验证控制流,没有调用真实模型或数据库。可运行组合示例
5. 配置和扩展边界 #
当前 storyV1 没有 直接暴露 world/narrative 的所有内部 policy,不能把公共工具构造参数与应用 JSON 开关混为一谈。世界工具也没有 options.prompt 参数;它们的语义提示词在源码中,不能照抄叙事工具的构造方式来修改。
projectStoryStage(prepared, bias?) 可在 Writer 前改变同等候选间的 conflict / progress 偏好;结果应保存。restoreStoryPreparedTurn(snapshot, bias?) 是恢复,bias 仅用于断言一致,不重新选择、不再次消耗随机数。新 prepared 会记录独立准备基线,校验时重放机械决策,防止保存的舞台与标记被一起改写后冒充合法结果。世界投影与恢复 · 生命周期 · 应用配置
默认关键值为 dealEveryTurns=3、每次新增目标 5–7、主线持续新方向 3 个已结算事件、副线 5 个已结算事件未推进触发替换、世界 surfaceBudget=2、background 最多 3、undercurrent 最多 5。它们的单位不同,不能统一解释成“累积 X 轮启动慢系统”。叙事默认策略 · 世界默认策略
原子能力 / 独立服务
世界书:保存设定,并在每次生成前选择需要交给模型的原文
世界书适合保存作者事先写好的地点、人物、规则和背景。例如“灰港没有电力”“铜钥匙只能开侧门”“守卫放行前要核对手令”。接入方把这些资料存进服务;用户说“我把铜钥匙递给守卫”时,服务选择本轮相关条目,把原文和放置位置交回来。接入方再把它放进回复模型的输入,模型才有机会按这条规则写出“手令呢?”这样的回应。
可复用的交付物是独立 HTTP 服务及源码;它本身不生成 RP 回复,也不是默认挂在主 Agent 工具菜单里的一个 function tool。 当前 Harness 已有一条专用世界书接线;客户也可以从自己的后端直接调用服务,把结果交给自己的模型或工作流。服务入口 · Harness 接线
能拆出来使用的能力 #
一次使用从准备到生效 #
运营准备设定 :把独立规则写成条目。为“灰港基本设定”标记常驻;为“铜钥匙与城门守卫”填写关键词“铜钥匙、城门”。原文由作者/运营提供,不是服务自动生成。
保存一本书 :调用原生创建接口。服务把原文、条目和版本写入 PostgreSQL,返回书籍 ID 与版本 1。以后修改是发布版本 2,不覆盖版本 1。
决定谁使用它 :若走现成 Harness 接线,把书关联到角色/作品的 prompt_id;若走独立服务,自己的程序在每次请求中直接提供书籍 ID 与版本。
用户发来一句话 :“我把铜钥匙递给守卫。”宿主取得这次允许使用的书籍版本,提交本轮输入与状态。
服务选出本轮原文 :“灰港基本设定”因常驻进入候选,“铜钥匙与城门守卫”因关键词进入候选;通过条件与预算后返回两段原文。返回原因分别是 constant、keyword。
回复前注入 :宿主把这两段文字放到角色资料前,再与角色、历史、本轮用户输入一起发给回复模型。模型生成的守卫回应受这份资料影响;选择成功不等于模型一定遵守,仍需业务侧验收实际回复。
下一轮再算 :书籍原文继续留在数据库;本轮选中的两段原文不会因此变成永久聊天历史。下一次输入重新选择,已关联的固定版本/最新版本策略决定用哪份书。
原文选择和位置装配发生在生成前,会占用本轮准备时间。它不是等后台累计若干轮再执行的记忆任务。逐轮请求与选择
运营准备什么数据 #
推荐从原生 JSON 开始。 下面整个 JSON 对象可以直接作为 POST /v1/books 的请求体。name 是书名;entries 中每项是一条设定;content 就是希望模型读到的原文。不要把书籍 ID、租户或 Prompt ID 写进这个对象,它们属于保存结果或关联关系。
{
"name": "灰港世界设定",
"description": "演示:常驻世界规则与按铜钥匙触发的城门规则。",
"entries": [
{
"id": "greyport-background",
"title": "灰港基本设定",
"content": "灰港是一座蒸汽城市,城内没有电力,夜晚依靠煤气灯照明。",
"constant": true,
"position": "before_character",
"audience": ["actor"]
},
{
"id": "copper-key",
"title": "铜钥匙与城门守卫",
"content": "铜钥匙只能开启灰港城门的侧门。守卫在放行前必须核对来访者的手令,不能仅凭钥匙放人。",
"keys": ["铜钥匙", "城门"],
"position": "before_character",
"audience": ["actor"],
"priority": 20
}
]
}
运营首先需要填好 条目名称、原文、何时使用 。常驻条目用 constant: true;动态条目用 keys。每项给一个稳定且不重复的 id,方便后续修改与定位。同一条目可以写多条触发词,命中任意主关键词就有机会进入本轮候选。
原生 JSON 和文件导入是两个入口。 这个示例应提交到 /v1/books,批量原生书则提交 {"books":[上述对象]} 到 /v1/imports。不要把原生 JSON 当作 ST 文件上传到 /v1/imports/files:文件入口会按 ST/角色卡规则重新归一化,字段含义和保存方式不同。原生模型 · 两个导入入口
原生数据格式、校验与字段默认值
数据模型拒绝未知字段。把 tenant_id、book_id、prompt_id 随意混入创建书籍 JSON,会触发校验错误,而不是自动建立绑定。完整契约
已有 ST/角色卡资料怎样导入 #
ST 指 SillyTavern。当前代码支持它的静态世界书资料导入,不是完整运行 SillyTavern。 文件可以是独立 world-info JSON,也可以是 CCv2/CCv3 角色卡 JSON 或 PNG 中嵌入的 character_book。这里提取的是世界书;角色卡的其他角色字段不会自动变成此服务管理的角色产品。解析格式与报告
预览报告中的 runtime_blocked_count 是需要额外运行环境的条目数。EJS 模板、MVU 状态更新、CharInfo 人物生成协议、脚本/前端面板、sticky/cooldown/delay 等动态功能,不会因为导入成功就执行;带这类要求的条目会被默认上下文选择排除。普通 {{char}}、{{user}} 可以替换,但调用方必须传入对应变量。动态能力识别
文件上传请求、重复导入和发布新版本
以下命令运行在能访问内网服务的开发环境;tenant_id=local 是示例租户。WB_BASE 应设置为实际服务地址。
WB_BASE='http://127.0.0.1:8876'
curl --fail-with-body -sS "$WB_BASE/v1/imports/preview?tenant_id=local" \
-F 'file=@world-info.json'
curl --fail-with-body -sS "$WB_BASE/v1/imports/files?tenant_id=local" \
-H 'Idempotency-Key: greyport-file-import-v1' \
-F 'files=@world-info.json'
单文件默认上限 16 MiB,批量文件和整个 HTTP body 默认各 48 MiB;一次 1–10 个文件。PNG 只读取支持的内嵌卡片元数据,不执行脚本,也不抓取其中的远程链接。
创建/导入接口支持 Idempotency-Key:同租户、同 key、同内容返回同一结果;同 key 换内容返回 409。原生 JSON 不传 key,每次会新建一本书。原文件按文件 SHA-256 去重;重复上传同一个原文件返回最初的 revision 1,而不是后来编辑出的最新版。
编辑已有书要调用 POST /v1/books/{book_id}/revisions,提交新版本全量书籍内容 ,并加 expected_revision。例如当前版本 1,则提交 expected_revision: 1,成功返回版本 2;如果别人先改过,返回 revision_conflict,重新读取后再发布。此接口不是单条条目 PATCH;省略的旧条目不会自动继承进新版本。创建与去重 · 全量修订
怎样独立接入:一次创建与选择的最小请求 #
这条路径不依赖 Agent V2、Story 或 Axon。你的后端调用世界书 HTTP API,再负责把结果装进自己的模型请求。
第一步:创建。 将前面的原生 JSON 保存为 worldbook-native-example.json,提交:
WB_BASE='http://127.0.0.1:8876'
curl --fail-with-body -sS "$WB_BASE/v1/books?tenant_id=local" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: greyport-native-v1' \
--data-binary @worldbook-native-example.json
返回的结构如下。UUID 是示意,实际使用服务返回的值:
{
"books": [{
"book_id": "00000000-0000-0000-0000-000000000001",
"revision": 1,
"deduplicated": false,
"import_report": {"format": "native", "entry_count": 2}
}],
"replayed": false
}
第二步:选择。 把真实 book_id 替换进下列请求,保存为 select.json。书籍可固定 revision;为了同轮结果稳定,接入方应先解析出具体版本,再在同一轮中复用。
{
"books": [{"book_id": "00000000-0000-0000-0000-000000000001", "revision": 1}],
"query": "我把铜钥匙递给守卫。",
"history": [],
"state": {},
"audience": "actor",
"strategy": "rules",
"scan_depth": 4,
"token_budget": 1500,
"seed": "example-turn-1",
"template_vars": {"char": "守卫", "user": "旅人"}
}
curl --fail-with-body -sS "$WB_BASE/v1/context/select?tenant_id=local" \
-H 'Content-Type: application/json' \
--data-binary @select.json
下面是返回中一条 blocks 元素的完整形状。真实响应还包含另一条常驻设定和整体诊断:
{
"id": "00000000-0000-0000-0000-000000000001:1:copper-key",
"book_id": "00000000-0000-0000-0000-000000000001",
"revision": 1,
"entry_id": "copper-key",
"title": "铜钥匙与城门守卫",
"content": "铜钥匙只能开启灰港城门的侧门。守卫在放行前必须核对来访者的手令,不能仅凭钥匙放人。",
"position": "before_character",
"depth": 0,
"role": "system",
"order": 0,
"reason": "keyword"
}
第三步:使用。 content 是作者的文字,并未经过另一个 LLM 总结。自建宿主按 position 放置这些 blocks,再加角色资料、历史和本轮输入,最终调用自己的回复模型。简单单位置接法也可使用聚合 context,但如果书里使用不同深度或插槽,就必须按 blocks 的位置装配,不能把所有内容统一贴在末尾。
本文样例已用此固定版本的 BookCreate、SelectRequest、真实选择函数和 OpenAPI 结构验证:选出两条,分别因为关键词与常驻;示例聚合文本为 191 tokens。此验证没有连接数据库或调用模型,不作为线上部署或模型遵循效果的验证。返回实现
常驻、动态、必需:谁决定本轮读哪些条目 #
决定者分三层:作者决定条目的规则,接入方决定本轮材料与预算,服务程序按规则选择。 当前这步不需要一个 LLM 阅读全书再挑条目。
执行次序:
排除停用、受众不符、状态条件不符、不支持动态运行环境、概率未通过、空内容或无法放置的条目。
激活必需、常驻、关键词命中和 hybrid 召回的条目;按配置允许有限递归激活。
展开同版本内的显式依赖,把“条目+依赖”作为一组,检查冲突。
按优先级和预算选择:必需优先,其次可选常驻,再处理其他候选;默认常驻可选条目最多使用总预算的 35%。
返回完整原文和实际选择原因。装不下的可选条目整条被排除,不自动缩写成摘要。
例如“守卫手令规则”命中了,但被标记为仅在 state.quest.started = true 时允许使用;本轮宿主没有交这项状态,规则仍不会进入。state 必须由宿主提供,服务不自行查询剧情数据库。选择步骤
扫描、混合召回、预算与递归的具体控制参数
当前选择器先放必需组,再放常驻组,再放其他候选;组内依次看 required、高 priority、关键词命中、混合得分、高 order、稳定 ID。装配输出另按位置、深度及 order 排序,不能把“优先入选”理解为“最后放在 Prompt 哪儿”。
次关键词逻辑仅在主关键词已命中、selective=true 且有次关键词时参与:and_any 至少命中一个;and_all 全部命中;not_any 一个都不能命中;not_all 不能全部命中。
hybrid 在同一本书版本范围内结合词法和向量排序;不是 LLM reranker。向量不可用或指定版本索引没就绪时,hybrid 在 degraded 标注原因并退回词法候选;纯 vector 搜索会直接返回错误。规则选择无需 Embedding。
服务的计数默认 cl100k_base,只计算返回的 context。接入方仍须计算角色、聊天历史、工具消息和输出预留组成的完整请求。当前 Harness 在世界书装配之后、发模型之前做最终 Actor 请求预算检查;超出时会以 agent.context_overflow 失败,世界书 1500/2400 tokens 的小预算不能替代模型总窗口配置。接口默认值 · 混合降级 · 最终请求预算
原文放进模型输入的哪里 #
“本轮注入”就是把选中的条目原文装成这次模型请求中的消息。它不表示永久改写角色卡、给模型训练新知识,或把整本世界书塞进历史。
服务根据 placement_capabilities 先排除宿主不支持的位置。普通条目被排除并记录原因;必需条目找不到位置则报错。服务不声明位置能力时只允许前三种默认位置。当前 Harness 会明确声明本次真实具备的区域,而不是把所有 8 种位置都声称可用。位置资格 · Harness 位置装配
接现成 Harness:具体开关、每轮读取与模型边界 #
当前已经接好的入口是 agentic-v2-worldbook 专用运行配置。 它要求 Agentic V2、Actor 的模型配置和 preset;不能同时启用 preActorDirector、storyV1 或 sumiV1。这意味着世界书服务能够给客户自己的 Story/生图流程复用,但此固定版本没有“给现成 Story 或 Sumi 加一个 worldbook 开关就自动接好”的组合。启动配置限制
宿主配置片段如下;这是添加到完整 Agentic V2 场景中的字段,不是独立运行所需的全部配置:
{
"worldbook": {
"tenantId": "tavern",
"strategy": "hybrid",
"tokenBudget": 2400
}
}
tenantId 必填,选择资料所在命名空间;strategy 在 Harness 默认 hybrid,tokenBudget 默认 2400。它们不同于独立服务 SelectRequest 的 rules/1500 默认值。Harness 当前这组配置不直接开放所有服务选择参数,例如 constant_budget_ratio、scan_depth、max_entries;如需逐项控制,要扩展适配层,或从自己的后端直接调用服务。
现成接线每轮做这些事:
世界书的条件不会自动读取 Director 笔记里的事实。 例如,笔记写了“守卫已接过铜钥匙”,不等于选择器收到 keyHolder:"守卫" 这个字段。若要按任务进度、人物状态等条件选条目,宿主必须向选择接口提供结构化 state;使用当前 Harness 接线则需扩展适配层,把所需字段明确传入。JSON 状态中的 current_scene.location/time/situation 可补入查询;普通 V2 文本只有 Current scene: 行参与这条查询补充路径。状态解析与查询生成
Actor 与后台没有自动共享这批原文。 WorldbookTurn 只用于本轮 Actor;慢通道 planner 的输入独立组装,并不会自动收到本轮入选的世界书条目。“知识专家”这个名字也不会额外授予世界书访问能力。若客户希望后台根据同一份规则做校验,需要明确把条目传给后台,或给后台另外接一个带书籍范围的读取工具;这是二开工作。本轮持有与 Actor 注入 · Actor 状态与查询准备
哪些资料什么时候保存,什么时候生效 #
关联保存示例(书籍 ID 替换为实际返回值):
{
"bindings": [{
"book_id": "00000000-0000-0000-0000-000000000001",
"revision": null,
"position": 0,
"enabled": true
}]
}
提交到 PUT /v1/prompt-bindings?tenant_id=local&prompt_id=guard-role-1。prompt_id 是外部角色/作品 ID,不是 PromptManager 模板 ID。revision: null 表示跟随最新版;正整数表示固定版本;bindings: [] 清空。PUT 原子替换这个 Prompt 的全部关联 ,不是只追加本次列表;最多 20 本,不能重复。
每轮 GET /v1/prompt-bindings/resolve?tenant_id=local&prompt_id=guard-role-1 返回:
{
"tenant_id": "local",
"prompt_id": "guard-role-1",
"books": [{"book_id": "00000000-0000-0000-0000-000000000001", "revision": 1}]
}
这是逐轮解析角色关联 ,不是“建会话时永久 pin 一版书”。无启用关联返回空 books,Harness 正常聊天且跳过 select;显式关联失效则失败,不悄悄改用别的书。修改书或关联会影响已有会话的下一轮,不会重写已经产生的历史消息。关联保存与解析 · Harness 逐轮解析
索引、异常处理、独立部署与身份接入
索引。 POST /v1/books/{book_id}/index?tenant_id=local,请求 {"revision":1,"force":false}。这是同步构建接口;已有相同 Embedding 版本的 ready 索引会复用。正文每 1000 字符取最多 1200 字符块,存在重叠;按批生成 Embedding。生成索引不会改变原文和条目 enabled。失败记录 failed,重试该版本;单版本同时构建冲突返回 index_busy。索引实现
部署。 服务需要 Python 3.12–3.13、PostgreSQL;仓库提供依赖锁文件、Dockerfile、迁移和本地 Compose。仅规则模式 VECTOR_BACKEND=none 不调用 Embedding;启用 pgvector 后需要对应向量配置和 Embedding 服务/本地后端。原文与向量同在 PostgreSQL,向量可由原文重建。依赖与启动配置
调用身份。 当前普通 REST 路由要求 query tenant_id,服务没有默认租户,也没有实现请求认证。租户是资料命名空间,不是登录凭据。不能把“能够传 tenant_id”理解为“服务会确认这个用户有权访问该租户”。公网接入需要业务后端/网关处理登录、租户授权、管理端权限等,再调用此服务;/v1/admin/* 还能跨租户浏览,须放在受控入口。自带 Python client 虽会发送 Bearer Header,当前服务代码不会据此校验用户。实际租户参数 · 无认证测试
接入 Harness。 已有调用链是业务请求 → Harness → Axon 的 worldbook RPC → Worldbook Service。Harness 将可信 X-User-ID 交给 Axon,用于会话身份命名空间;它不是最终用户可自行宣称的认证凭据。Worldbook Service 本身无需 Axon;直接 HTTP 复用时由自己的后端提供同等权限和版本管理。Axon 属于外部依赖,本文对 RPC 的说明以 Harness 调用端为准,不把外部仓库实现当作本次已扫描代码。
Demo。 Harness 的世界书 Demo 提供查看关联、聊天和注入诊断;共享导入和关联修改不对 Demo 开放。不能把 Demo 的页面包装直接当成完整对外的世界书管理产品。Demo 接口边界
二开时能直接复用什么,还需自己补什么 #
可以直接复用 :版本化存储、静态资料归一化、Prompt 关联解析、规则/混合选择、精确版本索引、诊断返回;接自己的模型无需先启用整套 Agent V2。调用 search 可做资料浏览,调用 context/select 可做生成前的资料选择。
需在宿主实现 :把返回资料正确放进模型窗口、总 token 预算、用户/租户授权、聊天与状态保存、所用产品的接入流程。若让 Agent 自己决定何时查世界书,还需注册一个带明确 scope 与输入返回契约的工具;服务存在不等于它已经挂进每个 Agent 的工具菜单。
当前没有自动完成 :Story/Sumi 的现成开关组合、后台专家读取同批条目、剧情状态自动修改世界书、从新 NPC 自动生成并持久化独立小卡、完整 ST 动态运行时、独立服务自带的公网用户鉴权。客户如果需要,应把它们明确列为二开范围,而不是世界书默认交付内容。
源码基线:Worldbook 9fd897eafcb23c3e9db6e0a8fdc6bf859fd7753b;Harness fa41d220d48327e58d994f936517b23f2f15f658。本章使用固定源码、类型、路由与样例验证;未把 main 合并状态当作生产开启或客户交付验证。
原子能力 / 工具与应用接线
记忆与状态:什么时候写,下一轮拿回什么
**可以二开的能力有两层:**一层是公共包提供的 JSON 读写工具,适合接入自己的数据库;另一层是应用已接好的日记、记忆表和 V2 状态。它们分别保存,改动一处不会自动同步到其他所有地方。
独立 JSON 记忆工具 #
入口:@flowgpt/agent-core-tools/memory 的 createMemoryTools。宿主把存储绑定到已认证的产品、用户和会话,再交给工具;模型参数里没有任意切换用户或租户的 ID。
例:先读出版本 7,内容 {"scene":"城门","keyHolder":"用户"};确认守卫接过钥匙后,提交版本 7 和完整新对象 {"scene":"城门","keyHolder":"守卫"};存储成功返回版本 8。之后再读,拿到的是版本 8 的对象。
这里是完整替换,不是字段合并。如果新对象只写 {"keyHolder":"守卫"},原来的 scene 会被删除。expectedRevision 必须使用产生该修改时依据的版本;冲突后重新读和重新判断,不能拿旧提议配一个新版本强行覆盖。
记忆必须是普通 JSON 对象,不能把数组、null、函数或循环对象当成整份记忆。序列化后最多 64 KiB ,嵌套深度上限 32 ;版本是非负安全整数且小于 Number.MAX_SAFE_INTEGER。空记录严格对应 revision:0, memory:null,已有记录必须是正版本和对象。beforeUpdate 可以做业务字段检查,检查通过后才调用宿主存储;写入后若返回的版本不是旧版本+1,工具报错,此时要先读回确认,不能当作一定未写入而盲目重试。
参数、写入与结果 · JSON范围、大小和深度校验 · 完整读取、冲突和业务接受示例
最小宿主接线 #
import { createMemoryTools } from '@flowgpt/agent-core-tools/memory';
const [getMemory, updateMemory] = createMemoryTools({
store: {
get: signal => scopedStore.get(signal),
compareAndSet: (commit, signal) => scopedStore.compareAndSet(commit, signal),
},
beforeUpdate(memory) {
if (typeof memory.scene !== 'string') throw new Error('scene 必填');
},
});
const current = (await getMemory.execute('read-1', {}, signal)).details;
const saved = await updateMemory.execute('write-1', {
expectedRevision: current.revision,
memory: { scene: '城门', keyHolder: '守卫' },
}, signal);
这是接线片段:scopedStore 必须由你实现,数据库中要原子检查版本并写入,返回版本应为原版本+1。若业务要求用户接受正文后才能改变事实,不要提前让模型直接调用写工具;先产出提议,接受后再由宿主执行写入。工具设置 replay: "never",不应盲目重放结果不确定的写操作。
/memory 是工具包的正式导出入口。仓库的发布配置为 GitHub Packages、restricted 访问;使用发布包还需要相应包读取权限,不能默认从公共 npm 匿名安装。若按源码构建,也需安装该入口使用的 TypeBox 等依赖。
memory导出与发布权限配置
产品里已经接好的几种资料 #
**日记的“累计轮次”不是普通 V2 后台的全局触发规则。**日记节点默认 min_rounds=20、免费周期 free_rounds_per_diary=10;另一组 memory_v2 配置默认首次 20、后续 10,且是否实际生成、只存摘要还是也存日记,由 planDiary 返回及免费/付费策略共同决定。不能简化成“所有记忆每十轮保存一次”。
日记和表格此处收到的是回复完成事件,代码没有在这里额外等待“用户接受这段正文”的确认事件。业务若需要接受之后再保存,应在接入层明确实现该条件,不能用“后台保存”替代产品的接受规则。
取回摘要和表格 · 何时安排写入 · 日记节点默认参数 · 规划、生成和实际保存
Hinos:外部记忆服务怎样参加一轮 #
这是已接入应用的另一种记忆运行时,需要部署或接通 Hinos 服务。它不调用 get_memory / update_memory,也不把 Hinos 返回内容存进 V2 的 Director 状态。
例如,本轮用户说“我答应明天归还铜钥匙”,Actor 回应“守卫点头,记下了约定”。Harness 完成正文后把这两段文本提交给 Hinos。下一轮 Hinos 可能 返回“用户答应明天归还铜钥匙”等记忆文本,实际内容由该服务决定;Harness 负责把返回文本交给本轮模板,不自行把例子中的句子硬编码为记忆。
当前闭环路径会等待 commit 调用返回,再发出 turn.completed,并未另等用户接受正文。prepare 失败会阻止本轮继续生成;commit 失败会发出记忆写入失败事件,而当前 Router 的 memoryIngest:"best_effort" 允许正文仍完成。已经流出的正文与记忆保存成功是两个结果。取消检查或请求终止可能阻止提交,不能仅凭“看到正文”认定记忆已写入。
启用时,在已有可运行 Scenario 中加入以下字段,并设置服务环境变量 HINOS_MEMORY_URL;HINOS_MEMORY_TIMEOUT_MS 默认 10000 ms 。memorySources 中必须恰好有一项 variable:"hinosMemory";id、agentRef 仍是 Scenario 来源配置的必填字段。
{
"memoryRuntime": "hinos",
"memorySources": [{
"id": "external-memory",
"agentRef": "your-hinos-source",
"variable": "hinosMemory"
}]
}
服务收到 x-flow-conversation-id 与 x-request-id 请求头。当前适配器不附带独立认证凭证,接入部署需要自行配置受信服务访问及其权限边界;这里不能把会话 ID 当成鉴权。
来源检查、prepare协议与记忆返回 · commit、请求头与超时 · 放回Actor输入 · 生成后提交与完成顺序 · 闭环记忆失败策略 · 服务地址与超时
应用接入开关与需要接通的依赖
Scenario 的 memorySources 每项包含 id、agentRef、variable,可有 priority。Axon Go 接法只支持 compassMemory 和 memoryTable,没有配置相应来源就不发对应召回请求。当前召回 RPC 按用户、会话和来源取资料,没有把本轮“铜钥匙”这个 query 传去做语义搜索。
memoryRuntime 可以选 personalization、axon_shadow、axon_go、hinos。axon_shadow 比较两套召回,不能理解为已经切换到新的主写入路径;日记编排器在 axon_go 下按来源装配。Hinos 还需自己的服务地址和超时。业务接线应跟随这些实际分支,而不是只换文档中的“记忆系统”名称。
不指定 memoryRuntime 时,当前 Router 使用 personalization 路径。Axon Go 的日记召回条数还受 subscriptionCategory 影响:默认 1、plus 2、ultra 3、max 5;这是召回资料量,不是生成/保存日记的轮数。表格与日记返回的是本次可用的提示文本,日记缓存 agentRef 来自所配来源,不能把它误当成模型自动选择的检索工具。
日记依赖额度、锁、轮次规划、模型、解析、保存与可选通知;表格依赖编辑解析、版本替换,以及可选提炼上传。表格替换成功后,后续提炼步骤失败不能证明表格没有写入。模板没有引用返回变量时,“取到了”仍不等于“模型看到了”。
多种记忆运行时装配 · 来源与运行时配置校验 · 表格保存与后续副作用
二开时的最小验收 #
连续执行三步:读取旧值 → 触发一次明确修改并检查保存回执 → 新请求读回新值并确认真正放入模型输入。同时测一次版本冲突和一次保存失败。若接日记/表格,还要验证“不满足轮次/没有编辑”时确实不产生相应写入。
原子能力 / 应用模块
历史压缩:对话变长以后,模型怎样继续读材料
用户已经与角色聊了很久,但回复模型一次能读取的文本有限。Compact 把较早的一段历史变成摘要,保留最近的原文,再重新计算这次输入是否放得下。它处理的是“这一次把哪些历史交给模型”,不是把所有聊天变成永不丢失、自动检索的长期记忆。
当前交付形态是应用内的 Compact 运行时、Scenario 配置及检查点存储接线。 两个公共 npm 包没有把这一整套历史压缩服务直接导出。客户可以使用仓内应用接法,或在源码层移植它并提供模型、计数、历史身份和存储实现。
配置契约 · SDK 公开导出范围
先看压缩前后真正拿到什么 #
举例:前面几十条聊天交代了“莉娅借给用户铜钥匙,约定天黑前归还”;最近四条原文仍在讨论城门。用户现在说:“我回来还钥匙了。”
这里的摘要是解释性示例。压缩模型会读取当前用户输入以判断重要性,但“用户说要还钥匙”不能仅因此被叙述成“历史中已经归还”。摘要事实质量由压缩提示词、模型和验收决定;运行时重点校验格式、长度、历史覆盖与版本关系。
何时执行:按窗口预算,而不是固定聊天轮数 #
开启 Compact 的请求先取得压缩模型配置,并读取检查点。即使请求只带较短的近期历史,也先检查能否接上之前的摘要。
计算这次可容纳输入的预算:模型上下文上限 − 最大输出预留 − 安全余量 。
把实际角色提示词、历史、旧摘要、最终注入内容组装起来,计数;不能只估算历史字符数。
超过配置的触发比例时尝试压缩。若旧摘要的覆盖末端即将离开请求携带的历史窗口,也会尝试提前推进检查点。
选择可压缩的已闭合历史组,排除配置要求保留的最近组,调用压缩模型;再将新摘要和保留原文组装成 Actor 输入,重新计数。
只有实际输入放得下,才采用这次压缩结果;具备持久化条件时还会在 Actor 生成之前保存检查点。
例如模型窗口假设为 8,192 tokens,输出预留 2,048,安全余量 512,则输入预算是 5,632;触发比例设为 0.8 时,普通长度触发线为 4,505。这组数字只演示计算方法,不是所有场景的默认模型窗口 。窗口取自模型配置,输出预算取自本轮实际配置;调大配置不能扩大模型服务自身支持的上限。
输入预算 · 先读取检查点 · 触发及保留历史 · 压缩后重新计数
保存什么、什么时候保存、下一轮拿什么 #
当前压缩成功而保存失败时,本轮仍可能使用内存中的摘要继续回复;以后请求不保证能读回这次摘要。需要看 persisted 或检查点结果,不能把“本轮压缩成功”与“以后一定记住”合成一个状态。
检查点按 用户+会话+Scenario 读取和保存。应用适配器只在 chat、continue、auto_reply 类型允许修改检查点,其他类型不能借这条路径改写旧摘要;这是组装器收到请求后的权限条件,不表示独立 Auto Reply 建议入口一定会执行 Compact。调用方若直接复用运行时,须明确传入 checkpointMutationAllowed。
读回也不是“有摘要就使用”:代码检查格式版本、角色/配置的语义指纹、摘要大小及压缩协议;若覆盖的首尾消息都在当前历史里,还检查覆盖条数与历史内容指纹。至少需要找到旧摘要覆盖的末端消息,才能证明它与后续原文接得上。读失败、配置无法核实或未知格式时,本次不会拿该检查点继续写新版本;已知配置/历史发生变化且允许修改时,先按版本将旧检查点失效,再考虑重建。
保存前提及覆盖信息 · 版本提交 · 本轮返回的投影
请求身份与修改权限 · 读回、校验与失效
接入和配置:实际字段放在哪里 #
以下是仓库 compact-automemory-v0-native.json 中 Compact 部分的原样配置。它展示当前解析器支持的字段;compactorModelConfigId 需在所接入配置中心真实存在。该场景样例并非普通 V2 的统一默认值,也不是打开整个记忆系统的开关。
{
"compact": {
"enabled": true,
"boundaryMode": "selected_turns",
"compactorModelConfigId": "RP-Harness-Compact-AutoMemory-v0-Native",
"triggerRatio": 0.8,
"keepLastCanonicalGroups": 2,
"checkpointMutationTimeoutMs": 5000,
"safetyMarginTokens": 512,
"maxSummaryTokens": 700,
"reliability": {
"version": "v1.1",
"minGenerationTokens": 256,
"maxCandidateAttempts": 24,
"maxModelCalls": 2,
"maxCompactionMs": 120000
},
"profile": "configurable-v2",
"inputProtocol": "dream_text_v0",
"outputFormat": "plain_text",
"lengthPolicy": "accept_bounded",
"executionPolicy": "bounded_recent"
}
}
compact.contentPolicy 已被明确拒绝;代码要求移除旧字段,通过压缩用 PromptManager/Preset 表达内容提炼策略。本轮问题始终进入压缩材料。普通 V2 同时启用 Compact 时还需要 contextGovernance,其中 enforce 才让后台采用受控的压缩投影;shadow 是观测对照。Sumi 配置当前不允许这个 Compact 开关。
真实配置样例 · 字段校验 · 协议支持范围 · 旧字段处理
协议、可靠性预算与失败行为
generic-json-v1 与 configurable-v2 是当前支持的 profile。只有 configurable-v2 接受自选输入/输出协议,且必须显式提供 reliability。
输入协议支持 harness_json_v1、dream_text_v0、automemory_recursive_v1;输出支持 structured_json、plain_text。
结构化摘要包含 canonical(可延续事实)、interaction(互动关系)、open_threads(未完事项);纯文本投影为 {format:"plain_text",text:...}。不是所有输出格式都返回同一业务对象。
lengthPolicy 默认 reject;accept_bounded 仅允许纯文本。无论采用哪一种,最终还要通过摘要及 Actor 的总预算检查。
maxCandidateAttempts 限制尝试候选,maxModelCalls 限制真正发给压缩模型的调用,maxCompactionMs 限制候选压缩阶段的时长。它不是整轮请求时限:最终 Actor 输入计数及检查点保存另按请求/存储时限执行。候选次数不等于付费模型次数。
可选递归切片 recursive.segmentContentTokens 需要 configurable-v2 与 automemory_recursive_v1;中间摘要不逐片保存成最终检查点,整个结果通过后再提交。
模型出错但原输入仍在窗口内时,可以沿用已有输入;否则逐步裁掉可移除的旧完整组,再计数。bounded_recent 裁掉未覆盖历史、破坏旧摘要连接时,也会放弃该次旧摘要。仍超窗则返回上下文溢出错误。
配了检查点独立超时时,写超时后最多用 2 秒读回,核对版本、链路及完整提交内容;匹配才认定已保存。版本冲突不通过覆盖来“强行成功”。
输出投影与格式 · 可靠性策略 · 保存失败与确认 · 历史裁剪降级
原子能力 / 公共函数
上下文组装:把资料放到模型真正看得见的位置
**用途:**业务已经取到了角色设定、世界书、历史摘要或工具结果,需要决定它们放在模型输入的哪里、以哪个消息角色出现。
**公共入口:**SDK 的 placeContextBlocks、getPlacementCapabilities。这是纯消息组装能力,输入消息与资料块,返回新消息数组及放置记录;不调用 LLM,不检索数据库,不保存任何长期资料。
一次具体调用 #
原输入里有角色设定和用户问题。你选择把“进入禁区需要手令”放在角色设定后面:
import { placeContextBlocks } from '@flowgpt/roleplay-harness';
const result = placeContextBlocks({
messages: [
{ role: 'system', content: '你扮演城门守卫。' },
{ role: 'user', content: '我递出铜钥匙。' },
],
layout: { character: { start: 0, end: 1 } },
blocks: [{
id: 'gate-rule', position: 'after_character', order: 0,
role: 'system', content: '进入禁区需要手令;铜钥匙不能替代手令。',
}],
createMessage: message => message,
});
// result.messages:角色设定 → 手令规则 → 本轮用户输入
// 将这个新数组交给模型,规则才会参与本轮生成。
这个例子只展示组装;资料是否可信、是否被授权、是否适合本轮,应由调用前的业务选择完成。组装后也应计数并执行模型预算检查。
位置、资料块与布局定义 · 组装函数 · 完整组装示例
位置可以改,但需要对应锚点 #
depth:0 在最后一条真实对话之后,depth:1 在最后一条真实对话之前;深度超过历史条数时放在最早一条真实对话前。order 控制同类放置的顺序,同值再按 ID 排序;相邻区域共享插入点时先遵守区域顺序,不能用一个更小的 order 任意跨越角色/示例区域。没有对应锚点、重复 ID、非法角色或嵌套 outlet 时会报错,不悄悄丢掉已经选择的条目。
getPlacementCapabilities(layout) 只按你声明的布局列出可用位置与 outlet 名称;完整的边界、索引和模板标记校验在 placeContextBlocks 执行。调用方可以用 renderBlock 格式化内容,或用 renderMessages 将独立资料块转换成多条消息;作者备注和 outlet 是嵌入宿主消息,不按独立消息的格式转换。
公共组装器支持八种位置,不等于每条业务接线开放了全部位置。世界书服务的选择结果、Harness 当前布局和可用位置应配套,详见 世界书 。
分析工具的上下文是另一个入口 #
六类分析工具可以传 compileContext,它返回整条 user message 文本;系统岗位说明仍由 prompt 提供。先检索资料再编译,不能在同步编译器里期待自动查库。普通 V2 Actor、后台 Orchestrator、专家和 Director 各自组装输入;给 Actor 注入过的世界书不会自动传播到其他模型。
自定义编译器的职责 · 系统提示和 user 材料的组合
窗口在哪里改,超了怎么办 #
模型完整窗口由实际模型配置与适配层负责,资料字符截断、世界书 token 预算、Actor 输出预留属于不同限制。placeContextBlocks 不替你扩容或自动压缩;组装完成后由宿主计数。普通应用可以使用 Compact 和相应 contextGovernance;自己构建 Agent 时也要明确执行预算策略。
验收时直接检查最终 messages 与 placements:规则在哪条消息、role 是什么、是否出现一次、给哪个模型;再测缺少锚点和预算超限。不能只检查“世界书接口返回了条目”。
原子能力 / 工具与应用接线
图片:独立图片资产、聊天配图与视觉连续性
如果客户只想“给一段描述,拿到一张图”,可以复用 generate_image_asset 独立资产工具 。如果客户要“角色先说一段、这段配一张图、图片出现在对应聊天消息中”,还需要正文分段、图片提示词、消息占位、图片服务回填这条应用接线。两者的参数和保存职责不同。
Sumi 是仓库另一个专门的图文应用,拥有自己的生成与保存流程。它不等于给普通 Agent V2 打开一个生图开关;本文先说明可复用能力和已有接法,完整产品入口仍分别见 Agent V2 与 Story。
先选要复用的能力 #
独立图片工具 · 聊天配图模块 · Sumi 独立入口
独立图片资产:怎样接入 #
业务示例:运营输入“灰港城门的铜钥匙,桌面静物特写”,生成一张道具插图,之后由客户自己的后台保存到道具资料里。这里不需要 RP 会话,也不会自动创建 NPC 或世界书条目。
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: ...} 不符合协议。配置引用由宿主绑定,模型不能通过工具参数自行切换账户或配置。
返回格式、采用条件与失败处理
返回包含顶层 moderation_status 和 1–8 个 jobs;每个任务含 task_id、image_urls、与每张图片一一对应的 moderation。审核项的 asset_url 必须对应图片 URL。
例如,后端可以提供如下结构(任务、地址和审核字段是业务示例):
{
"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"。调用只提交一次;超时或返回证据不完整时,不能自动重提并假定第一次没发生。任务追踪、去重与已生成资产的恢复由宿主承担。
结果校验与执行 · 独立图片接入示例
聊天配图:从用户请求到消息里的图片 #
示例输入:“莉娅抬头看我,给这个瞬间配张图。”当前应用接线按以下顺序执行:
前台控制模型决定下一步。 开启前台工具循环后,控制模型可以调用 actor 生成正文片段,再选择 generate_image 给片段配图;另一路是调用 actor 时指定 responseMode="visual_beat",该段正文成功后由程序自动安排这张图,不需要模型再为同一段调用一次 generate_image。工具名不是用户直接上传的画图指令,具体如何选由控制提示词和模型决定。
程序准备画图材料。 首次实际画图时,读取或初始化视觉世界;取得角色卡、角色 Prompt、持久角色状态,以及已投影的人物/线索/世界材料。
图片提示词模型写画面描述。 输入包括原用户要求、这段正文、图片序号、人物永久外貌及当前视觉状态。输出必须是一个带镜头、比例等属性且正文符合规定结构的 <image> 块。用户指定的画面重点优先于默认构图。
等待业务把助手消息保存下来。 ImagineSkill 按助手消息 ID 读取消息。默认最多等 120 秒,每 200 毫秒检查;消息尚不存在时,无法给它插入图片位置。
先保存图片位置。 在这条消息的 narrative.sections[index] 写入 type:image、status:pending、空 URL 等;对应描述存入 narrative.imagePrompts[index]。失败则停止该次图片提交。
调用图片后端。 ImageMcpTool 通过 MCP generate_image,或配置了直接视觉运行时后的 Image Agent HTTP 接口,提交对应消息和位置。最终图片生成、审核和消息回填依赖下游服务。
整轮完成后处理视觉连续性。 如果本轮确实准备过视觉状态,且 Harness 完成成功,程序安排后台 commit_visual_world,用正文和本轮旧视觉状态进行更新;不会在每张图片之后都重复结算整轮。
正文生成完成、图片任务已提交、图片可展示、视觉状态已保存是四个不同时间点。 普通 V2 的后台导演笔记也不是这个图片占位或视觉状态。
接线与任务调度 · 图片模型材料 · 图片提示词生成与校验 · 消息位置保存 · 整轮视觉状态结算
前台两种配图触发及重复限制见 前台配图决策与自动安排 。前台工具结果返回图片序号或任务受理信息,让控制模型继续当前正文;最终图片 URL 由下游生成和消息回填链路提供。
开启需要什么,不能只填一个工具名 #
这些是应用内部接法,generate_image 不是 Core Tools /image 子入口里 generate_image_asset 的别名。如果客户只需要图片资产,直接用独立工具;如果需要沿用这套聊天交付,需要接好上表依赖或自行实现同等职责。
异常时读者应该预期什么 #
图片描述模型失败时,应用记录降级原因,改用正文里的现成 <image> 块;正文没有图片块时,就把整段文字包成 <image ratio="16:9">正文</image>。这一步不再调用另一个描述模型,不应承诺仍有完整镜头结构。
多图片任务按顺序交接,已有任务的等待会影响后续图片到达;代码中的后台超时预算不是对客户承诺的图片出图时间。
pending 已保存之后,下游提交仍可能失败。这段 Imagine 接线没有在所有失败路径里自动把该位置改为终态;业务需读取任务及消息状态,不能把 pending 当作成功。
没有配置图片依赖时,普通 Actor 回复和后台专家分析不自动获得生图能力。
描述失败的降级 · 提交错误处理 · 场景兼容规则
原子能力 / 服务适配
DIO:把明确的长期要求用于后续角色回复
用户说“从现在起莉娅都用短句回答,少用比喻”,这是一条希望持续生效的角色要求。现有 DIO 接线把这类要求提交给外部指令服务处理;后续 RP 请求再读取服务产出的有效提示词,让回复模型使用。
可复用形态:仓内的 DioTool 服务适配器,以及前台工具循环中的 schedule_dio 动作。 DIO 服务本身不在本次两个仓库里;本仓能验证的是提交、查询、有效提示词读取和注入链路,不能据此承诺外部服务内部怎样编辑或审核每条要求。
服务适配器 · 前台提交接线
从一句要求到后续生效 #
受理结果中的 appliesFrom: "next_turn" 表示后续轮次使用这条通路;不是保证紧接着的一句一定等得到新要求。 如果任务仍在处理、没有生成新 Prompt,下一句只能读取当时服务实际已有的有效内容。
当前程序把原始用户输入 发给 DIO,没有让前台模型另外编造一份要提交的文本。控制提示词要求只在用户明确要求长期变化时使用该动作;执行端主要验证工具可用、参数和重复调用,不额外运行一个语义模型重新判断用户意图。
提交原始输入及后台查询 · 下一轮读取 · 有效内容与哈希校验
有效提示词怎样真正进入 Actor #
下一轮查询得到有效 compiledPrompt 后,入口把它放到请求的 dioCompiledPrompt。随后 Actor 组装器执行 applyDioCompiledPrompt,并把原文放入模板变量 dioCompiledPrompt,再由原有模板展开流程生成 Actor 的实际消息。
替换规则是明确的:
如果 PromptManager 有且只有一个 name="roleplay" 的提示词,替换它的整个内容,角色和顺序保持不变。
如果没有 roleplay 提示词,则要求所有提示词合计只有一个 {{getvar::systemPrompt}} 占位,把该占位改为 {{getvar::dioCompiledPrompt}}。
重复的 roleplay、没有可用位置、或没有 roleplay 却有多个 systemPrompt 占位,均会抛出错误;程序不随意挑一个位置,也不把长期要求追加到整份上下文末尾。
例如,原 Actor 模板为“控制说明 → 角色提示词 → 历史 → 本轮问题”,新一轮变为“控制说明 → DIO 编译后的完整角色提示词 → 历史 → 本轮问题”。这意味着 DIO 交付的是可替换该角色位置的完整编译提示词 ,不能只交一句“少用比喻”并假定原角色设定仍然保留在这个被替换的位置。
仓库测试检查了最终 prepared.history 确实含 DIO 内容,并检查控制说明、历史与当前问题保留;因此这里不是仅把字符串读回日志、却没有交给 Actor 的半成品接线。DIO 服务如何把旧角色资料与新要求编译成这段文本,仍属于外部服务内部职责。
角色提示词替换规则 · 绑定变量并继续组装 · 最终 Actor 消息与歧义失败测试
如何接入 #
接入现成 RP 应用时,需要场景开启 preActorDirector,服务依赖中提供 DIO 适配器,并带正确的用户、会话、目标 Prompt 身份;Actor 的 PromptManager 还必须满足上述唯一角色提示词位置要求。只填写 schedule_dio 字符串不能建立服务连接。
客户若在仓内二开,可以复用适配器。它是应用源码模块,当前不是 Core Tools 的公共子入口:
// 仓库内代码位置:apps/emochi/tools/dio-skill.ts
const dio = new DioTool(dioServiceUrl, requestTimeoutMs);
const accepted = await dio.schedule({
actorTurnRequestId: "rp-turn-101",
userId: "user-42",
rpConversationId: "conversation-8",
targetPromptId: "lia-character",
instruction: "从现在起莉娅都用短句回答,少用比喻。",
language: "zh",
userAuthorization: userAuthorizationHeader,
}, signal);
示例中服务地址、超时、授权头和 signal 由接入者提供。适配器派生 dio_conversation-8 作为 DIO 会话,rp-turn-101:dio 作为请求 ID,rp-turn-101:dio:assistant 作为任务消息 ID;收到的受理身份必须与这次提交一致,否则抛错。
接口、返回字段及失败边界
应用后台轮询任务预算为 16 分钟;不是每次 RP 都等 16 分钟,也不是服务处理时效承诺。成功终态仍可能 promptChanged:false,不能据“任务成功”就宣布角色要求已改变。
有效提示词查询或哈希校验失败 时,入口记录降级事件,继续使用正常 RP 材料;显式取消仍向外传播。与此不同,已经读到有效内容,但后续发现 Actor 模板缺少或重复替换位置时,组装器会报错,不走上述查询失败降级。接入验收需要同时验证“服务能返回有效内容”和“Actor 模板能够正确采用”。
长期要求不是普通 V2 的持续笔记,不保存在 update_director_state 里,也不自动写世界书或创建独立角色小卡。
受理请求与字段校验 · 终态返回校验 · 读取失败的处理
原子能力 / 公共 SDK
编排与扩展:做自己的 Agent、工具循环和快慢协作
**用途:**业务可以复用公共执行组件,自己决定模型要完成什么、能够调用哪些工具、结果由谁采用。这个入口不自动附带普通 V2 的六专家、Director 或 Story 的剧情策略。
三个可以单独使用的公共入口 #
Agent 入口已经具备通用模型选工具循环;不能写成“Harness 的 agentic 编排都待支持”。同时,普通 V2 应用自己的两阶段规则并不因此自动替换为这个通用循环。
三者都是 SDK 根入口 @flowgpt/roleplay-harness 的公开导出。apps/emochi 下的 Actor、应用协调器与 profile 节点执行器不在这个包的公开导出范围内;安装 SDK 不会同时装出一套可直接运行的 RP 产品。固定源码的 package.json 标注 SDK 0.3.0、Node.js ≥22、受限 GitHub Packages;这个版本号本身不能证明某个仓库新增字段已在注册表中的同号包发布。
通用 Agent 的 prepare/run · 独立慢路实际循环 · 读取状态与启动快慢两路
实际公开导出 · 包范围、运行环境与发布目标
接线片段:调用自己的快慢两路 #
下面用公共入口展示接入位置。replyModel、analysisModel 是宿主实现的模型适配器;scopedStateStore 绑定业务身份并实现读与 CAS;registeredTools 是宿主明确允许的工具执行器列表;validateStateProposal 是自己的采用规则。它们都不是安装包后自动出现的服务。
import { FastSlowRuntime } from '@flowgpt/roleplay-harness';
const runtime = new FastSlowRuntime({
fast: { model: replyModel, modelConfigId: 'reply-config' },
slow: {
model: analysisModel, modelConfigId: 'analysis-config',
prompt: '检查连续性;把已确认事实与后续建议分开。',
capabilities: registeredTools,
state: scopedStateStore,
prepareCommit: (final, results, context) =>
validateStateProposal(final, results, context),
},
});
const turn = await runtime.run({ context: {
requestId: 'turn-2', sourceTurn: 2, sourceMessageId: 'user-message-2',
language: 'zh', input: '我递出铜钥匙。',
history: [{ role: 'assistant', content: '莉娅站在城门旁,等你归还钥匙。' }],
}});
const reply = await turn.fast; // reply.content 交给自己的聊天业务
const background = await turn.slow; // 检查 status 和 stateOutcome
该例使用 run(),返回完整快路结果。若前端需要流式正文,改用 stream() 并提供真实流式快路执行器。validateStateProposal 只能返回符合契约的候选或 undefined,不能直接返回任意数据库对象。产品级服务通常复用 runtime;服务关闭时调用 shutdown(),并处理尚未完成任务的结果。
模型适配器实现 complete(request, signal),返回 content、toolCalls、模型身份和所需 usage;状态仓库提供 get / compareAndSet。仓库的完整示例连同假模型、工具适配与存储实现可以直接查看并运行,示例里的固定文本不代表真实模型质量。
完整工具接线与快慢调用 · 上下文与状态仓库示例 · 通用 Agent 的 prepare 与 result 示例
主模型实际收到什么 #
SlowTurnRuntime 给主模型的消息依次是:你配置的系统提示词、已保存状态的 system 消息、传入的历史、本轮 user 输入;提供给模型的工具列表来自你传入的 capabilities。没有自动读取业务数据库、世界书或角色卡的步骤。
每次模型返回工具请求,程序解析参数、验证这一批请求,再调用已注册执行器。结果回到模型消息中,模型可以继续选择工具或输出最终内容。调用方必须解释 final.content 与 toolResults,不能把任意文字直接当作一份合格的业务状态。
SimpleFastRuntime 使用同一份起始状态、历史和输入调用快模型。它的默认 stream() 先等待 complete() 再输出一个完整块;要真正逐 token 交付,需要提供自己的流式快路执行器。
快照、历史与本轮消息组装 · 主模型输入及工具回传 · 默认快路的流式行为
结果何时保存 #
SlowTurnRuntime 可以只做分析。未配置 prepareCommit 时,不自动写状态;配置后由该回调检查模型结果,返回 {state, toolsCalled?, ledgerEntries?},或返回 undefined 表示本次不改。
只有回调给出候选,才调用宿主的 state.compareAndSet,使用本轮读取的版本、来源消息和轮次做检查。版本冲突返回 stale_write_rejected。数据库、权限与原子检查由宿主实现,SDK 没有自带持久数据库。下一轮 FastSlowRuntime 才会重新读当时已保存的版本。
**例:**用户要求角色以后说话简洁,快路先承接本轮对话;慢路分析后返回风格偏好提议。宿主只允许白名单字段,通过才写入。下一轮重新取出这份偏好并作为输入,才可能影响说话方式。若没实现 prepareCommit 或没把保存值交回下一轮,分析不会自行变成长期能力。快路也不应在保存尚未确认时把“永久记住了”当成已完成事实。
默认 prepareCommit 只拿到慢路最终模型结果、工具结果和起始上下文,没有本轮快路最终正文或“业务已接受正文”的标志 。若你的产品要求正文接受后才能改状态,应采用等待式 SlowTurnRuntime,先得到分析结果,再由业务接受边界显式提交;不能假设 FastSlowRuntime 已自动等快路完成。
候选到 CAS 保存 · 状态快照与写入字段
预算、gate、取消和工作流接入参数
gate 如果自身调用模型,该调用和成本要由宿主记录;慢循环计数为 0 不代表整项业务毫无开销。gate 抛错、取消或返回非法对象会进入失败路径。它没有自动接入普通 V2 应用,也不能当作“普通 V2 现在累计 X 轮才运行”的证据。该能力在本次主干中有实现,使用发布包前需核实具体版本是否包含。
gate 的合法结果必须有 boolean run,另可含 reason、0–1 的 confidence 与非负 latencyMs;跳过时没有 final,读取结果前需要判空。FastSlowRuntime 仍先读状态、再调度慢任务和快路;它不是让 gate 在快路之前统一决定本轮是否执行。
两路是同一进程中的异步任务,不是各自创建一个系统线程。快路失败、用户停止读取快路迭代器,不会自动等价于 cancel();需要取消整轮时显式触发 cancel 或外部 signal。取消会终止等待并传递信号,但不能撤销已经发生的外部工具副作用或数据库提交;后端仍需配合取消和幂等。drain() 只等现有后台任务,shutdown() 发出取消再等;两者到等待期限都可返回,不能作为“所有外部写入一定停止”的证明。
后台任务是进程内执行,不是可跨进程恢复的持久任务队列。需要可靠工作流时,把 SlowTurnRuntime.run 作为可等待的步骤,持久化任务输入、结果与业务接受状态,再由外部工作流决定重试和提交。status:'failed' 是返回结果,不一定抛异常,工作流步骤应主动检查。
预算与 gate 定义 · 默认限制 · 跳过与失败行为 · 等待分析与业务接受后提交的完整示例
drain、shutdown 与共享取消 · 进程内异步任务 · 按模型响应计量预算
工具注册和 Hook 怎样接 #
注册表保存的是工具定义和执行器;不会扫描仓库把所有函数自动挂给模型。AgenticCapabilityRegistry 直接构造时参数校验默认关闭,设置 validateArguments:true 才按 Schema 强校验;SlowTurnRuntime 使用自己的受控注册与调用校验。通用 Pi Agent 则由 options.initialState.tools 明确提供工具。
Core Tools 基类的生命周期是 validate → beforeExecute → 再次 validate → perform → afterExecute。afterExecute 失败时底层副作用可能已经完成,不自动重跑。afterTurn 必须由宿主在整轮结束后显式调用,execute 不会自动触发它。
已有 Workflow 可以在一个节点直接 await 工具,也可以把 SlowTurnRuntime.run 作为一个节点;依赖执行顺序由该 Workflow 决定。仓内另有下面这套 Profile 固定流程,可以按业务规定的顺序串联工具。
注册与参数校验开关 · 工具生命周期 · Hook 接口 · 完整通用 Agent 示例 · 完整快慢协作示例
固定 Workflow:先查资料,再形成写作要求 #
可复用形态是仓内程序组件,当前不是公共 npm SDK 导出。 StaticPlanner 读取业务定义的节点图,CapabilityExecutor 按依赖调用工具,RPPromptCompiler 把明确指定的文本结果整理到输入区域。模型不用先判断要不要查这份资料:只要走到这个流程,程序就按定义执行。
比如用户说“我递出铜钥匙”,你希望每次都先查询城门通行规则,再结合规则形成写作要求,最后交给正文模型。业务可以定义:查规则 → 准备场面要求 → 组装模型输入 → 调用自己的正文模型 。这套组件负责前三步的节点执行和材料整理;模型调用、正文保存与接受规则仍由接入者或选用的应用运行时负责。
McpCapability 没有默认 MCP 服务地址、账号或网络客户端。接入者实现适配器并绑定允许使用的服务;如果只是调用本地函数,直接实现下面示例的 Capability.execute 即可。
固定图的校验与排序 · 执行入口 · 编译结果 · 外部工具包装
Profile 和节点里分别填什么 #
failurePolicy 的实际字段为 memoryRecall: "fail_turn" | "continue_without_memory"、memoryIngest: "fail_turn" | "best_effort"。这是记忆步骤的策略;工具节点失败由 required 处理。能力元数据中的 readOnly、idempotent 等描述性质,执行器不会据此自动撤销写入或保证幂等。
能力、节点与 Profile 字段 · Capability 调用与返回契约 · 完整 Harness 的超时与记忆策略
最小可运行示例:看到工具结果进入最终 messages #
下面两个工具是示例里由接入者新定义的工具 ,不冒充仓库自带的世界书 API。它们使用内存资料演示接口;换成真实世界书或其他服务时,在各自 execute 内完成调用和校验。
在已安装依赖并完成 npm run build 的源码仓库根目录,将此段保存为 workflow-demo.mjs,运行 node workflow-demo.mjs。使用的是仓内构建产物路径,不是公共 SDK 的导入路径:
import { StaticPlanner } from './dist/apps/emochi/planning/static-planner.js';
import { CapabilityExecutor } from './dist/apps/emochi/capabilities/executor.js';
import { RPPromptCompiler } from './dist/apps/emochi/prompt/compiler.js';
const request = {
requestId: 'workflow-1', scenarioId: 'gate-workflow',
actorId: 'guard', input: '我递出铜钥匙。', stateVersion: 0,
};
const profile = {
id: 'gate-workflow', modelConfigRef: 'actor-config', presetRef: 'actor-preset',
defaultTimeoutMs: 30000,
failurePolicy: { memoryRecall: 'fail_turn', memoryIngest: 'best_effort' },
visibleCapabilities: ['read_gate_rule', 'prepare_scene'],
executionGraph: { nodes: [
{ id: 'rule', capability: 'read_gate_rule', dependsOn: [],
required: true, projectTo: 'none', input: { location: '城门' } },
{ id: 'scene', capability: 'prepare_scene', dependsOn: ['rule'],
required: true, projectTo: 'turn_context' },
] },
};
const metadata = name => ({ name, readOnly: true, idempotent: true,
longRunning: false, streaming: false, cancellable: true });
const tools = [
{
metadata: metadata('read_gate_rule'),
async execute({ input }, signal) {
signal.throwIfAborted();
if (input.location !== '城门') throw new Error('未知地点');
return { value: { rule: '进入禁区需要手令;铜钥匙不能替代手令。' } };
},
},
{
metadata: metadata('prepare_scene'),
async execute({ dependencyOutputs }, signal) {
signal.throwIfAborted();
const rule = dependencyOutputs.get('rule')?.value?.rule;
if (typeof rule !== 'string') throw new Error('缺少已查到的规则');
const instruction = `${rule} 守卫应检查手令,不把递出钥匙写成已经获准通行。`;
return { value: { instruction }, promptContent: instruction };
},
},
];
const plan = new StaticPlanner().plan(request, profile);
const capabilities = await new CapabilityExecutor(tools).execute(
plan, request, AbortSignal.timeout(30000), () => {},
);
const compiled = new RPPromptCompiler().compile({
axon: { history: [], foundationBlocks: [] },
memory: { version: '0', items: [] }, plan, capabilities,
});
const messages = [
{ role: 'system', content: '你扮演城门守卫,承接用户动作。' },
...compiled.blocks.map(({ role, content }) => ({ role, content })),
{ role: 'user', content: request.input },
];
console.log(JSON.stringify({ outputs: capabilities.outputs, messages }, null, 2));
// 自己的模型适配器接收 messages 后,材料才真正参与生成。
执行后,outputs 中有原始规则对象与场面要求对象;messages 是“角色岗位 → 场面要求(内含手令规则)→ 用户输入”。rule 节点设为 none,所以它不会被重复直接注入;scene 节点显式返回 promptContent,才产生模型可读的文本块。这段示例不调用真实模型,也不写数据库。
执行结果、失败及进入模型的边界 #
编译器按区域、priority 和来源等字段排序;它不是直接把所有结果按工具完成先后追加在末尾。TurnHarness 负责把 compiledContext 交给所选正文运行时,具体运行时仍必须使用它 。例如 ChatKernel 接法写入 harnessFoundationContext / harnessTurnContext 模板变量;当前闭环 Actor 主要发送 axon.history 中已准备好的消息,不能仅把工具结果放进 compiledContext 就假定已经注入了这个 Actor。上面的示例显式构造 messages,正是为了让采用动作可见。
当前 Agent Router 的普通入口与闭环入口会构造空的 visibleCapabilities 和 executionGraph.nodes。所以这项能力的交付方式是在仓内二开组装 Profile、执行器和模型材料接法 ,不是往现有 Scenario JSON 随意加一个 executionGraph 就能加载任意工具。通用 SDK 的工具名单、这个固定节点图、普通 V2 的后台专家名单,是三种不同接线。
依赖、并行及失败 · 只投影明确的提示文本 · 模板采用编译区域 · 闭环 Actor 的实际消息来源 · 闭环入口构造的空节点图 · 普通入口构造的空节点图
观测与接入验证 #
可以接入 HarnessTelemetry、observeModel 和 /langfuse 子入口,记录一次调用的模型、工具、状态步骤;createAgentBuildMetadata 描述构建和配置身份。观测用于核对“实际执行了什么”,不代替业务接受或存储成功的检查。
先用仓库公开示例中的假模型和假存储跑通:模型请求工具 → 执行器收到正确参数 → 结果回到模型 → 宿主校验并采用 → CAS 写入 → 下一轮读回;再替换真实后端。安装包不会自动获得 Axon、Kaon 或其他业务凭证。
原子能力 / 应用接口
下一句建议:给用户三个可选择的回应
**用途:**角色回复后,为用户提供三条“我接下来可以说什么/做什么”的候选。例如守卫问“手令呢?”,可以返回“我翻找随身的手令”“我问他去哪里补办”“我先退到旁边等候”。这些是用户可选择的输入建议,不是角色再回复三次,也不会自动替用户发送。
**交付形态:**当前是 Harness 应用里的 Auto Reply 生成流程,已有普通 Agent V2 和 Story 两种接线;不是 @flowgpt/agent-core-tools 导出的独立工具,也不是默认交给 Orchestrator 选择的专家。接完整产品时走现成 AUTO_REPLY 请求;单独二开需复用应用实现并接通模型、Prompt 与对应的状态/消息读取依赖。
调用输入与普通V2实现 · Story实现 · 产品请求分流和返回
用户看到什么,业务拿回什么 #
业务在一条已生成的角色回复下请求建议。程序以这条回复的 assistantMessageId 为锚点,组织材料,调用专门模型,解析成三条不同的短文本,交还业务显示。
应用函数返回:
{
"options": [
"我翻找随身的手令。",
"如果没带手令,我该去哪里补办?",
"我先退到旁边等候。"
],
"usage": {"inputTokens": 800, "outputTokens": 60, "totalTokens": 860}
}
这里是结构示例,具体文案与 token 数由模型实际返回。现成 Agent Router 把三个选项转为单个结果中的编号文本:
1: 我翻找随身的手令。
2: 如果没带手令,我该去哪里补办?
3: 我先退到旁边等候。
生成建议时不写用户聊天消息、不更新 V2 状态、不结算 Story 剧情。 业务方决定把它们做成按钮、填入输入框或保存为建议记录;只有用户实际选择/编辑并发送后,业务才按正常聊天流程保存那一句并发起下一轮。普通 V2 的调试记录可能记录建议文本,但调试记录不是用户已说出口的聊天历史。
调试记录与options返回 · 编号文本、空artifacts与stateVersion0
普通 Agent V2 怎样生成 #
等待的是什么状态 #
普通 V2 的后台开始工作时,会把这一轮开始时读到的状态及其版本 ,连同目标回复 ID、轮次和阶段,通过 markPlanning 发给 Axon。Auto Reply 随后读取状态接口里与这个回复 ID 匹配的 auto_reply_guidance。
所以它等待的是“这条回复所属轮次的指导快照可读”,不是必须等六位专家都执行完,也不是必须等 Director 把本轮新状态保存成功 。指导记录可以在 pending 中,也可以在 planning_settlements 中。Harness 没有在这里现生成一份摘要;具体指导文字由外部状态服务提供,当前两个仓库不能证明其内部转换算法。
默认最多等待窗口为 30,000ms ,两次读取之间等 250ms ;配置范围 0–120,000ms。设为 0 仍会读取一次,不表示完全跳过检查。该数值控制查快照的轮询窗口,不是包括网络请求与模型生成的完整响应时限。
后台阶段记录是旁路写入,失败不阻塞 RP 正文;因此可能出现“角色回复已经成功,但建议因指导快照未就绪而失败”。这种情况不应伪造三条静态建议作为成功结果。
轮初快照随阶段写入 · mark_planning传输字段 · 按回复ID轮询指导
Story 怎样生成 #
Story 的建议流程不读取普通 V2 的 pending/planning_settlements,也不等待旧后台快照。
例如请求里缓存的是“守卫让你通过”,但聊天服务保存的正式正文是“守卫伸手索要手令”,Story 会按后者生成建议。如果正式正文尚未保存、查不到、角色不对或为空,流程失败;它不会退回请求里的旧文本继续生成,也没有在此实现等待正文落库的轮询。
源码函数允许额外提供 pendingStage,但它只是可选的未完成阶段建议,提示词明确不能把它当成已经发生的事实。当前统一 Router 调用没有传入这个字段 ,不能写成 Story 默认会把剧情计划一起交给 Auto Reply。
唯一锚点与正式正文读取 · 输入与用户自主权要求 · 严格结果解析
如何在现成产品里开启和调用 #
先配置专用模型和提示词 #
在已有完整场景配置的 agenticV2.modelConfigs 中补 autoReply,普通 V2 可同时调整 autoReplyWaitMs:
{
"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模型字段读取 · 等待范围校验 · auto_reply模板要求 · 普通V2/Story分流
再由业务发起 AUTO_REPLY 请求 #
现成产品入口是 Agent Router 的 ChatRequest,chatType 设为 AUTO_REPLY。必要材料包括:
下例基于已有且完整有效的 ChatRequest 修改本次用途; request、runtimeConfig、userId 和 signal 由可信宿主准备,其他项目与协议字段不在此重复展开:
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请求构造示例 · 提前返回分支 · Sumi调用边界
单独二开、返回校验、重试、费用与失败细节
源码入口。 普通 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 计数。若希望发布为客户独立可安装的能力,还需做这一层依赖封装。
这里的“最多 3 次”是 Auto Reply 自己的调用循环;宿主模型适配器若配置了独立模型恢复,还可能在单次调用内尝试恢复。不能把它作为整体所有网络请求数的硬上限。
普通 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 · 普通解析和语言检查 · Story重试和修正 · Pioneer独立包装 · SDK公共导出范围 · 工具包公共导出范围
二开验收应看到的结果 #
先保存一条角色回复,再对它请求建议:得到三个文本,并确认聊天里没有因此多出用户消息。接着让用户选择其中一条,验证它才按正常聊天入口成为下一轮输入。
普通 V2 再测同一回复指导尚未就绪、目标回复之后已有新消息这两种情况;Story 再测请求侧旧正文与已保存正文不一致、正式正文未保存这两种情况。检查候选建议依据了正确的一轮,且失败时没有从其他轮次捡一个状态继续。
基线为 Harness fa41d220d48327e58d994f936517b23f2f15f658;本章区分调用源码、公共包导出与产品接线。配置样例不是所有线上场景已开启的证明。