Files
fire-safety-ymd/docs/specs/fire-safety-ymd-chat-api-v1.md
T
2026-09-05 17:36:23 +08:00

9.9 KiB
Raw Blame History

用户对话 API v1 Spec

项 内容
状态 Implemented
日期 2026-09-05
负责人 fire-safety-ymd 后端
需求来源 用户要求“先做对话 API”
关联 Change Request 无

1. 背景

项目已经具备 SuperAgent Open API 出站 Adapter 和空间只读 MCP,但用户侧应用还没有安全的服务端对话入口。前端不能直接持有 SuperAgent Open API Key,也不能自行指定 SuperAgent Session、用户身份或授权范围。

本 checkpoint 提供一个最小可联调的对话 API。它由 Go 服务创建并复用 SuperAgent Session,通过 SSE 返回安全进度和最终回答。真实用户认证、动态区域授权、数据库会话持久化和生产审计后续单独设计。

2. 目标

  • 新增默认关闭的 POST /api/chat。
  • 使用独立静态 Bearer Token 保护首版测试入口,不复用 SuperAgent Key 或 MCP Token;默认要求至少 32 个可打印 ASCII 字符。
  • 首轮创建随机本地 conversation_id 和 SuperAgent Session;后续轮次复用映射。
  • 同一 conversation_id 同时只允许一个活动 Run,冲突时返回 409。
  • 通过 SSE 返回对话 ID、安全进度、严格完成后的最终回答和完成事件。
  • 不把消息正文、回答正文、Secret、原始工具参数或输出写入日志。
  • 对输入大小、总运行时间、内存会话数量、空闲过期时间和浏览器 Origin 设上限。

3. 非目标

  • 不实现注册、登录、JWT、SSO、角色、租户或最终用户级数据授权。
  • 不接受客户端提交的 user_id、external_subject_id、SuperAgent session_id、角色或区域范围。
  • 不持久化对话;进程重启或多实例切换后,旧 conversation_id 不可继续使用。
  • 不返回逐字 token、模型原始思考、原始 Trace、工具输入或工具输出。
  • 不实现历史消息查询、会话列表、主动取消、重试队列、限流或 WebSocket。
  • 不把 Agent 回答写入消防业务事实。

4. HTTP 契约

4.1 请求

POST /api/chat
Authorization: Bearer <FIRE_SAFETY_CHAT_AUTH_TOKEN>
Content-Type: application/json
Accept: text/event-stream

{
  "message": "观水镇附近有哪些可用水源?",
  "conversation_id": "conv_..."
}
  • message 必填,去除首尾空白后不能为空,并服从 SuperAgent 单条消息字节上限。
  • conversation_id 首轮省略;后续使用服务返回的随机值。
  • 未知字段、多个 JSON 值、非法媒体类型和超大请求体均拒绝。
  • 客户端不得提交身份、权限、Provider Session 或任意 metadata。

4.2 成功响应

响应媒体类型为 text/event-stream。事件顺序如下:

event: conversation
data: {"conversation_id":"conv_...","reused":false}

event: progress
data: {"event":"tool.started","tool_name":"fire_safety_search_place_candidates","status":"running"}

event: message
data: {"conversation_id":"conv_...","answer":"..."}

event: done
data: {"conversation_id":"conv_...","run_id":"...","usage":{"input":0,"output":0,"total":0}}
  • conversation 在 Provider Session 准备完成后首先发送。
  • progress 只包含经过现有 Adapter 清洗的事件名、工具名和状态;不包含文本、ID、参数或工具结果。
  • 没有业务事件时,服务每 15 秒发送一个 SSE comment heartbeat,保持长连接且不携带业务数据。
  • message 只在 Adapter 同时确认最终内容、成功 run.completed 和顶层 end 后发送。
  • done 是成功终止事件。

4.3 错误响应

在 SSE 开始前,错误使用 HTTP 状态码和稳定 JSON 错误,例如:

{
  "error": {
    "code": "CHAT_AUTH_INVALID",
    "message": "Chat authentication failed."
  },
  "request_id": "..."
}

SSE 开始后的 Provider 失败使用终止 error 事件,不返回部分回答。主要错误类别:

  • CHAT_REQUEST_INVALID:输入或 JSON 不合法,HTTP 400。
  • CHAT_AUTH_INVALID:Bearer 无效,HTTP 401。
  • CHAT_ORIGIN_FORBIDDEN:浏览器 Origin 未获准,HTTP 403。
  • CHAT_CONVERSATION_NOT_FOUND:会话不存在、已过期或服务已重启,HTTP 404。
  • CHAT_CONVERSATION_BUSY:同一会话已有活动 Run,HTTP 409。
  • CHAT_CAPACITY_REACHED:内存会话达到上限,HTTP 503。
  • CHAT_UPSTREAM_TIMEOUT、CHAT_UPSTREAM_UNAVAILABLE、CHAT_UPSTREAM_PROTOCOL_ERROR、CHAT_RUN_FAILED:上游运行失败;SSE 尚未开始时使用 502/504,否则发送 error 事件。

5. 会话与并发规则

  • 映射仅保存在当前 Go 进程内:conversation_id -> SuperAgent session_id。
  • conversation_id 由加密安全随机数生成,不包含用户、镇街或业务语义。
  • 新会话使用服务端固定测试主体 FIRE_SAFETY_CHAT_SUBJECT_ID 创建;客户端不能覆盖。
  • 每次发送消息使用新的幂等键和请求关联 ID。
  • 同一会话的 Run 使用互斥占用;不同会话可并发。
  • 空闲会话超过 TTL 后惰性清理;活动 Run 不清理。
  • 达到最大会话数后先清理过期会话,仍满则拒绝创建。

