@@ -87,7 +87,7 @@
|
||||
4. 重新执行:
|
||||
|
||||
```powershell
|
||||
spacetime generate --no-config --lang rust --out-dir D:\Genarrative\server-rs\crates\spacetime-client\src\module_bindings --module-path D:\Genarrative\server-rs\crates\spacetime-module --include-private --yes
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
```bash
|
||||
spacetime start --listen-addr 127.0.0.1:3000
|
||||
spacetime publish genarrative-dev --server local --yes --module-path server-rs/crates/spacetime-module
|
||||
spacetime generate --lang rust --out-dir server-rs/crates/spacetime-client/src/module_bindings --module-path server-rs/crates/spacetime-module --include-private --yes
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
## 5. 为什么当前阶段不用 CLI 文本解析
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
1. `cargo check -p module-story --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml`
|
||||
2. `cargo check -p spacetime-module --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml`
|
||||
3. `cargo check -p spacetime-module --target wasm32-unknown-unknown --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml`
|
||||
4. `spacetime generate --no-config --lang rust --out-dir D:\\Genarrative\\server-rs\\crates\\spacetime-client\\src\\module_bindings --module-path D:\\Genarrative\\server-rs\\crates\\spacetime-module --include-private --yes`
|
||||
4. `npm run spacetime:generate`
|
||||
5. `cargo check -p spacetime-client --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml`
|
||||
6. `cargo check -p api-server --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml`
|
||||
7. `npm run check:encoding`
|
||||
@@ -234,7 +234,7 @@
|
||||
|
||||
同时,本轮还完成了以下工程收口:
|
||||
|
||||
1. 已重新执行 `spacetime generate --no-config --lang rust --out-dir D:\\Genarrative\\server-rs\\crates\\spacetime-client\\src\\module_bindings --module-path D:\\Genarrative\\server-rs\\crates\\spacetime-module --include-private --yes`。
|
||||
1. 已重新执行 `npm run spacetime:generate`。
|
||||
2. 已把 `spacetime-client` 中 battle query 的占位实现替换为真实 procedure 调用。
|
||||
3. 已再次执行 `cargo check -p spacetime-client --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml` 与 `cargo check -p api-server --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml` 并通过。
|
||||
|
||||
|
||||
@@ -340,7 +340,7 @@ finalScore = tagSimilarityScore * 0.7 + sameAuthorScore * 0.3;
|
||||
4. `cargo check -p module-puzzle`
|
||||
5. `cargo check -p shared-contracts`
|
||||
6. `cargo check -p spacetime-module`
|
||||
7. `spacetime generate --no-config --lang rust --out-dir server-rs/crates/spacetime-client/src/module_bindings --module-path server-rs/crates/spacetime-module --include-private --yes`
|
||||
7. `npm run spacetime:generate`
|
||||
8. `cargo check -p spacetime-client`
|
||||
9. `cargo check -p api-server`
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md](./RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md):记录 RPG 运行时 NPC 聊天、RPG/自定义世界 Agent 与大鱼 Agent 从“拼完整 SSE 字符串后一次性返回”改为 `mpsc + Sse<Event>` 真流式输出的后端落地口径。
|
||||
- [RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md](./RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md):记录 RPG 战斗血条安全锚点、服务端战斗回包前端短表现,以及 `battle_use_skill` 指定技能兜底结算的修复口径。
|
||||
- [SPACETIMEDB_TABLE_CATALOG.md](./SPACETIMEDB_TABLE_CATALOG.md):持续维护当前 SpacetimeDB 表目录,按领域说明每张表的作用、字段结构、索引和常用 `spacetime sql` 查询模板。
|
||||
- [RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md](./RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md):记录开局场景与普通场景复用同一场景展示解析服务,修复列表幕缩略图和详情幕背景预览图片不一致的问题。
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# RPG 与 Agent 聊天真流式 SSE 修复(2026-04-26)
|
||||
|
||||
## 背景
|
||||
|
||||
RPG 运行时 NPC 聊天和创作 Agent 聊天此前都使用 `platform-llm.stream_text(...)`,但部分 HTTP handler 会把 `reply_delta` 先拼进内存字符串,等模型完整返回、建议生成和会话落库完成后才一次性响应。前端虽然消费的是 SSE 协议,实际体验仍接近非流式。
|
||||
|
||||
本次落地把这类路由改成真正边生成边输出:
|
||||
|
||||
1. RPG 运行时 NPC 聊天:`POST /api/runtime/chat/npc/turn/stream`
|
||||
2. RPG/自定义世界创作 Agent:`POST /api/runtime/custom-world/agent/sessions/:sessionId/messages/stream`
|
||||
3. 大鱼吃小鱼创作 Agent:`POST /api/runtime/big-fish/agent/sessions/:sessionId/messages/stream`
|
||||
|
||||
拼图 Agent 已经使用同类 `mpsc + Sse<Event>` 结构,本轮只作为参照,不重复改动。
|
||||
|
||||
## 设计约束
|
||||
|
||||
1. 前端 SSE contract 不变,继续消费 `reply_delta / complete / session / done / error`。
|
||||
2. `reply_delta` 必须在模型生成过程中实时发送,不再等待完整 handler 结束。
|
||||
3. Agent 的最终 `session` 仍必须等 SpacetimeDB `finalize_*` 成功后再发送,保证前端真相态与表内状态一致。
|
||||
4. RPG NPC 聊天的建议、好感变化、敌对终止判定仍在完整回复后计算,最终通过 `complete` 一次性返回。
|
||||
5. LLM 不可用或失败时,RPG NPC 聊天继续走确定性兜底,仍发送至少一条 `reply_delta` 和 `complete`。
|
||||
|
||||
## 落地方案
|
||||
|
||||
### Axum SSE 输出
|
||||
|
||||
相关 handler 改为返回 `Sse::new(async_stream::stream! { ... })`。模型回调不能直接 `yield`,因此使用:
|
||||
|
||||
1. `tokio::sync::mpsc::unbounded_channel::<String>()`
|
||||
2. LLM turn 回调把最新可见回复写入 `reply_tx`
|
||||
3. 外层 `tokio::select!` 同时等待 LLM 完成和 `reply_rx.recv()`
|
||||
4. 每次收到文本立即 `yield Event::default().event("reply_delta")`
|
||||
|
||||
### Agent 写回顺序
|
||||
|
||||
Agent 路由保持原有 submit/finalize 分工:
|
||||
|
||||
1. `submit_*_message` 先写入用户消息与 operation。
|
||||
2. `run_*_agent_turn` 运行模型,同时把 `replyText` 增量推给 SSE。
|
||||
3. turn 完成后构造 `finalize_*_agent_message` 输入并写回 SpacetimeDB。
|
||||
4. finalize 成功后再读取或映射最新 session,发送 `session` 和 `done`。
|
||||
5. finalize 失败时发送 `error` 并结束 SSE。
|
||||
|
||||
## 验收
|
||||
|
||||
1. `cargo fmt -p api-server`
|
||||
2. `cargo check -p api-server`
|
||||
3. `cargo test -p api-server runtime_chat`
|
||||
4. `cargo test -p api-server creation_agent_llm_turn`
|
||||
5. `node scripts/check-encoding.mjs docs/technical/RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md server-rs/crates/api-server/src/runtime_chat.rs server-rs/crates/api-server/src/custom_world.rs server-rs/crates/api-server/src/big_fish.rs docs/technical/README.md`
|
||||
6. 修改后端代码后,使用 `npm run api-server:maincloud` 重启后端。
|
||||
@@ -47,6 +47,7 @@
|
||||
- 优先 `LlmClient.stream_text(...)` 生成 `reply_delta`。
|
||||
- 再调用 `request_text(...)` 生成建议。
|
||||
- 计算 `affinityDelta / affinityText / chatDirective` 后输出 `complete`。
|
||||
- SSE stream 返回给 Axum 时要求 `'static`,进入 stream 的玩家输入必须先转成 owned `String`,不能把 handler 栈上的 `&str` 借入 stream。
|
||||
3. 修改 `server-rs/crates/api-server/src/auth.rs`
|
||||
- `allows_internal_forwarded_auth(...)` 允许 `/api/runtime/chat/`,与 big-fish、puzzle 的内部转发鉴权策略保持一致。
|
||||
- 单测覆盖 `/api/runtime/chat/npc/turn/stream`,防止后续新增 runtime 路由时再次遗漏内部转发白名单。
|
||||
|
||||
@@ -34,7 +34,7 @@ npm run dev:rust
|
||||
1. 检查 `cargo`、`node` 与 `spacetime` CLI。
|
||||
2. Windows Git Bash 下如 `server-rs/.spacetimedb/local/bin/current/spacetimedb-cli.exe` 不存在,先把本机 `spacetime` 所在安装目录的 `bin/` 与 `spacetime.exe` 同步到 `server-rs/.spacetimedb/local/`。
|
||||
3. 启动 `spacetime --root-dir=server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101`,确保本地数据库与 SpacetimeDB 内部日志不会落到开发者全局目录。
|
||||
4. 等待 `spacetime --root-dir=server-rs/.spacetimedb/local server ping http://127.0.0.1:3101` 可用。
|
||||
4. 等待 `spacetime --root-dir=server-rs/.spacetimedb/local server ping http://127.0.0.1:3101` 可用;判定标准必须包含输出中的 `Server is online:`,不能只依赖 CLI 退出码,因为 SpacetimeDB CLI `2.1.0` 在 `502 Bad Gateway` 时也可能返回退出码 `0`。
|
||||
5. 执行 `spacetime --root-dir=server-rs/.spacetimedb/local publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes`,确保 publish 的签名身份与 standalone 的本地控制库一致,并在当前开发阶段允许新版模块表结构变化且发生 schema 冲突时清除旧模块数据。
|
||||
6. 注入 `GENARRATIVE_API_*` 与 `GENARRATIVE_SPACETIME_*` 后启动 `cargo run -p api-server`;直接运行 `api-server` 时,如未显式设置 `GENARRATIVE_SPACETIME_DATABASE`,服务端也会向上查找 `spacetime.local.json` 作为本地默认库名。
|
||||
7. 等待 `http://127.0.0.1:<api-port>/healthz` 返回 HTTP 响应后再启动 Vite,避免前端初始化请求早于 Rust `api-server` 监听完成并在终端刷出 `ECONNREFUSED 127.0.0.1:<api-port>`。
|
||||
@@ -65,6 +65,23 @@ npm run dev:rust
|
||||
./scripts/dev-rust-stack.sh --preserve-database
|
||||
```
|
||||
|
||||
bindings 生成:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate
|
||||
npm run spacetime:generate -- --rust-only
|
||||
```
|
||||
|
||||
生成规则:
|
||||
|
||||
1. `npm run spacetime:generate` 是本仓库刷新 SpacetimeDB Rust client bindings 的唯一推荐入口。
|
||||
2. 前端不直连 SpacetimeDB,不生成 TypeScript bindings;前端只通过 Rust `api-server` 暴露的 HTTP / SSE contract 访问后端。
|
||||
3. Rust bindings 不生成私有表绑定,不追加 `--include-private`,生成到短临时目录后同步到 `server-rs/crates/spacetime-client/src/module_bindings`。
|
||||
4. Windows 下 SpacetimeDB CLI `2.1.0` 会在生成结束后把所有生成文件路径一次性传给 formatter;Rust bindings 文件较多,若直接输出到仓库深目录,可能触发 `Could not format generated files: 文件名或扩展名太长。`。该错误不是单个最长文件路径超过限制,而是 formatter 子进程参数总长超过 Windows `CreateProcess` 限制。
|
||||
5. CLI 在上述 formatter 失败时仍可能返回退出码 `0`;脚本会捕获输出,只要出现 `Could not format generated files` 就视为失败。
|
||||
6. 根目录 `spacetime.json` 不配置 `generate` 目标,避免裸 `spacetime generate` 在 Windows 上直接碰到 Rust formatter 路径总长限制,也避免误生成前端 bindings。
|
||||
7. 不直接手写或局部补 `server-rs/crates/spacetime-client/src/module_bindings` 下的生成文件;schema 变化后重新执行本脚本,并补跑编码检查与对应类型检查。
|
||||
|
||||
日志提取:
|
||||
|
||||
```bash
|
||||
@@ -86,6 +103,7 @@ npm run dev:rust:logs -- --follow
|
||||
2. `spacetime --root-dir=server-rs/.spacetimedb/local list --server http://127.0.0.1:3101` 应能看到 `spacetime.local.json` 中的库名;若没有,执行 `spacetime --root-dir=server-rs/.spacetimedb/local publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes`。
|
||||
3. 发布库名与 `GENARRATIVE_SPACETIME_DATABASE` 不一致时,`/api/runtime/custom-world-gallery` 会从 Rust `api-server` 返回 `502`,前端首页只能展示空态或错误提示,无法自行修复。
|
||||
4. 如果 Vite 输出 `/api/auth/refresh`、`/api/auth/login-options` 或 `/api/runtime/custom-world-gallery` 的 `ECONNREFUSED`,先确认当前脚本是否已经打印 `等待 api-server 就绪` 并通过;正常情况下 Vite 只会在 `/healthz` 可访问后启动,不应再因为 Rust 监听未完成而代理失败。
|
||||
5. 如果 `spacetime server ping` 打印 `Server could not be reached (502 Bad Gateway)`,即使命令退出码为 `0` 也必须视为未就绪;脚本会继续等待新启动实例,或在 root-dir 已被其他实例占用时输出占用进程。
|
||||
|
||||
编译警告治理:
|
||||
|
||||
@@ -93,6 +111,12 @@ npm run dev:rust:logs -- --follow
|
||||
2. 仅供测试断言使用的辅助函数使用 `#[cfg(test)]` 限定,避免进入 `cargo run -p api-server` 的普通二进制编译。
|
||||
3. 已无调用入口且无迁移价值的映射函数直接删除;如果后续新增同类 SpacetimeDB 记录映射,再按实际调用路径补回,避免提前保留死代码。
|
||||
|
||||
Maincloud API 重启补充:
|
||||
|
||||
1. `npm run api-server:maincloud` 会先读取 `.env`、`.env.local`,把 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 映射为 `api-server` 使用的 `GENARRATIVE_SPACETIME_*`,再运行 `cargo run -p api-server --manifest-path server-rs/Cargo.toml`。
|
||||
2. Windows 下脚本会尽力停止本仓库 `server-rs/target/debug/api-server.exe` 对应的旧进程,避免 cargo 重新编译时 exe 被占用。
|
||||
3. 旧进程已经退出或清理过程中出现瞬时等待失败时,不应阻断新的 `api-server` 启动;脚本只记录清理失败并继续启动。
|
||||
|
||||
## 3. Ubuntu 发布包脚本
|
||||
|
||||
入口:
|
||||
|
||||
@@ -48,7 +48,7 @@ spacetime publish xushi-p4wfr --server http://127.0.0.1:3101 --module-path D:\Ge
|
||||
publish 成功后,继续执行:
|
||||
|
||||
```bash
|
||||
spacetime generate --no-config --lang rust --out-dir D:\Genarrative\server-rs\crates\spacetime-client\src\module_bindings --module-path D:\Genarrative\server-rs\crates\spacetime-module --include-private --yes
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
@@ -164,19 +164,18 @@ Stage 5 已接入 agent `publish_world` action,但调用方仍需要显式提
|
||||
本仓默认刷新命令:
|
||||
|
||||
```bash
|
||||
spacetime generate --no-config --lang rust --out-dir server-rs/crates/spacetime-client/src/module_bindings --module-path server-rs/crates/spacetime-module --include-private --yes
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
如果当前工作树里存在其他并行 `cargo build/test/check` 导致 `spacetime generate` 内部构建长期卡在锁竞争,则先在独立目标目录编译 wasm,再使用 `--bin-path` 生成:
|
||||
如果当前工作树里存在其他并行 `cargo build/test/check` 导致 `spacetime generate` 内部构建长期卡在锁竞争,则先结束占锁任务后重新执行仓库脚本;脚本内部会使用短临时目录生成公开 Rust bindings,不生成私有表绑定。
|
||||
|
||||
```bash
|
||||
CARGO_TARGET_DIR=server-rs/target-spacetime-bindgen cargo build --manifest-path server-rs/crates/spacetime-module/Cargo.toml --target wasm32-unknown-unknown --release
|
||||
spacetime generate --no-config --lang rust --out-dir server-rs/crates/spacetime-client/src/module_bindings --bin-path server-rs/target-spacetime-bindgen/wasm32-unknown-unknown/release/spacetime_module.wasm --include-private --yes
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
这样做的目的固定如下:
|
||||
|
||||
1. 避开根目录 `spacetime.json` 多 generate target 对单次 Rust 生成的参数冲突。
|
||||
1. 避开根目录 `spacetime.json` 误触发前端 bindings 或私有表 bindings 生成。
|
||||
2. 避开共享 `target/` 目录被其他任务占锁时的构建阻塞。
|
||||
3. 保证 `create_custom_world_agent_session` / `get_custom_world_agent_session` 这类新 procedure 与输入输出类型能同步进入 Rust bindings。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user