9.9 KiB
用户对话 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、SuperAgentsession_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-TypeHeader。 - 不启用 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 字节上限和 ASCII0x21-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 提交。