固定测试主体只是首版联调边界,不代表真实用户认证,也不能用于用户级授权或审计。

6. 配置契约

环境变量 默认值 说明
FIRE_SAFETY_CHAT_ENABLED false 对话 API 总开关;启用时要求 SuperAgent 同时启用
FIRE_SAFETY_CHAT_AUTH_TOKEN 空 独立静态 Bearer;默认至少 32 个可打印 ASCII 字符,兼容开关开启时允许已交付的旧短凭证
FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN false 仅为已交付旧客户端短凭证的受控测试/迁移临时兼容开关;新环境不得开启
FIRE_SAFETY_CHAT_SUBJECT_ID fire-safety-ymd-chat-test-subject 服务端固定测试主体,不使用真实用户标识
FIRE_SAFETY_CHAT_ALLOWED_ORIGINS 空 逗号分隔的精确 HTTP(S) Origin;空值只允许无 Origin 的服务端/CLI 调用
FIRE_SAFETY_CHAT_MAX_BODY_BYTES 131072 HTTP JSON 请求体上限,最大 1 MiB
FIRE_SAFETY_CHAT_RUN_TIMEOUT 10m 创建 Session 加单轮 Run 的总时限,最大 30 分钟
FIRE_SAFETY_CHAT_SESSION_TTL 30m 空闲内存会话保留时间,最大 24 小时
FIRE_SAFETY_CHAT_MAX_SESSIONS 1000 单进程最大内存会话数,最大 10000

FIRE_SAFETY_CHAT_AUTH_TOKEN 必须分别不同于 FIRE_SAFETY_SUPERAGENT_OPEN_API_KEY 和 FIRE_SAFETY_MCP_AUTH_TOKEN。

FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN 默认为 false。只有在旧客户端已经拿到、且无法立即更换的短凭证时,才可在受控测试或迁移窗口显式设置为 true。无论开关取值如何,FIRE_SAFETY_CHAT_AUTH_TOKEN 都必须非空、不超过 4096 字节,并且每个字符都在 ASCII 0x21-0x7e 范围内(不得包含空格、控制字符或 Unicode);开关只影响 Chat 原生 Bearer 和兼容入口的 xtoken,不放宽 MCP Token 至少 32 个可打印 ASCII 字符的门禁。三种凭证仍必须使用不同值。凭证轮换完成后必须恢复 false;新环境不得以该开关绕过默认门禁。开关启用时,服务启动日志会记录不含 Secret 的安全 warning,便于后续清理。

7. CORS 与鉴权边界

  • 无 Origin 的 CLI/服务端请求可以进入 Bearer 校验。
  • 带 Origin 的浏览器请求必须精确匹配配置列表;不支持 *。
  • 预检只允许 POST 以及 Authorization、Content-Type Header。
  • 不启用 Cookie 身份或 CORS credentials。
  • 静态 Bearer 只适合首版受控联调;legacy 短凭证开关只适合明确的迁移窗口,公网最终用户入口必须接入真实身份认证、速率限制和动态授权。

8. 日志与数据边界

  • 只记录 request ID、结果类别、是否复用会话和耗时。
  • 不记录 Bearer、Open API Key、消息、回答、Provider payload、坐标、工具参数或工具输出。
  • 对外错误不包含上游响应正文、URL、Session ID、堆栈或 Secret。
  • SuperAgent 回答属于辅助内容,不自动升级为权威事实,也不替代报警、撤离和现场指挥。

9. 验收标准

  • Given 对话 API 未启用,When 请求 /api/chat,Then 返回 404 且不创建 SuperAgent Client 调用。
  • Given 未授权或 Origin 不在白名单,When 请求 API,Then 在读取和转发消息前拒绝。
  • Given 首轮合法消息,When Provider 严格成功,Then 返回新 conversation_id、最终回答和 done。
  • Given 后续合法消息,When 传入同一 conversation_id,Then 复用原 SuperAgent Session。
  • Given 同一会话已有活动 Run,When 再次发送,Then 返回 409 且不发第二个上游请求。
  • Given Provider 流不完整或 Run 失败,When 处理结束,Then 不发送 message 或 done,只返回安全错误。
  • Given 服务重启、会话过期或随机 ID 不存在,When 继续对话,Then 返回 404 而不把客户端值当作 Provider Session。
  • Given FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN 未开启,When Chat Token 少于 32 个字符,Then 服务启动配置校验失败。
  • Given FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN=true,When 旧短凭证用于受控测试/迁移,Then 原生 Bearer 和兼容 xtoken 均可校验,但非空、4096 字节上限和 ASCII 0x21-0x7e 约束仍生效,且启动日志只记录不含 Secret 的安全 warning。
  • Given 无真实网络和 Secret,When 执行自动化测试,Then 使用本地 fake/模拟 Provider 并全部通过。

10. Definition of Done

  • 配置、Service、SuperAgent 适配、HTTP Handler 和应用装配满足本文契约。
  • 覆盖鉴权、CORS、输入、SSE、复用、并发、过期、容量、上游错误和敏感信息边界测试。
  • gofmt、go test ./...、go test -race ./... 和 go vet ./... 通过。
  • .env.example、集成指南、安全边界、项目索引和 PROJECT_STATE.md 同步。
  • 不提交真实 Secret,不自动创建 Git 提交。