# AI 生成过程草稿持久化设计(2026-04-24) ## 1. 背景 当前创作类模板已经具备 session / message / operation 级别的最终态落库能力,但部分流式生成只把模型增量推给前端。若 HTTP/SSE 连接、浏览器页面或 LLM 请求在最终解析前中断,用户只能看到短暂流式文本,服务端缺少可恢复的生成中间态。 本设计补齐“生成过程中已经生成的内容必须持续持久化”的机制,并要求该机制对所有创作模板统一生效。 ## 2. 目标 1. 每次模板生成开始前创建或绑定一个 `ai_task`。 2. 模型每次产出可见文本增量时,写入 `ai_text_chunk`,并同步更新 `ai_task.latest_text_output` 与对应 stage 的 `text_output`。 3. 生成失败或连接中断时,不丢弃已经落库的 chunk;后续可用 `ai_task.latest_text_output` 作为续写上下文。 4. 成功解析并 finalize 后,将最终结构化结果继续写回各模板原有 session 表,保持现有业务快照不变。 ## 3. 统一落库边界 ### 3.1 真相表 - `ai_task`:记录一次模板生成任务的业务来源、状态、最新聚合文本、结构化结果。 - `ai_task_stage`:记录模板生成阶段状态;当前创作对话统一使用 `DraftGeneration`。 - `ai_text_chunk`:按 `sequence` 追加保存模型增量文本,是断点恢复的最小粒度。 ### 3.2 适用模板 - 自定义世界创作 Agent。 - 解谜游戏创作 Agent。 - 大鱼吃小鱼创作 Agent。 - 后续新增模板必须复用同一生成草稿持久化工具,不允许只在 UI 内存保存流式文本。 ## 4. 续写策略 1. 发起生成时,后端根据 `template_key + session_id + operation_id` 创建稳定 `task_id`。 2. LLM 流式回调收到 `replyText` 的最新可见文本后,计算相对上一次文本的增量;只有非空增量写入 `ai_text_chunk`。 3. 写入失败不应阻断当前生成主流程,但必须记录 warn 日志,避免因持久化瞬时失败导致用户生成直接失败。 4. 若最终解析失败,`ai_task` 保持 `Running` 或显式 `Failed`,已写入的 `latest_text_output` 仍可作为下一轮 prompt 的“已生成草稿”。 5. 下一轮续写 prompt 应优先带上最近未完成任务的 `latest_text_output`;本次先落地服务端 chunk 持久化能力,后续模板 prompt 可逐步消费该草稿。 ## 5. 编码要求 1. 持久化逻辑放在 `server-rs/crates/api-server` 的通用工具中,由各模板路由接入。 2. 不引入 `server-node` 兼容分支。 3. SpacetimeDB 写入必须通过 `spacetime-client` 已生成绑定,不在 reducer 中访问网络或文件系统。 4. 所有新增 Rust 代码保留中文注释,且只做局部修改,避免重写包含中文的大文件。 ## 6. 失败排查原文日志 1. RPG 草稿生成链路的模型输入与模型输出原文日志统一收口在 `platform-llm` 网关层,避免每个模板调用点重复实现。 2. 只有发生请求失败、上游非 2xx、响应读取失败、JSON/SSE 解析失败或空响应时,才将本次模型输入与已拿到的模型输出原文分别写入文件;正常成功生成不默认落盘原文,避免日志体积不可控。 3. 日志目录默认使用仓库运行目录下的 `logs/llm-raw`,可通过 `LLM_RAW_LOG_DIR` 覆盖;每次失败写成同一 trace 前缀下的 `*.input.json` 与 `*.output.txt` 两个 UTF-8 文件。 4. `*.input.json` 记录 provider、model、stream、attempt、maxTokens 与完整 messages;`*.output.txt` 记录上游 HTTP 原文、非流式响应原文、SSE 原始事件文本,或请求尚未到达上游时的错误摘要。 5. 文件名只使用时间戳、进程号、递增序号与安全化错误阶段,不包含用户输入、sessionId 或 API key;输入 JSON 不写入 API key。 6. 文件日志失败只写 warn,不影响草稿生成主错误返回;该日志仅用于本地开发与排障,不作为 SpacetimeDB 真相态。