# M4 module-ai Axum facade 设计(2026-04-22) 更新时间:`2026-04-22` ## 0. 文档目标 本文件只冻结一件事: **把已经在 `spacetime-module` 落地的 `module-ai` 任务真相表与最小 procedure / reducer,继续向上接到 `shared-contracts`、`spacetime-client` 与 `api-server`,形成可由 HTTP 直接调用的 AI task mutation facade。** 本轮只做最小同步 mutation 链,不扩到 SSE、真实模型供应商请求或前端订阅。 --- ## 1. 本轮要解决的问题 当前仓库已经具备: 1. `module-ai` - 统一 `AiTaskKind / AiTaskStageKind / AiResultReferenceKind` - 统一任务、阶段、文本片段、结果引用领域模型 2. `spacetime-module` - `ai_task / ai_task_stage / ai_text_chunk / ai_result_reference` - `create / append / complete / attach / fail / cancel` 最小 procedure - `start_ai_task / start_ai_task_stage` reducer 3. `spacetime-client` - 已生成 AI 相关 Rust bindings 但当前仍缺三层: 1. `shared-contracts` 还没有 AI task HTTP DTO 2. `spacetime-client` 还没有 AI facade 方法与 record 映射 3. `api-server` 还没有 `/api/ai/tasks*` 路由 因此本轮只补下面三层: 1. `shared-contracts` AI DTO 2. `spacetime-client` AI facade 3. `api-server` AI tasks HTTP route --- ## 2. 当前明确不做的事 本轮明确不做: 1. 不接入真实 `platform-llm` 流式回调 2. 不提供 SSE 增量推送接口 3. 不增加 AI task 查询 / 订阅 projection 4. 不把 story / npc / quest / custom-world 旧入口自动迁到这组新接口 5. 不修改 `spacetime-client/src/module_bindings/*` 生成文件 原因很直接: 1. 当前先把 AI task mutation 的最小 HTTP contract 固定下来 2. SSE 与查询态必须等待后续订阅策略或 query procedure 冻结 3. 业务编排入口切换应该在上层模块各自评估,不在本轮提前硬迁 --- ## 3. 路由冻结 本轮首版新增以下路由: 1. `POST /api/ai/tasks` 2. `POST /api/ai/tasks/{taskId}/start` 3. `POST /api/ai/tasks/{taskId}/stages/{stageKind}/start` 4. `POST /api/ai/tasks/{taskId}/chunks` 5. `POST /api/ai/tasks/{taskId}/stages/{stageKind}/complete` 6. `POST /api/ai/tasks/{taskId}/references` 7. `POST /api/ai/tasks/{taskId}/complete` 8. `POST /api/ai/tasks/{taskId}/fail` 9. `POST /api/ai/tasks/{taskId}/cancel` ### 3.1 同步返回路由 当前下列路由走 `procedure`,成功时同步返回 `aiTask` 快照: 1. `POST /api/ai/tasks` 2. `POST /api/ai/tasks/{taskId}/chunks` 3. `POST /api/ai/tasks/{taskId}/stages/{stageKind}/complete` 4. `POST /api/ai/tasks/{taskId}/references` 5. `POST /api/ai/tasks/{taskId}/complete` 6. `POST /api/ai/tasks/{taskId}/fail` 7. `POST /api/ai/tasks/{taskId}/cancel` 其中: 1. `chunks` 额外返回 `aiTextChunk` 2. 其他 mutation 当前只返回 `aiTask` ### 3.2 Accepted 路由 当前下列路由只接 `reducer`,不会同步返回快照: 1. `POST /api/ai/tasks/{taskId}/start` 2. `POST /api/ai/tasks/{taskId}/stages/{stageKind}/start` 因此本轮明确冻结为: 1. HTTP 成功状态码返回 `202 Accepted` 2. body 只返回: - `accepted` - `taskId` - `action` - `stageKind`(仅 stage start) 3. 不伪装成“已经拿到最新任务快照” 后续如果要让这两条路由也同步返回快照,应先在 `spacetime-module` 增加对应 procedure。 --- ## 4. 请求与响应 DTO 冻结 ### 4.1 创建任务请求 `POST /api/ai/tasks` 请求体冻结为: 1. `taskKind` 2. `requestLabel` 3. `sourceModule` 4. `sourceEntityId` 5. `requestPayloadJson` 6. `stageKinds` 其中: 1. `taskId` 不接受外部写入,由 Axum 使用 `generate_ai_task_id(nowMicros)` 生成 2. `ownerUserId` 不接受外部写入,必须取自 Bearer token 3. `stageKinds` 为空时,由 `module-ai` 根据 `taskKind.default_stage_blueprints()` 自动补齐默认阶段蓝图 ### 4.2 追加文本片段请求 `POST /api/ai/tasks/{taskId}/chunks` 请求体冻结为: 1. `stageKind` 2. `sequence` 3. `deltaText` ### 4.3 完成阶段请求 `POST /api/ai/tasks/{taskId}/stages/{stageKind}/complete` 请求体冻结为: 1. `textOutput` 2. `structuredPayloadJson` 3. `warningMessages` ### 4.4 绑定结果引用请求 `POST /api/ai/tasks/{taskId}/references` 请求体冻结为: 1. `referenceKind` 2. `referenceId` 3. `label` ### 4.5 失败请求 `POST /api/ai/tasks/{taskId}/fail` 请求体冻结为: 1. `failureMessage` ### 4.6 成功响应 本轮统一返回以下 payload: 1. `AiTaskPayload` 2. `AiTaskStagePayload` 3. `AiResultReferencePayload` 4. `AiTextChunkPayload` 5. `AiTaskMutationResponse` 6. `AiTaskAcceptedResponse` 时间字段继续统一为 RFC3339 字符串。 --- ## 5. `spacetime-client` 冻结口径 本轮新增以下 facade: 1. `create_ai_task` 2. `start_ai_task` 3. `start_ai_task_stage` 4. `append_ai_text_chunk` 5. `complete_ai_stage` 6. `attach_ai_result_reference` 7. `complete_ai_task` 8. `fail_ai_task` 9. `cancel_ai_task` ### 5.1 输入边界 1. procedure 输入直接复用 `module-ai` 领域输入结构 2. `start_ai_task` 与 `start_ai_task_stage` 直接复用 reducer 输入结构 3. 不让 `api-server` 直接依赖 generated binding 类型 ### 5.2 输出边界 `spacetime-client` 新增下列 record,供 `api-server` 直接消费: 1. `AiTaskRecord` 2. `AiTaskStageRecord` 3. `AiTextChunkRecord` 4. `AiResultReferenceRecord` 5. `AiTaskMutationRecord` 字符串字段规范: 1. `taskKind` 使用: - `story_generation` - `character_chat` - `npc_chat` - `custom_world_generation` - `quest_intent` - `runtime_item_intent` 2. `stageKind` 使用 `module-ai::AiTaskStageKind::as_str()` 3. `status` 使用 snake_case 4. `referenceKind` 使用 snake_case ### 5.3 错误映射 AI facade 在 `spacetime-client` 内部按以下规则区分: 1. procedure / reducer 返回的业务拒绝 - 映射为 `SpacetimeClientError::Runtime` 2. SDK 调用、连接、超时、意外缺字段 - 映射为 `Build / Procedure / ConnectDropped / Timeout` 这样 `api-server` 才能稳定把业务错误映射成 `400`。 --- ## 6. `api-server` 冻结口径 ### 6.1 鉴权与身份 所有 `/api/ai/tasks*` 路由继续统一挂 Bearer 鉴权。 其中: 1. `ownerUserId` 必须来自 `AuthenticatedAccessToken.claims().user_id()` 2. 不接受前端自行写入任务所有者 ### 6.2 时间与 ID 以下字段不接受外部写入: 1. `taskId` 2. `createdAtMicros` 3. `startedAtMicros` 4. `completedAtMicros` 统一由 Axum 在请求进入时生成。 ### 6.3 字段解析 `api-server` 负责把 HTTP 字符串解析为领域枚举: 1. `taskKind` 2. `stageKind` 3. `referenceKind` 解析失败统一返回 `400`,`details.provider` 分别写: 1. `ai-task` 2. `ai-task-stage` 3. `ai-task-reference` --- ## 7. 错误映射 本轮 AI facade 的错误策略冻结如下: 1. 请求 JSON 非法、路径字段非法、枚举解析失败:`400` 2. `SpacetimeClientError::Runtime(_)`:`400` 3. 其他 `SpacetimeClientError`:`502` `details.provider` 统一写: 1. 路由入参准备错误:`ai-task` 2. SpacetimeDB 上游错误:`spacetimedb` --- ## 8. 本轮验收口径 满足以下条件,视为本轮 facade 基线完成: 1. `shared-contracts` 已新增 `ai.rs` 2. `spacetime-client` 已新增 AI facade 方法与 record 映射 3. `api-server` 已新增 `ai_tasks.rs` 4. `/api/ai/tasks*` 路由已注册并挂 Bearer 鉴权 5. `cargo fmt -p shared-contracts -p spacetime-client -p api-server` 通过 6. `cargo check -p shared-contracts -p spacetime-client -p api-server` 通过 --- ## 9. 下一步建议 本轮完成后,后续最稳的顺序是: 1. 为 `start_ai_task / start_ai_task_stage` 增加同步 procedure 2. 增加 AI task 查询态或订阅 projection 3. 再把 `platform-llm` 流式回调真正接到 `append_ai_text_chunk / complete_ai_stage / fail_ai_task` 4. 最后再把 story / npc / custom-world / quest / runtime-item 的 AI 编排主链逐步切到这组新接口