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

149 lines
8.4 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.
# DashScope 风格兼容对话 API v1 Spec
| 项 | 内容 |
| --- | --- |
| 状态 | Implemented |
| 日期 | 2026-09-05 |
| 负责人 | fire-safety-ymd 后端 |
| 需求来源 | 复用既有客户端的 `/api/v1/apps/{app_id}/completion`、`xtoken` 与 `event: result` 契约 |
## 1. 背景
既有用户端按照 DashScope Application HTTP 形式调用固定应用路径,并以 SSE `result` 事件读取 `output.session_id`、`finish_reason` 和 `text`。本项目原生 `/api/chat` 的请求体与事件名称不同。为避免客户端一次性重写,本 checkpoint 在同一 Chat Service 之上增加一个受限兼容适配层,并保留原生接口。
本契约参考阿里云官方 Application API 的路径、`input.prompt`、`input.session_id` 和 SSE 结果外形,但不是 DashScope 全量代理或完整复刻。
## 2. 目标
- 新增可选的 `POST /api/v1/apps/{app_id}/completion`。
- 接收既有客户端的 `xtoken`、`input.prompt`、可选 `input.session_id` 和 `parameters` 空对象。
- 返回 `event: result` SSE;首个事件提供会话 ID,严格成功事件以 `finish_reason: "stop"` 终止。
- 复用 `/api/chat` 的会话容量、TTL、同会话并发排斥、SuperAgent 严格完成判定和错误分类。
- Nginx 只终止 TLS、限制路径、限流并反代本 Go 服务,不保存或注入任何应用 Secret。
## 3. 非目标与兼容边界
- 不移除或改变原生 `POST /api/chat`。
- 不实现 DashScope 的 `messages`、`biz_params`、文件、图像、知识库、思考过程或任意 `parameters`。
- 不代理到 DashScope,也不把兼容 `app_id` 当成真正的 SuperAgent Profile ID。
- 不向客户端暴露 Provider Session、Run、Trace、工具参数/结果或半截回答。
- 不提供非流式 JSON 模式;即使请求未带 `X-DashScope-SSE: enable`,本兼容路径也始终返回 SSE,以匹配现有客户端。
- `parameters.incremental_output` 仅为输入兼容而接受。v1 只有严格完成后的一个正文结果,因此 `true` 与 `false` 不改变正文分片方式。
- 静态 `xtoken` 只用于受控联调,不是最终用户身份或动态授权。
## 4. 配置与路由
| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `FIRE_SAFETY_CHAT_COMPAT_APP_ID` | 空 | 非空时注册兼容路径;1 至 128 个 ASCII 字母、数字、下划线或连字符 |
| `FIRE_SAFETY_CHAT_AUTH_TOKEN` | 空 | 兼容路径期望的 `xtoken`;默认至少 32 个可打印 ASCII 字符,兼容开关开启时允许已交付的旧短凭证 |
| `FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN` | `false` | 仅为已交付旧客户端短凭证的受控测试/迁移临时兼容开关;新环境不得开启 |
兼容路径只有在 `FIRE_SAFETY_CHAT_ENABLED=true` 且 App ID 非空时注册。App ID 是用于路径匹配的公开标识,不是 Secret;不匹配的路径返回 404。
`FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN` 默认为 `false`。只有在旧客户端已经拿到、且无法立即更换的短 `xtoken` 时,才可在受控测试或迁移窗口显式设置为 `true`。无论开关取值如何,Chat 凭证都必须非空、不超过 4096 字节,并且每个字符都在 ASCII `0x21-0x7e` 范围内(不得包含空格、控制字符或 Unicode);开关同时适用于原生 `/api/chat` 的 Bearer 和本兼容路径的 `xtoken`,不放宽 MCP Token 至少 32 个可打印 ASCII 字符的门禁。Chat、SuperAgent Open API 和 MCP 三种凭证仍必须使用不同值。凭证轮换完成后必须恢复 `false`,新环境不得开启该开关。开关启用时,服务启动日志会记录不含 Secret 的安全 warning,便于后续清理。
## 5. 请求契约
```http
POST /api/v1/apps/fire-safety-public-app/completion
xtoken: <FIRE_SAFETY_CHAT_AUTH_TOKEN>
Content-Type: application/json
Accept: text/event-stream
{
"input": {
"prompt": "杨家盘瞭望哨 3 公里内的水源?"
},
"parameters": {}
}
```
后续轮次把前一轮成功响应的 `output.session_id` 放回 `input`:
```json
{
"input": {
"prompt": "再说明这些候选的限制",
"session_id": "conv_..."
},
"parameters": {
"incremental_output": true
},
"debug": {}
}
```
约束:
- `input.prompt` 必填,非空,并服从现有消息大小上限。
- `input.session_id` 只能是本服务此前返回的本地会话 ID;服务端仍负责映射 Provider Session。
- `parameters` 可省略、为空对象,或只包含布尔型 `incremental_output`。
- `debug` 可省略或只能是空对象。
- 未知字段、`null`、错误类型、尾随 JSON 和超大请求体均拒绝。
- 兼容 DTO 不接收用户身份、角色、区域、租户、Provider Session 或 metadata。
## 6. 成功 SSE
Prepare 成功后先发送会话事件。`finish_reason` 的初始值按官方示例使用字符串 `"null"`,不是 JSON `null`:
```text
id: 1
event: result
:HTTP_STATUS/200
data: {"output":{"session_id":"conv_...","finish_reason":"null"},"usage":{},"request_id":"..."}
```
只有 SuperAgent Adapter 同时验证最终内容、成功 `run.completed` 和顶层 `end` 后,才发送终止事件:
```text
id: 2
event: result
:HTTP_STATUS/200
data: {"output":{"session_id":"conv_...","finish_reason":"stop","text":"..."},"usage":{"models":[{"input_tokens":12,"output_tokens":34,"model_id":"qwen-plus-latest"}]},"request_id":"..."}
```
- 两个事件的 `session_id` 和 `request_id` 必须相同。
- `session_id` 是本地随机会话 ID,不是 Provider Session。
- `model_id` 在上游未提供有效模型名时省略;token 数量是当前 Provider 返回的单轮聚合值。
- 等待期间每 15 秒可发送不含业务数据的 SSE comment heartbeat。
- 客户端只有看到 `event: result` 且 `output.finish_reason == "stop"` 才能把本轮视为成功。
## 7. 错误
SSE 开始前使用 HTTP 状态和 DashScope 风格安全 JSON:
```json
{"code":"CHAT_AUTH_INVALID","message":"Chat authentication failed.","request_id":"..."}
```
SSE 开始后的失败发送 `event: error`,只包含稳定 code、通用 message、request ID 和本地 session ID;不发送 `finish_reason: "stop"`,也不返回任何已接收的部分回答。
沿用原生 Chat 的主要状态:400 输入错误、401 token 错误、403 Origin 错误、404 App/会话不存在、409 会话忙、503 容量不足,以及 502/504 上游失败或超时。
## 8. CORS、Nginx 与 Secret
- 浏览器 Origin 必须精确出现在 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS`;不允许 `*` 或 credentials。
- 预检只允许 `POST` 以及 `Content-Type`、`xtoken`、`X-DashScope-SSE`、`X-Request-ID`。
- Nginx 示例只公开精确兼容路径、`/mcp` 和可选 `/health`,其余路径返回 404。
- Nginx 不比较或注入 `xtoken`、SuperAgent Open API Key、MCP Bearer 或数据库凭证;Header 原样交给 Go 验证。
- 对话 SSE 必须关闭代理缓冲、缓存、gzip 和上游自动重试,并让代理超时覆盖 Chat 总运行时限。
## 9. 验收标准
- Given App ID 未配置,When 请求兼容路径,Then 路由返回 404,原生 `/api/chat` 行为不变。
- Given App ID 或 `xtoken` 错误,When 请求,Then 在读取问题和调用 SuperAgent 前拒绝。
- Given `FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN` 未开启,When 已交付的旧 `xtoken` 少于 32 个字符,Then 服务启动配置校验失败。
- Given `FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN=true`,When 旧短 `xtoken` 用于受控测试/迁移,Then 兼容入口允许该凭证但仍执行非空、4096 字节上限和 ASCII `0x21-0x7e` 校验,并记录不含 Secret 的安全 warning;MCP Token 的至少 32 字符门禁不变。
- Given 首轮请求成功,When 读取 SSE,Then 先收到字符串 `"null"` 会话事件,再收到同会话 `"stop"` 最终正文。
- Given 后续请求携带成功返回的 `session_id`,When 调用,Then 复用同一服务端会话映射。
- Given Provider 流失败,When SSE 已开始,Then 收到安全 `error`,不收到部分正文或 `stop`。
- Given Nginx 配置生效,When 请求未列出的路径,Then 不会转发到 Go 或外部 DashScope。
## 10. 协议来源
- [Alibaba Cloud Model Studio Application API reference](https://www.alibabacloud.com/help/en/model-studio/application-api-reference)
- [Call a Model Studio application by using an API](https://www.alibabacloud.com/help/en/model-studio/application-calling-guide)
外部文档会演进;本项目以本 Spec 和自动化测试定义的受限兼容子集为准。