Files
fire-safety-ymd/docs/specs/fire-safety-ymd-chat-page-v1.md
T
2026-09-06 00:25:25 +08:00

217 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fire-safety-ymd 测试对话页面 v1 Spec
| 项 | 内容 |
| --- | --- |
| 状态 | Implemented locally;测试服务器公网部署待验收 |
| 日期 | 2026-09-06 |
| 负责人 | fire-safety-ymd 后端 |
| 关联接口 | `POST /api/v1/apps/{app_id}/completion` |
| 关联配置 | `FIRE_SAFETY_CHAT_PAGE_ENABLED` |
## 1. 背景
项目已有 DashScope 风格的兼容对话接口。SuperAgent、数据库和公网 MCP 联调时,使用者需要
一个无需另建前端工程的最小浏览器页面,用来输入问题、观察 SSE 结果并验证同一会话的后续轮次。
这个页面只服务于测试和受控联调,不改变既有第三方请求契约,也不提供真实用户登录或授权。
## 2. 目标
- 由 Go 服务可选地提供 `GET /chat` 和 `GET /chat/` 两个页面入口,并提供同源的 CSS/JavaScript 资源。
- `FIRE_SAFETY_CHAT_PAGE_ENABLED` 默认值为 `false`;关闭时两个入口都返回 HTTP 404。
- 开启时页面使用与第三方完全相同的兼容对话请求:
`POST /api/v1/apps/{FIRE_SAFETY_CHAT_COMPAT_APP_ID}/completion`。
- 页面让用户手动输入 `xtoken`,在当前页面内发起 SSE 请求,不把凭证写入页面源码、Cookie、
URL、localStorage、sessionStorage 或服务端配置。
- 页面保存上一轮成功响应的 `output.session_id` 仅在 JavaScript 内存中,并将它用于同一页面的
后续轮次。
- 页面能区分 SSE 的中间会话事件、最终 `finish_reason=stop`、上游错误和 HTTP 错误。
- Nginx 公网入口固定反代 `/chat`、`/chat/`、`/chat/app.css`、`/chat/app.js` 和兼容 `completion` 路径;是否实际提供页面由 Go
配置开关决定。
## 3. 非目标
- 不实现注册、登录、JWT、SSO、角色、租户或最终用户级动态授权。
- 不把静态测试 `xtoken` 写入 HTML、JavaScript 常量、Nginx、镜像或 Git。
- 不持久化 Token、问题、答案、聊天历史或会话映射;刷新页面后不自动恢复会话。
- 不新增一套页面专用的聊天协议、Provider Session 或数据库访问接口。
- 不实现逐字流式动画、文件上传、图像、地图选点、后台任务或多实例共享会话。
- 不把页面公开等同于公网生产能力;页面和当前 Chat 兼容 API 都只适用于受控联调。
## 4. 路由与开关
| 路由 | 开关关闭 | 开关开启 | 说明 |
| --- | --- | --- | --- |
| `GET /chat` | 404 | 308 到 `/chat/` | 页面规范化入口 |
| `GET /chat/` | 404 | 200 `text/html` | 页面入口 |
| `GET /chat/app.css` | 404 | 200 `text/css` | 页面样式资源 |
| `GET /chat/app.js` | 404 | 200 `text/javascript` | 页面脚本资源 |
| `POST /api/v1/apps/{app_id}/completion` | 按 Chat/Compat 配置 | 按 Chat/Compat 配置 | 页面和第三方共用 |
页面开关为 Go 进程启动时读取的配置。修改 `.env` 后必须重新创建 Compose 容器;仅执行
`docker compose restart` 不会把新的环境值加载进已有容器。Nginx 可以始终保留四个页面/资源的
精确反代 location,但 Go 关闭页面时必须返回 404。
当页面开启时,以下依赖也必须满足:
- `FIRE_SAFETY_CHAT_ENABLED=true`;
- `FIRE_SAFETY_CHAT_COMPAT_APP_ID` 非空且与 Nginx 的精确 completion location 一致;
- `FIRE_SAFETY_CHAT_AUTH_TOKEN` 已配置为 Chat 方向的测试凭证;
- `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 包含精确值 `https://agent.nianxx.com`;
- SuperAgent 的服务端配置和 MCP 联调配置按对应 Spec 已完成。
页面请求不向 Go 发送身份、角色、区域、租户、Provider Session 或 metadata。页面中出现的
App ID 是公开路由标识,不是 Secret;Chat Token、MCP Bearer 和 SuperAgent Open API Key
必须分别使用不同值。
## 5. 页面行为
页面至少包含:Token 输入框、问题输入框、发送按钮、清空当前会话按钮、运行状态和结果区域。
Token 输入框默认为空,推荐使用密码输入类型;页面不得从 URL、Cookie 或浏览器存储预填。
用户发送问题时,页面使用浏览器 `fetch`(不能使用无法设置自定义 Header 的 `EventSource`)向
同源兼容路径发送:
```http
POST /api/v1/apps/<public-app-id>/completion
Origin: https://agent.nianxx.com
xtoken: <用户本次手动输入的测试 Token>
Content-Type: application/json
Accept: text/event-stream
```
首轮请求体:
```json
{
"input": { "prompt": "观水镇附近有哪些地点候选?" },
"parameters": {}
}
```
后续请求体把同一页面内最近一次成功返回的本地会话 ID 放入 `input.session_id`:
```json
{
"input": {
"prompt": "选择第 2 个,再查询附近水源。",
"session_id": "conv_<previous-result>"
},
"parameters": {}
}
```
页面必须逐个读取 `event: result` 的 SSE 数据:
1. 首个结果一般只包含 `output.session_id` 和 `finish_reason="null"`;页面保存会话 ID,但不把
该事件显示为最终答案。
2. 只有收到同一会话的 `finish_reason="stop"` 且带 `output.text` 时,页面才显示为成功。
3. `event: error`、非 2xx HTTP 响应、解析失败或没有收到 `stop` 时,页面显示失败,不把部分
文本当成可信答案,也不继续复用一个结果未知的会话。
4. 用户点击清空、刷新页面、关闭页面或新开页面时,页面内存中的 Token 和 `session_id` 都丢弃。
页面不应把工具名、工具参数、Provider Session、内部 Trace 或数据库敏感字段直接作为调试
信息展示。最终回答仍是 SuperAgent 辅助内容,不能替代报警、人员撤离、现场核验或现场指挥。
## 6. Origin、Nginx 与安全边界
- 页面与 API 均从 `https://agent.nianxx.com` 提供,因此浏览器的 `Origin` 必须命中 Go 的精确
`FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 项;不得用 `*` 代替。
- Nginx 只负责 TLS、精确路径、限流、SSE 传输和反代,不比较、注入、记录或保存 `xtoken`。
- `/chat` 和 `/chat/` 页面 GET 本身不依赖 Token;真正的兼容 completion 请求仍由 Go 校验
`xtoken`。页面公开可访问不代表 API 无鉴权。
- 页面不会把 Token 放入 Referer、查询参数、片段、日志或错误消息。输入框可被浏览器用户读取,
所以 Token 只能是受控测试凭证,不能作为最终用户身份。
- 页面表单显式使用 POST,CSP 使用 `form-action 'none'`;JavaScript 不可用时页面显示警告,
不允许浏览器把 Token 或问题退化提交到 URL。
- 当前页面和兼容 API 的会话是单进程内存映射;Docker 重建、进程重启或多实例切换会使旧
`session_id` 失效。
- 不通过页面对外暴露 `/api/chat`、数据库、迁移命令或其他 Go 路由;Nginx 未列出的路径仍返回 404。
## 7. 验收标准
### 配置与路由
- Given 未设置 `FIRE_SAFETY_CHAT_PAGE_ENABLED`,When 请求 `/chat`,Then 返回 404;When 请求
`/chat/` 或两个资源路径,Then 均返回 404,且不创建 SuperAgent Run。
- Given `FIRE_SAFETY_CHAT_PAGE_ENABLED=true` 且依赖配置完整,When 请求 `/chat`,Then 返回 308
到 `/chat/`;When 请求 `/chat/`、`/chat/app.css` 和 `/chat/app.js`,Then 分别返回 200 HTML、
CSS 和 JavaScript。
- Given 页面开启但 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 不含 `https://agent.nianxx.com`,Then
配置校验失败或浏览器 completion 请求被精确 Origin 拒绝;不得放行通配符。
- Given 页面关闭,When Nginx 仍保留 `/chat`、`/chat/` location,Then Go 返回 404,Nginx 不应
把它们改成 DashScope 或其他 upstream。
### 浏览器与兼容接口
- Given 页面打开且用户手动输入合法测试 Token,When 发送首轮问题,Then 请求体和 Header 与
第三方 completion 请求相同,并收到 `event: result`。
- Given 首轮最终事件为 `finish_reason="stop"`,When 用户发送第二个问题,Then 请求使用同一页面
内存中的 `input.session_id`,服务端复用会话。
- Given 用户刷新页面,When 再次发送问题,Then 页面不提交旧的 `session_id` 或 Token。
- Given 错误或空 Token,When 发送问题,Then 服务返回 401;页面不展示或记录 Token。
- Given SSE 只有初始 `null` 事件、发生断流或最终不是 `stop`,Then 页面不显示为成功。
### 公网部署
- Given 服务器 Compose 已重建且 Nginx 已通过 `nginx -t` 并 reload,When `curl -i` 请求公网页面,
Then 开关关闭时四个页面路径均返回 404;开启时 `/chat` 返回 308,`/chat/` 返回 200 `text/html`,
两个资源路径返回 200。
- Given 页面开启,When 浏览器从 `https://agent.nianxx.com/chat/` 发起 completion,Then 服务端
日志可按现有脱敏规则确认 Chat 请求,且不含 Token、问题、答案或会话内部 ID。
- Given 已知公网 MCP 地点搜索曾记录
`operation=fire_safety_search_place_candidates result=success`,Then 该证据只证明地点搜索
已到达并成功;`tools/list` 之外的完整多工具链仍需单独验收。
## 8. 运行与回滚
页面开关变更只修改服务器未提交的 `.env`,不修改 Nginx 中的 Token:
```text
# 默认关闭
FIRE_SAFETY_CHAT_PAGE_ENABLED=false
# 测试开启时
FIRE_SAFETY_CHAT_PAGE_ENABLED=true
FIRE_SAFETY_CHAT_ALLOWED_ORIGINS=https://agent.nianxx.com
```
重新加载运行环境:
```bash
cd /home/firee-safety-ymd
docker compose config --quiet
docker compose up -d --force-recreate --no-build
docker compose ps
curl --fail http://127.0.0.1:16587/health
```
页面验收:
```bash
curl -i https://agent.nianxx.com/chat/
curl -i https://agent.nianxx.com/chat
curl -i https://agent.nianxx.com/chat/app.css
curl -i https://agent.nianxx.com/chat/app.js
```
关闭页面时预期 `/chat`、`/chat/` 以及两个资源均直接为 HTTP 404;开启页面时
预期 `/chat` 为 HTTP 308、跟随后 `/chat/` 为 HTTP 200 `text/html`,两个资源也为 HTTP 200。
使用 `curl -iL https://agent.nianxx.com/chat` 可直接跟随规范化重定向。浏览器
打开 `https://agent.nianxx.com/chat/`,手动输入受控测试 Token,发送不含敏感信息的问题,
再发送第二轮问题确认会话复用。
回滚优先使用配置回滚:把 `FIRE_SAFETY_CHAT_PAGE_ENABLED` 改回 `false`,执行
`docker compose up -d --force-recreate --no-build`,确认 `/chat`、`/chat/` 和两个资源路径都直接
返回 404,之后按需继续保留
兼容 completion 或将其一并关闭。若是版本回滚,切回维护者指定的已验证 revision 后重建;若是
Nginx 配置回滚,恢复 root-only 备份,先执行 `nginx -t` 再 reload。不得通过恢复全路径 `/`、
关闭 Go 鉴权或把 Token 写进 Nginx 解决问题。
## 9. 相关文档
- [`../architecture/public-chat-entry-v1.md`](../architecture/public-chat-entry-v1.md)
- [`../workflows/user-chat.md`](../workflows/user-chat.md)
- [`../project/operations/docker-test-deployment.md`](../project/operations/docker-test-deployment.md)
- [`../project/operations/nginx-public-entry.md`](../project/operations/nginx-public-entry.md)
- [`fire-safety-ymd-dashscope-compatible-chat-v1.md`](fire-safety-ymd-dashscope-compatible-chat-v1.md)