# 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: 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 和自动化测试定义的受限兼容子集为准。