Preserve partial creation replies on stream failure
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# 创作 Agent 流式失败保留可见回复修复 2026-05-05
|
||||
|
||||
## 1. 问题
|
||||
|
||||
方洞挑战等轻量玩法复用 `usePlatformCreationAgentFlowController` 与 `creationAgentSse.ts` 消费 `reply_delta / session / error`。当上游 LLM 已经返回部分 `replyText`,但后续因为超时、上游断流、SSE 解析或最终 JSON 解析失败而发送 `error` 事件时,前端会在 `finally` 里退出流式态。
|
||||
|
||||
旧 UI 只在 `isStreamingReply=true` 时展示临时 assistant 气泡,因此用户会先看到一段回答,然后回答突然消失,只剩错误提示。
|
||||
|
||||
## 2. 目标
|
||||
|
||||
1. 已经展示给用户的流式回复不能因为最终失败从聊天区消失。
|
||||
2. SSE `error` 仍然终止本轮提交,并保留错误提示。
|
||||
3. 后端错误不能只压成 `上游服务请求失败`,应优先把 LLM 流错误原因放到业务 `message`。
|
||||
4. 不修改 SpacetimeDB schema、消息表结构或玩法运行规则。
|
||||
|
||||
## 3. 前端契约
|
||||
|
||||
`readCreationAgentSessionFromSse()` 在收到 `reply_delta` 后再收到 `error` 时,必须先触发 `onUpdate(text)`,再抛出错误。调用方可以从最近一次可见文本中恢复 UI。
|
||||
|
||||
`usePlatformCreationAgentFlowController.submitMessage()` 的失败收尾规则:
|
||||
|
||||
1. 提交时仍先追加 optimistic user message。
|
||||
2. 每次 `onUpdate` 同步更新 `streamingReplyText` 与最近可见回复引用。
|
||||
3. 如果 `streamMessage()` 抛错且最近可见回复非空,把该文本追加为本地 assistant `warning` 消息。
|
||||
4. 再设置 `error`,最后关闭 `isStreamingReply`。
|
||||
5. 成功拿到最终 session 时,以后端 session snapshot 为准,并清空最近可见回复。
|
||||
|
||||
这条本地 `warning` 消息只用于失败态 UI 保留,不代表该 assistant 消息已经写入 SpacetimeDB。
|
||||
|
||||
## 4. 后端契约
|
||||
|
||||
`creation_agent_llm_turn` 在 `LlmClient::stream_text()` 失败时,返回:
|
||||
|
||||
```text
|
||||
<玩法 generation_failed 文案>:<LlmError Display>
|
||||
```
|
||||
|
||||
同时写 `warn` 日志,便于结合 `logs/llm-raw` 定位上游原始输入输出。
|
||||
|
||||
方洞挑战 SSE 错误提取优先级:
|
||||
|
||||
1. `error.details.message`
|
||||
2. `error.message`
|
||||
3. 其它嵌套 JSON message
|
||||
4. 原始 body 文本
|
||||
5. 状态码兜底
|
||||
|
||||
## 5. 验收
|
||||
|
||||
1. `reply_delta` 后收到 `error` 时,测试应断言 `onUpdate` 已经收到可见文本。
|
||||
2. 控制器测试应断言失败后本地消息列表包含 user 消息和 assistant warning 消息。
|
||||
3. `cargo check -p api-server` 通过。
|
||||
4. `npm run typecheck` 与编码检查通过。
|
||||
@@ -22,6 +22,7 @@
|
||||
| 大鱼吃小鱼 | 否 | 是 | 功能仍保留,不在新建作品入口展示 |
|
||||
| 拼图 | 是 | 是 | 点击后进入拼图 Agent 共创工作台 |
|
||||
| 抓大鹅 | 是 | 是 | 点击后进入抓大鹅 Agent 共创工作台 |
|
||||
| 方洞挑战 | 是 | 是 | 点击后进入方洞挑战 Agent 共创工作台,支持草稿、结果页、发布、试玩、作品架与广场 |
|
||||
| AIRP | 是 | 否 | 保留入口,显示敬请期待 |
|
||||
| 视觉小说 | 是 | 否 | 保留入口,显示敬请期待 |
|
||||
|
||||
@@ -31,3 +32,4 @@
|
||||
2. 隐藏玩法不触发入口预加载,也不出现在新建作品入口中。
|
||||
3. 未开放玩法点击态保持禁用,不应进入鉴权或创建会话链路。
|
||||
4. 已开放玩法点击后必须进入对应创建链路;若用户未登录,先走登录保护。
|
||||
5. 方洞挑战作品发布后应生成 `SH-` 作品号,并能从作品架、广场详情和试玩 runtime 回到同一作品详情。
|
||||
|
||||
@@ -29,6 +29,7 @@ spacetime sql <db> "SELECT * FROM custom_world_gallery_entry"
|
||||
| 世界创作 | `custom_world_profile`, `custom_world_session`, `custom_world_agent_session`, `custom_world_agent_message`, `custom_world_agent_operation`, `custom_world_draft_card`, `custom_world_gallery_entry` |
|
||||
| 拼图 | `puzzle_agent_session`, `puzzle_agent_message`, `puzzle_work_profile`, `puzzle_event`, `puzzle_runtime_run`, `puzzle_leaderboard_entry` |
|
||||
| 抓大鹅 Match3D | `match3d_agent_session`, `match3d_agent_message`, `match3d_work_profile`, `match3d_runtime_run` |
|
||||
| 方洞挑战 | `square_hole_agent_session`, `square_hole_agent_message`, `square_hole_work_profile`, `square_hole_runtime_run` |
|
||||
| 大鱼吃小鱼 | `big_fish_creation_session`, `big_fish_agent_message`, `big_fish_asset_slot`, `big_fish_event`, `big_fish_runtime_run` |
|
||||
| 资产 | `asset_object`, `asset_entity_binding`, `asset_event` |
|
||||
| AI 任务 | `ai_task`, `ai_task_stage`, `ai_text_chunk`, `ai_result_reference`, `ai_task_event` |
|
||||
@@ -669,6 +670,53 @@ SELECT * FROM match3d_runtime_run WHERE owner_user_id = '<user_id>' ORDER BY upd
|
||||
SELECT * FROM match3d_runtime_run WHERE profile_id = '<profile_id>';
|
||||
```
|
||||
|
||||
## 方洞挑战表
|
||||
|
||||
### `square_hole_agent_session`
|
||||
|
||||
- 作用:方洞挑战创作 Agent 会话表,保存种子、配置 JSON、草稿 JSON 和发布 profile 指针。
|
||||
- 结构:`session_id PK: String`, `owner_user_id: String`, `seed_text: String`, `current_turn: u32`, `progress_percent: u32`, `stage: String`, `config_json: String`, `draft_json: String`, `last_assistant_reply: String`, `published_profile_id: String`, `created_at: Timestamp`, `updated_at: Timestamp`。
|
||||
- 索引:`owner_user_id`。
|
||||
|
||||
```sql
|
||||
SELECT * FROM square_hole_agent_session WHERE session_id = '<session_id>';
|
||||
SELECT * FROM square_hole_agent_session WHERE owner_user_id = '<user_id>' ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### `square_hole_agent_message`
|
||||
|
||||
- 作用:方洞挑战创作 Agent 消息流水。
|
||||
- 结构:`message_id PK: String`, `session_id: String`, `role: String`, `kind: String`, `text: String`, `created_at: Timestamp`。
|
||||
- 索引:`session_id`。
|
||||
|
||||
```sql
|
||||
SELECT * FROM square_hole_agent_message WHERE session_id = '<session_id>' ORDER BY created_at ASC;
|
||||
```
|
||||
|
||||
### `square_hole_work_profile`
|
||||
|
||||
- 作用:方洞挑战作品主表,保存作品基础信息、反直觉规则、配置、发布状态和游玩次数。
|
||||
- 结构:`profile_id PK: String`, `work_id: String`, `owner_user_id: String`, `source_session_id: String`, `author_display_name: String`, `game_name: String`, `theme_text: String`, `twist_rule: String`, `summary_text: String`, `tags_json: String`, `cover_image_src: String`, `shape_count: u32`, `difficulty: u32`, `config_json: String`, `publication_status: String`, `play_count: u32`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`。
|
||||
- 索引:`owner_user_id`, `publication_status`。
|
||||
|
||||
```sql
|
||||
SELECT * FROM square_hole_work_profile WHERE profile_id = '<profile_id>';
|
||||
SELECT * FROM square_hole_work_profile WHERE owner_user_id = '<user_id>' ORDER BY updated_at DESC;
|
||||
SELECT * FROM square_hole_work_profile WHERE publication_status = 'Published';
|
||||
```
|
||||
|
||||
### `square_hole_runtime_run`
|
||||
|
||||
- 作用:方洞挑战单局运行态表,保存后端权威快照、快照版本、胜负状态和成绩基础字段。
|
||||
- 结构:`run_id PK: String`, `owner_user_id: String`, `profile_id: String`, `status: String`, `snapshot_version: u64`, `started_at_ms: i64`, `duration_limit_ms: i64`, `finished_at_ms: i64`, `elapsed_ms: i64`, `total_shape_count: u32`, `completed_shape_count: u32`, `score: u32`, `snapshot_json: String`, `created_at: Timestamp`, `updated_at: Timestamp`。
|
||||
- 索引:`owner_user_id`, `profile_id`。
|
||||
|
||||
```sql
|
||||
SELECT * FROM square_hole_runtime_run WHERE run_id = '<run_id>';
|
||||
SELECT * FROM square_hole_runtime_run WHERE owner_user_id = '<user_id>' ORDER BY updated_at DESC;
|
||||
SELECT * FROM square_hole_runtime_run WHERE profile_id = '<profile_id>';
|
||||
```
|
||||
|
||||
## 大鱼吃小鱼表
|
||||
|
||||
### `big_fish_creation_session`
|
||||
|
||||
Reference in New Issue
Block a user