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

4.8 KiB
Raw Blame History

用户对话 API v1 架构

1. 组件边界

flowchart LR
    UI[用户侧应用或 curl] -->|POST /api/chat\nChat Bearer + JSON| H[Chat Handler]
    H -->|Prepare / Stream| S[Chat Service]
    S -->|CreateSession / StreamMessage| A[SuperAgent Chat Adapter]
    A -->|Open API Key + HTTPS/SSE| SA[SuperAgent]
    SA -->|独立 MCP Bearer| M[POST /mcp]
    M --> P[(PostgreSQL/PostGIS)]

三种凭证属于不同信任方向:

  • Chat Bearer:用户侧应用到本 Go 服务,仅用于首版受控联调。
  • SuperAgent Open API Key:本 Go 服务到 SuperAgent,只保存在服务端。
  • MCP Bearer:SuperAgent 到本 Go 服务,只保护 /mcp。

三者必须使用不同值。

2. 请求生命周期

  1. Handler 校验请求方法、精确 Origin、Chat Bearer、媒体类型、Accept、请求体大小和严格 JSON。
  2. Service 校验消息和本地 conversation_id。
  3. 首轮请求由 Service 生成随机 conversation_id,并使用服务端固定测试主体创建 SuperAgent Session。
  4. Service 在内存中保存 conversation_id -> provider session_id;Provider Session ID 不返回客户端。
  5. Handler 开始 SSE,先返回 conversation 事件。
  6. Adapter 把消息发送到已准备的 Session,只将经过清洗的 run.* / tool.* 进度投影给 Handler。
  7. Adapter 严格确认最终内容、成功 run.completed 和顶层 end 后,Handler 才发送 message 与 done。
  8. 发生断流、超时、协议错误或失败 Run 时,不返回部分回答;当前映射失效,客户端下一次应创建新对话。

Handler 在没有可公开进度时每 15 秒发送不含数据的 SSE comment heartbeat,并把单请求写期限扩展到配置的总运行 deadline 之后 5 秒,以便返回终止错误。反向代理仍需显式关闭该路径的响应缓冲并设置相容的空闲超时。

3. 会话状态机

不存在
  -> 创建 Provider Session
  -> busy
  -> 成功:idle
  -> 上游结果不确定或失败:删除映射

idle
  -> 新一轮 Prepare:busy
  -> TTL 到期:惰性删除

busy
  -> 同 conversation_id 的并发请求:409

会话存储是单进程内存映射:

  • 进程重启后全部丢失。
  • 多实例之间不共享,未配置粘性路由时后续轮次可能返回 404。
  • 只保存随机本地 ID、Provider Session ID、占用状态和最后使用时间,不保存消息历史。
  • SuperAgent 负责其 Session 内的上下文;本服务不会把完整历史在每轮重新发送。

4. 安全设计

  • 客户端 DTO 没有 user_id、external_subject_id、角色、镇街范围、Provider Session 或 metadata 字段;未知字段直接拒绝。
  • 固定测试主体由服务端配置,不能作为最终用户身份或权限依据。
  • CORS 使用精确 Origin 列表,不支持 * 或 credentials。
  • Handler 日志只记录 request ID、结果、是否复用和耗时,不记录问题、答案或对话 ID。
  • 进度只允许安全字符组成的 run.* / tool.* 事件,以及工具名和状态;消息 delta、Provider ID、工具参数和结果不向客户端透传。
  • 错误映射为稳定代码,不回显 Provider 响应正文、URL、Session、堆栈或 Secret。
  • API 默认关闭;启用必须同时具备有效 SuperAgent 配置和独立 Chat Bearer。
  • Chat 凭证默认至少 32 个可打印 ASCII 字符。为兼容已经交付且无法立即更换的旧客户端短凭证,只有在受控测试/迁移窗口显式设置 FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN=true 才允许短值;无论开关如何,凭证都必须非空、不超过 4096 字节且仅含 ASCII 0x21-0x7e(无空格、控制字符或 Unicode)。该开关同时适用于原生 Bearer 和兼容 xtoken,不影响 MCP 的至少 32 字符门禁;三种凭证必须不同。轮换后恢复 false,新环境不得开启。
  • 开关启用时只记录不含 Secret 的安全 warning,提醒运维在迁移完成后关闭兼容。

5. 伸缩与后续替换点

当前内存实现适合单实例受控联调。进入多实例或真实用户阶段前,需要把 ChatService 的本地状态替换或扩展为:

  • 经验证的最终用户身份与动态数据授权上下文。
  • 持久化或共享的会话映射,并明确并发租约、过期和恢复语义。
  • 网关限流、滥用防护、请求配额和指标。
  • 对话、工具调用和安全事件的脱敏持久审计。
  • 主动取消 Provider Run,以及客户端断开后的终止策略。

这些扩展不能通过简单放宽当前静态 Token 或把客户端用户字段原样传给 Agent 来实现。

既有客户端所需的 DashScope 风格协议通过独立 Handler 适配并复用本文 Chat Service,不改变原生契约。公网入口和两种协议映射见 public-chat-entry-v1.md。