后端重写提交

This commit is contained in:
2026-04-22 12:34:49 +08:00
parent cf8da3f50f
commit 997a8daada
438 changed files with 53355 additions and 865 deletions

View File

@@ -0,0 +1,306 @@
# 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 编排主链逐步切到这组新接口