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

11 KiB
Raw Blame History

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)向 同源兼容路径发送:

POST /api/v1/apps/<public-app-id>/completion
Origin: https://agent.nianxx.com
xtoken: <用户本次手动输入的测试 Token>
Content-Type: application/json
Accept: text/event-stream

首轮请求体:

{
  "input": { "prompt": "观水镇附近有哪些地点候选?" },
  "parameters": {}
}

后续请求体把同一页面内最近一次成功返回的本地会话 ID 放入 input.session_id:

{
  "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:

# 默认关闭
FIRE_SAFETY_CHAT_PAGE_ENABLED=false

# 测试开启时
FIRE_SAFETY_CHAT_PAGE_ENABLED=true
FIRE_SAFETY_CHAT_ALLOWED_ORIGINS=https://agent.nianxx.com

重新加载运行环境:

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

页面验收:

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. 相关文档