ds2api / API.md
luckfun233
feat(prompt): 新增 prompt_style 风格档位与自定义模型列表
fcdd4aa
|
Raw History Blame
73.6 kB
# DS2API 接口文档1
语言 / Language: [中文](API.md) | [English](API.en.md)
本文档描述当前 Go 代码库的实际 API 行为。
文档导航:[总览](README.MD) / [架构说明](docs/ARCHITECTURE.md) / [部署指南](docs/DEPLOY.md) / [测试指南](docs/TESTING.md) / [上下文兼容流程](docs/prompt-compatibility.md)
---
## 目录
- [基础信息](#基础信息)
- [配置最佳实践](#配置最佳实践)
- [鉴权规则](#鉴权规则)
- [路由总览](#路由总览)
- [健康检查](#健康检查)
- [OpenAI 兼容接口](#openai-兼容接口)
- [Claude 兼容接口](#claude-兼容接口)
- [Gemini 兼容接口](#gemini-兼容接口)
- [Ollama 兼容接口](#ollama-兼容接口)
- [Admin 接口](#admin-接口)
- [错误响应格式](#错误响应格式)
- [cURL 示例](#curl-示例)
---
## 基础信息
| 项目 | 说明 |
| --- | --- |
| Base URL | `http://localhost:5001` 或你的部署域名 |
| 默认 Content-Type | `application/json` |
| 健康检查 | `GET /healthz`、`GET /readyz` |
| CORS | 已启用(统一覆盖 `/v1/*`、`/anthropic/*`、`/v1beta/models/*`、`/api/*`、`/admin/*`;浏览器有 `Origin` 时回显该 Origin,否则为 `*`;默认允许 `Content-Type`, `Authorization`, `X-API-Key`, `X-Ds2-Target-Account`, `X-Ds2-Source`, `X-Vercel-Protection-Bypass`, `X-Goog-Api-Key`, `Anthropic-Version`, `Anthropic-Beta`,并会放行预检里声明的第三方请求头,如 `x-stainless-*`;Vercel 上 `/v1/chat/completions` 的 Node Runtime 也对齐相同行为;内部专用头 `X-Ds2-Internal-Token` 仍被拦截) |
- 所有 JSON 请求体都必须是合法 UTF-8;非法字节序列会在入站阶段被拒绝为 `400 invalid json`。
### 3.0 接口适配层说明
- OpenAI / Claude / Gemini 三套协议已统一挂在同一 `chi` 路由树上,由 `internal/server/router.go` 负责装配。
- 适配器层职责收敛为:**请求归一化 → DeepSeek 调用 → 协议形态渲染**,减少历史版本中“同能力多处实现”的分叉。
- 从协议请求到纯文本 web-chat prompt 的完整链路(统一标准请求、思考格式注入、历史拆分、完成载荷组装、响应归档)见 [docs/prompt-compatibility.md](docs/prompt-compatibility.md),该文是这条流程的源文档;本文只描述各接口的可见字段与响应形态。
- Tool Calling 的解析策略在 Go 与 Node Runtime 间保持一致:推荐模型输出半角管道符 DSML 外壳 `<|DSML|tool_calls>` → `<|DSML|invoke name="...">` → `<|DSML|parameter name="...">`;兼容层也接受 DSML wrapper 别名 `<dsml|tool_calls>`、`<|tool_calls>`、常见 DSML 分隔符漏写形态(如 `<|DSML tool_calls>`)、`DSML` 与工具标签名黏连的常见 typo(如 `<DSMLtool_calls>`)、控制分隔符漂移(如 `<DSML␂tool_calls>` / 原始 STX `\x02`)、CJK 尖括号、全角感叹号、顿号、PascalCase 本地名、弯引号属性值与属性尾部分隔符漂移(如 `<DSM|parameter name="command"|>...〈/DSM|parameter〉` / `<!DSML!invoke name=“Bash”>` / `<、DSML、tool_calls>` / `<DSmartToolCalls>` / `<DSMLtool_calls※>`)、任意协议前缀壳(如 `<proto💥tool_calls>`),以及旧式 canonical XML `<tool_calls>` → `<invoke name="...">` → `<parameter name="...">`。实现上采用结构扫描:只要固定本地标签名是 `tool_calls` / `invoke` / `parameter`,标签名前或标签名后的非结构性分隔符会在解析入口归一化;CDATA 开头也会容错 `<![CDATA[` / `<、[CDATA[` 这类分隔符漂移;只有 `tool_calls` wrapper 或可修复的缺失 opening wrapper 会进入工具路径,裸 `<invoke>` 不计为已支持语法;流式场景继续执行防泄漏筛分。若参数体本身是合法 JSON 字面量(如 `123`、`true`、`null`、数组或对象),会按结构化值输出,不再一律当作字符串;显式空字符串和纯空白参数会结构化保留为空字符串,是否拒绝缺参由工具执行侧决定;完整但 malformed 的 wrapper 会作为普通文本释放,不会吞掉或伪造成工具调用;若 CDATA 偶发漏闭合,则会在最终 parse / flush 恢复阶段做窄修复,尽量保住已完整包裹的外层工具调用。
- `Admin API` 将配置与运行时策略分开:`/admin/config*` 管静态配置,`/admin/settings*` 管运行时行为。
- 当上游返回 thinking-only 响应(模型输出了推理链但无可见文本)时,Go 主路径与 Vercel Node 流式路径都会先自动重试一次:以多轮对话 follow-up 方式追加 prompt 后缀 `"Please provide a non-empty final answer or tool call."` 并设置 `parent_message_id` 在同一 DeepSeek session 内让模型重新输出;同账号重试最大 1 次。若同账号重试后仍即将返回 `429 upstream_empty_output`,托管账号模式会在返回 429 前自动切换到下一个可用账号,新建 session,用原始 payload 再 fresh retry 一次。
- 引用标记处理边界:流式输出默认隐藏 `[citation:N]` / `[reference:N]` 这类上游内部占位符;非流式输出默认把 DeepSeek 搜索引用标记转换为 Markdown 引用链接。
---
## 配置最佳实践
推荐把 `config.json` 作为唯一配置源:
```bash
cp config.example.json config.json
# 编辑 config.json(keys/accounts)
```
按部署方式使用:
- 本地运行:直接读取 `config.json`
- Docker / Vercel:从 `config.json` 生成 Base64,填入 `DS2API_CONFIG_JSON`,也可以直接填原始 JSON
```bash
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
```
Vercel 一键部署可先只填 `DS2API_ADMIN_KEY`,部署后在 `/admin` 导入配置,再通过 “Vercel 同步” 写回环境变量。
---
## 鉴权规则
### 业务接口(`/v1/*`、`/anthropic/*`、`/v1beta/models/*`)
支持两种传参方式:
| 方式 | 示例 |
| --- | --- |
| Bearer Token | `Authorization: Bearer <token>` |
| API Key Header | `x-api-key: <token>`(无 `Bearer` 前缀) |
| Gemini 兼容 | `x-goog-api-key: <token>` 或 `?key=<token>` / `?api_key=<token>` |
**鉴权行为**:
- token 在 `config.keys` 中 → **托管账号模式**,自动轮询选择账号
- token 不在 `config.keys` 中 → **直通 token 模式**,直接作为 DeepSeek token 使用
**可选请求头**:`X-Ds2-Target-Account: <email_or_mobile>` — 指定使用某个托管账号;如果目标账号不存在,或管理账号队列已耗尽,相关业务请求会返回 `429`,当前不会附带 `Retry-After` 头。若账号存在但登录/刷新失败,则返回对应的 `401` 或上游错误。未指定目标账号时,托管账号模式的 completion 空输出 429 会先尝试切到另一个可用账号 fresh retry 一次;指定目标账号或无其他可用账号时不会切号。
Gemini 兼容客户端还可以使用 `x-goog-api-key`、`?key=` 或 `?api_key=` 作为凭据来源。
### Admin 接口(`/admin/*`)
| 端点 | 鉴权 |
| --- | --- |
| `POST /admin/login` | 无需鉴权 |
| `GET /admin/verify` | `Authorization: Bearer <jwt>`(仅 JWT) |
| 其他 `/admin/*` | `Authorization: Bearer <jwt>` 或 `Authorization: Bearer <admin_key>`(直传管理密钥) |
---
## 路由总览
| 方法 | 路径 | 鉴权 | 说明 |
| --- | --- | --- | --- |
| GET | `/healthz` | 无 | 存活探针 |
| HEAD | `/healthz` | 无 | 存活探针(无响应体) |
| GET | `/readyz` | 无 | 就绪探针 |
| HEAD | `/readyz` | 无 | 就绪探针(无响应体) |
| GET | `/v1/models` | 无 | OpenAI 模型列表 |
| GET | `/v1/models/{id}` | 无 | OpenAI 单模型查询(支持 alias 入参) |
| POST | `/v1/chat/completions` | 业务 | OpenAI 对话补全 |
| POST | `/v1/responses` | 业务 | OpenAI Responses 接口(流式/非流式) |
| GET | `/v1/responses/{response_id}` | 业务 | 查询已生成 response(内存 TTL) |
| POST | `/v1/embeddings` | 业务 | OpenAI Embeddings 接口 |
| POST | `/v1/files` | 业务 | OpenAI Files 上传(multipart/form-data) |
| GET | `/v1/files/{file_id}` | 业务 | 查询已上传文件状态 |
| GET | `/anthropic/v1/models` | 无 | Claude 模型列表 |
| POST | `/anthropic/v1/messages` | 业务 | Claude 消息接口 |
| POST | `/anthropic/v1/messages/count_tokens` | 业务 | Claude token 计数 |
| POST | `/v1/messages` | 业务 | Claude 消息快捷路径 |
| POST | `/messages` | 业务 | Claude 消息快捷路径 |
| POST | `/v1/messages/count_tokens` | 业务 | Claude token 计数快捷路径 |
| POST | `/messages/count_tokens` | 业务 | Claude token 计数快捷路径 |
| POST | `/v1beta/models/{model}:generateContent` | 业务 | Gemini 非流式 |
| POST | `/v1beta/models/{model}:streamGenerateContent` | 业务 | Gemini 流式 |
| POST | `/v1/models/{model}:generateContent` | 业务 | Gemini 非流式兼容路径 |
| POST | `/v1/models/{model}:streamGenerateContent` | 业务 | Gemini 流式兼容路径 |
| GET | `/api/version` | 无 | Ollama 版本接口 |
| GET | `/api/tags` | 无 | Ollama 模型列表 |
| POST | `/api/show` | 无 | Ollama 单模型能力查询(返回 `id` 与 `capabilities`) |
| POST | `/admin/login` | 无 | 管理登录 |
| GET | `/admin/verify` | JWT | 校验管理 JWT |
| GET | `/admin/vercel/config` | Admin | 读取 Vercel 预配置 |
| GET | `/admin/config` | Admin | 读取配置(脱敏) |
| POST | `/admin/config` | Admin | 更新配置 |
| GET | `/admin/settings` | Admin | 读取运行时设置 |
| PUT | `/admin/settings` | Admin | 更新运行时设置(热更新) |
| POST | `/admin/settings/password` | Admin | 更新 Admin 密码并使旧 JWT 失效 |
| GET | `/admin/skills` | Admin | 读取技能库配置(全局开关、截断长度、条目列表) |
| PUT | `/admin/skills` | Admin | 整体替换技能库配置(校验失败返回 400) |
| POST | `/admin/config/import` | Admin | 导入配置(merge/replace) |
| GET | `/admin/config/export` | Admin | 导出完整配置(含 `config`/`json`/`base64`) |
| POST | `/admin/keys` | Admin | 添加 API key(可附 name/remark) |
| PUT | `/admin/keys/{key}` | Admin | 更新 API key 备注信息 |
| DELETE | `/admin/keys/{key}` | Admin | 删除 API key |
| GET | `/admin/proxies` | Admin | 代理列表 |
| POST | `/admin/proxies` | Admin | 添加代理 |
| PUT | `/admin/proxies/{proxyID}` | Admin | 更新代理(留空 password 表示保留原密码) |
| DELETE | `/admin/proxies/{proxyID}` | Admin | 删除代理(自动解绑引用该代理的账号) |
| POST | `/admin/proxies/test` | Admin | 测试代理连通性 |
| GET | `/admin/accounts` | Admin | 分页账号列表 |
| POST | `/admin/accounts` | Admin | 添加账号 |
| PUT | `/admin/accounts/{identifier}` | Admin | 更新账号 name/remark |
| DELETE | `/admin/accounts/{identifier}` | Admin | 删除账号 |
| PUT | `/admin/accounts/{identifier}/proxy` | Admin | 为账号绑定/解绑代理 |
| GET | `/admin/queue/status` | Admin | 账号队列状态 |
| POST | `/admin/accounts/test` | Admin | 测试单个账号 |
| POST | `/admin/accounts/test-all` | Admin | 测试全部账号 |
| POST | `/admin/accounts/sessions/delete-all` | Admin | 删除某账号的全部会话 |
| POST | `/admin/import` | Admin | 批量导入 keys/accounts |
| POST | `/admin/test` | Admin | 测试当前 API 可用性 |
| POST | `/admin/dev/raw-samples/capture` | Admin | 直接发起一次请求并保存为 raw sample |
| GET | `/admin/dev/raw-samples/query` | Admin | 按问题关键词查询当前内存抓包链 |
| POST | `/admin/dev/raw-samples/save` | Admin | 把命中的内存抓包链保存为 raw sample |
| POST | `/admin/vercel/sync` | Admin | 同步配置到 Vercel |
| GET | `/admin/vercel/status` | Admin | Vercel 同步状态 |
| POST | `/admin/vercel/status` | Admin | Vercel 同步状态 / 草稿对比 |
| GET | `/admin/export` | Admin | 导出配置 JSON/Base64 |
| GET | `/admin/dev/captures` | Admin | 查看本地抓包记录 |
| DELETE | `/admin/dev/captures` | Admin | 清空本地抓包记录 |
| GET | `/admin/chat-history` | Admin | 查看服务器端对话记录(支持按 caller/status/surface/关键词筛选与分页) |
| DELETE | `/admin/chat-history` | Admin | 清空服务器端对话记录 |
| GET | `/admin/chat-history/{id}` | Admin | 查看单条服务器端对话记录 |
| DELETE | `/admin/chat-history/{id}` | Admin | 删除单条服务器端对话记录 |
| PUT | `/admin/chat-history/settings` | Admin | 更新对话记录保留条数(`0`..`500`) |
| GET | `/admin/chat-history/export` | Admin | 按相同筛选条件导出对话记录(`json` / `markdown`) |
| GET | `/admin/version` | Admin | 查询当前版本与最新 Release |
OpenAI `/v1/*` 仍是规范路径。对于只配置 DS2API 根地址的客户端,同一套 OpenAI handler 也通过根路径快捷路由暴露:`/models`、`/models/{id}`、`/chat/completions`、`/responses`、`/responses/{response_id}`、`/embeddings`、`/files`、`/files/{file_id}`。
服务器端记录本质上是 DeepSeek 上游响应归档:OpenAI Chat、OpenAI Responses、Claude Messages、Gemini GenerateContent 等直连 DeepSeek 的生成接口,在收到上游响应后会于各协议回译/裁剪前写入记录;列表按请求创建时间倒序展示,流式请求会在生成过程中持续刷新状态与详情。WebUI「API 测试」发出的请求也会进入该记录。每条记录带 `caller_id`(API Key 调用方)与 `caller_label`(Key 的名称/备注/脱敏值),以及 `surface`(来源接口)、`status`、`status_code` 等可筛选字段;列表、详情、导出共用同一份数据,详见 [对话记录](#get-adminchat-history)。
---
## 健康检查
### `GET /healthz`
```json
{"status": "ok"}
```
### `GET /readyz`
```json
{"status": "ready"}
```
---
## OpenAI 兼容接口
### `GET /v1/models`
无需鉴权。返回当前支持的 DeepSeek 原生模型列表。
**响应示例**:
```json
{
"object": "list",
"data": [
{"id": "deepseek-v4-flash", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []},
{"id": "deepseek-v4-flash-nothinking", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []},
{"id": "deepseek-v4-flash-search", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []},
{"id": "deepseek-v4-flash-search-nothinking", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []}
]
}
```
> 说明:`/v1/models` 返回的是规范化后的 DeepSeek 原生模型 ID;常见 alias 仅用于请求入参解析,不会在该接口中单独展开返回。带 `-nothinking` 后缀的模型表示无论请求里是否显式开启 thinking / reasoning,都会强制关闭思考输出。
>
> 模型系列已收敛到上表 4 个 flash 变体:`deepseek-v4-pro` / `deepseek-v4-pro-search` / `deepseek-v4-vision` 及其 `-nothinking` 形式已下线,不再出现在本接口、Ollama `/api/tags` 与 `/anthropic/v1/models` 中,但旧名字仍可被请求,解析时静默映射到 flash 对应项(见下一节)。
### 模型 alias 解析策略
对 `chat` / `responses` / `embeddings` 的 `model` 字段采用“宽进严出”:
1. 先匹配 DeepSeek 原生模型。
2. 再匹配 `model_aliases` 精确映射。
3. 如果请求名以 `-nothinking` 结尾,则在最终解析出的规范模型上追加对应的无思考变体。
4. 仍未命中则返回 `invalid_request_error`。当前不会按未知模型家族做启发式兜底;需要新增兼容名时请通过 `model_aliases` 明确配置。
当前内置默认 alias 来自 `internal/config/models.go`,`config.model_aliases` 会在运行时覆盖或补充同名映射。节选(全部指向 flash 系列):
- OpenAI / Codex:`gpt-4o`、`gpt-4.1`、`gpt-5`、`gpt-5.5`、`gpt-5-codex`、`gpt-5.3-codex`、`codex-mini-latest` → `deepseek-v4-flash`
- OpenAI reasoning:`o1`、`o3`、`o4-mini` → `deepseek-v4-flash`;`o3-deep-research`、`o4-mini-deep-research` → `deepseek-v4-flash-search`
- Claude:`claude-opus-4-6`、`claude-sonnet-4-6`、`claude-haiku-4-5`、`claude-3-5-sonnet-latest` → `deepseek-v4-flash`
- Gemini:`gemini-2.5-pro`、`gemini-2.5-flash`、`gemini-3.1-pro`、`gemini-3-pro`、`gemini-3-flash`、`gemini-3.1-flash-lite`、`gemini-pro-vision` → `deepseek-v4-flash`
- 其他内置精确 alias:`llama-3.1-70b-instruct`、`qwen-max` → `deepseek-v4-flash`
上述 alias 若在请求名后追加 `-nothinking` 后缀,也会映射到对应的强制关闭 thinking 版本。
已下线的 DeepSeek 模型名不会让老客户端失效,解析时静默归一:
| 请求模型名 | 实际解析结果 |
| --- | --- |
| `deepseek-v4-pro` / `deepseek-v4-vision` | `deepseek-v4-flash` |
| `deepseek-v4-pro-search` | `deepseek-v4-flash-search` |
| `deepseek-v4-pro-nothinking` / `deepseek-v4-vision-nothinking` | `deepseek-v4-flash-nothinking` |
| `deepseek-v4-pro-search-nothinking` | `deepseek-v4-flash-search-nothinking` |
上游 `model_type` 现在只剩 `default`(历史配置里出现的 `expert` / `vision` 按 `default` 处理),因此不存在独立的视觉或 expert 通道。
退役历史模型(如 `claude-1.*`、`claude-2.*`、`claude-instant-*`、`gpt-3.5*`)会被显式拒绝。
### 思考与联网搜索解析策略
同一套规则适用于 `/v1/chat/completions`、`/v1/responses`、`/anthropic/v1/messages` 与 Gemini `generateContent`,实现在 `internal/util/thinking.go`、`internal/util/search.go`,不在各协议适配器里。
**思考(thinking)**,优先级从低到高:
| 顺序 | 来源 | 说明 |
| --- | --- | --- |
| 1 | 模型默认值 | `-nothinking` 变体默认关;其余 flash 模型默认开 |
| 2 | 客户端显式覆盖 | 命中任一字段即生效 |
| 3 | `-nothinking` 模型 | 强制关闭,优先级最高,客户端无法打开 |
客户端覆盖字段(`extra_body` 里的同名键同样会被读取):
| 字段 | 接受写法 |
| --- | --- |
| `thinking` | bool;`"enabled"` / `"disabled"`(含 `on` / `off` / `true` / `false` / `none`);`{"type": ...}` |
| `reasoning` | `{"effort": ...}`、`{"type": ...}`、`{"enabled": ...}`;字符串按 effort 解析 |
| `reasoning_effort` | `minimal` / `low` / `medium` / `high` / `xhigh` / `max` = 开启;`none` / `disabled` / `disable` / `off` / `false` = 关闭 |
Kelivo 这类客户端的「思维强度」界面就是靠 `reasoning_effort` 传开关:不发该字段表示跟随默认(开启思考),传 `none` 表示关闭。本项目只区分开/关,不区分强度档位。
**联网搜索(search)**,优先级为「模型默认 → 客户端显式信号优先」:
| 信号 | 说明 |
| --- | --- |
| `enable_search` | bool,或 `"true"` / `"false"` / `"on"` / `"off"` / `"enabled"` / `"disabled"` 等字符串 |
| `web_search_options` | 字段出现即视为开启 |
| `search_options` | 字段出现即视为开启 |
| `tools` 原生搜索类型 | `type` 以 `web_search` 开头、`type=google_search`,或工具对象含 `googleSearch` / `google_search` / `web_search` 字段 |
`-search` 变体默认开启联网;显式 `enable_search=false` 可在该模型上临时关掉。普通 function 工具(例如 Kelivo 客户端自己执行的 `search_web`)不算联网信号,所以默认仍不开启上游联网——要联网请选 `-search` 模型;一旦客户端开始发送上述原生信号,服务端会自动跟随,无需改配置。
### `POST /v1/chat/completions`
> 路径说明:除规范路径 `/v1/chat/completions` 外,也支持根路径快捷别名 `/chat/completions`。在 Vercel Runtime 上,`vercel.json` 仅把规范路径 `/v1/chat/completions` 重写到 Node 流式桥接;根路径快捷别名仍走 Go 主链路。因此 Vercel 上需要实时流式时请使用 `/v1/chat/completions`。
**请求头**:
```http
Authorization: Bearer your-api-key
Content-Type: application/json
```
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | ✅ | 支持 DeepSeek 原生模型 + 常见 alias(如 `gpt-5.5`、`gpt-5.4-mini`、`gpt-5.3-codex`、`o3`、`claude-opus-4-6`、`claude-sonnet-4-6`、`gemini-2.5-pro`、`gemini-3.1-pro`、`gemini-3-flash` 等);若模型名带 `-nothinking` 后缀,则强制关闭 thinking / reasoning |
| `messages` | array | ✅ | OpenAI 风格消息数组 |
| `stream` | boolean | ❌ | 默认 `false` |
| `tools` | array | ❌ | Function Calling 定义 |
| `thinking` | bool / string / object | ❌ | 显式思考开关,覆盖模型默认;`-nothinking` 模型会忽略 |
| `reasoning` / `reasoning_effort` | object / string | ❌ | 与 `thinking` 等价的另一种写法;`reasoning_effort=none` 关闭思考,`minimal`~`max` 开启 |
| `enable_search` | bool / string | ❌ | 显式联网开关,覆盖模型默认(用于在 `-search` 模型上临时关闭) |
| `temperature` 等 | any | ❌ | 兼容透传字段(最终效果由上游决定) |
> 思考与联网的完整解析优先级见 [思考与联网搜索解析策略](#思考与联网搜索解析策略)。
#### 非流式响应
```json
{
"id": "<chat_session_id>",
"object": "chat.completion",
"created": 1738400000,
"model": "deepseek-v4-flash",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "最终回复",
"reasoning_content": "思考内容(开启 thinking 时)"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30,
"completion_tokens_details": {
"reasoning_tokens": 5
}
}
}
```
#### 流式响应(`stream=true`)
SSE 格式:每段为 `data: <json>\n\n`,结束为 `data: [DONE]`。
```text
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"},"index":0}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"reasoning_content":"..."},"index":0}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"..."},"index":0}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{},"index":0,"finish_reason":"stop"}],"usage":{...}}
data: [DONE]
```
**字段说明**:
- 首个 delta 包含 `role: assistant`
- 开启 thinking 时会输出 `delta.reasoning_content`
- 普通文本输出 `delta.content`
- 最后一段包含 `finish_reason` 和 `usage`
- token 计数优先透传上游 DeepSeek SSE(如 `accumulated_token_usage` / `token_usage`);仅在上游缺失时回退本地估算。失败/中断型结束(例如 `response.failed`)可能不会携带 `usage`
#### Tool Calls
> 若 `tools.enabled=false`(默认),请求里的 `tools` 会被忽略,响应不会包含 `tool_calls`。下面描述的行为只在开启工具调用后生效,开启风险见 [docs/prompt-compatibility.md](docs/prompt-compatibility.md)。
当请求中含 `tools` 时,DS2API 做防泄漏处理:
**非流式**:识别到工具调用时,返回 `message.tool_calls`,设置 `finish_reason=tool_calls`,`message.content=null`。
```json
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_xxx",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"beijing\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
```
**流式**:命中高置信特征后立即输出 `delta.tool_calls`(不等待完整工具参数闭合),并持续发送 arguments 增量;已确认的工具调用片段不会回流到 `delta.content`。
补充说明:
- **非代码块上下文**下,工具负载即使与普通文本混合,也会按特征识别并产出可执行 tool call(前后普通文本仍可透传)。
- 解析器当前把推荐半角管道符 DSML 外壳(`<|DSML|tool_calls>` / `<|DSML|invoke name="...">` / `<|DSML|parameter name="...">`)、DSML wrapper 别名(`<dsml|tool_calls>`、`<|tool_calls>`)、常见 DSML 分隔符漏写形态(如 `<|DSML tool_calls>` / `<|DSML invoke>` / `<|DSML parameter>`)、`DSML` 与工具标签名黏连的常见 typo(如 `<DSMLtool_calls>` / `<DSMLinvoke>` / `<DSMLparameter>`)、控制分隔符漂移(如 `<DSML␂tool_calls>` / 原始 STX `\x02`)、CJK 尖括号、全角感叹号、顿号、PascalCase 本地名、弯引号属性值与属性尾部分隔符漂移(如 `<DSM|parameter name="command"|>...〈/DSM|parameter〉` / `<!DSML!invoke name=“Bash”>` / `<、DSML、tool_calls>` / `<DSmartToolCalls>` / `<DSMLtool_calls※>`)、任意协议前缀壳(如 `<proto💥tool_calls>`)和旧式 canonical XML 工具块(`<tool_calls>` / `<invoke name="...">` / `<parameter name="...">`)作为可执行调用解析;这些非结构性分隔符壳会先归一化回 XML,内部仍以 XML 解析语义为准,CDATA 开头也会容错 `<![CDATA[` / `<、[CDATA[`。旧式 `<tools>`、`<tool_call>`、`<tool_name>`、`<param>`、`<function_call>`、`tool_use`、antml 风格与纯 JSON `tool_calls` 片段默认都会按普通文本处理;完整但 malformed 的 wrapper 同样会作为普通文本释放。
- 解析层不会因为参数值为空而丢弃工具调用;显式空字符串或纯空白参数会按空字符串进入结构化 `tool_calls`。Prompt 会要求模型不要主动输出空参数,缺参/空命令的拒绝应由工具执行侧或客户端 schema 校验负责。
- 当最终可见正文为空但思维链里包含可执行工具调用时,Chat / Responses 会在收尾阶段补发标准 OpenAI `tool_calls` / `function_call` 输出;如果客户端未开启 thinking / reasoning,该思维链只用于检测,不会作为可见正文或 `reasoning_content` 暴露。
- Markdown fenced code block(例如 ```json ... ```)和行内 code span(例如 `` `<tool_calls>...</tool_calls>` ``)中的 `tool_calls` 仅视为示例文本,不会被执行。
---
### `GET /v1/models/{id}`
无需鉴权。入参支持 alias(例如 `gpt-4o`),返回的是映射后的 DeepSeek 模型对象。
### `POST /v1/responses`
OpenAI Responses 风格接口,兼容 `input` 或 `messages`。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | ✅ | 支持原生模型 + alias 自动映射 |
| `input` | string/array/object | ❌ | 与 `messages` 二选一 |
| `messages` | array | ❌ | 与 `input` 二选一 |
| `instructions` | string | ❌ | 自动前置为 system 消息 |
| `stream` | boolean | ❌ | 默认 `false` |
| `tools` | array | ❌ | 与 chat 同样的工具识别与转译策略(含代码块示例豁免) |
| `tool_choice` | string/object | ❌ | 支持 `auto`/`none`/`required` 与强制函数(`{"type":"function","name":"..."}`) |
**非流式响应**:返回标准 `response` 对象,`id` 形如 `resp_xxx`,并写入内存 TTL 存储。
当 `tool_choice=required` 且未产出有效工具调用时,返回 HTTP `422`(`error.code=tool_choice_violation`)。
**流式响应(SSE)**:最小事件序列如下。
```text
event: response.created
data: {"type":"response.created","id":"resp_xxx","status":"in_progress",...}
event: response.output_item.added
data: {"type":"response.output_item.added","response_id":"resp_xxx","item":{"type":"message|function_call",...},...}
event: response.content_part.added
data: {"type":"response.content_part.added","response_id":"resp_xxx","part":{"type":"output_text",...},...}
event: response.output_text.delta
data: {"type":"response.output_text.delta","response_id":"resp_xxx","item_id":"msg_xxx","output_index":0,"content_index":0,"delta":"..."}
event: response.function_call_arguments.delta
data: {"type":"response.function_call_arguments.delta","response_id":"resp_xxx","call_id":"call_xxx","delta":"..."}
event: response.function_call_arguments.done
data: {"type":"response.function_call_arguments.done","response_id":"resp_xxx","call_id":"call_xxx","name":"tool","arguments":"{...}"}
event: response.content_part.done
data: {"type":"response.content_part.done","response_id":"resp_xxx",...}
event: response.output_item.done
data: {"type":"response.output_item.done","response_id":"resp_xxx","item":{"type":"message|function_call",...},...}
event: response.completed
data: {"type":"response.completed","response":{...}}
data: [DONE]
```
流式场景下若 `tool_choice=required` 违规,会返回 `response.failed` 后结束(不再发送 `response.completed`)。
> 当前版本说明:解析层默认“尽量提取结构化 tool call”,未启用基于 `tools` allow-list 的硬拒绝;是否执行仍应由你的工具执行器做白名单校验。
### `GET /v1/responses/{response_id}`
需要业务鉴权。查询 `POST /v1/responses` 生成并缓存的 response 对象(按调用方鉴权隔离,仅同一 key/token 可读取)。
> 当前为内存 TTL 存储,默认过期时间 `900s`(可用 `responses.store_ttl_seconds` 调整)。
### `POST /v1/embeddings`
需要业务鉴权。返回 OpenAI Embeddings 兼容结构。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | ✅ | 支持原生模型 + alias 自动映射 |
| `input` | string/array | ✅ | 支持字符串、字符串数组、token 数组 |
> 需配置 `embeddings.provider`。当前支持:`mock` / `deterministic` / `builtin`(三者都走同一套本地确定性实现)。未配置或不支持时返回标准错误结构(HTTP 501)。
### `POST /v1/files`
需要业务鉴权。兼容 OpenAI Files 上传接口,当前仅支持 `multipart/form-data`。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `file` | file | ✅ | 上传文件二进制 |
| `purpose` | string | ❌ | 透传到上游用途字段 |
约束与行为:
- 请求必须为 `multipart/form-data`,否则返回 `400`。
- 请求体总大小上限 **100 MiB**(超限返回 `413`)。
- 成功返回 OpenAI `file` 对象(`id/object/bytes/filename/purpose/status` 等字段),并附带 `account_id` 便于定位来源账号。
### `GET /v1/files/{file_id}`
需要业务鉴权。查询 DeepSeek 上传文件的当前状态,并返回 OpenAI `file` 对象;未找到匹配文件时返回 `404`。
---
## Claude 兼容接口
除标准路径 `/anthropic/v1/*` 外,还支持快捷路径 `/v1/messages`、`/messages`、`/v1/messages/count_tokens`、`/messages/count_tokens`。
实现上统一走 OpenAI Chat Completions 解析与回译链路,避免多套解析逻辑分叉维护。
### `GET /anthropic/v1/models`
无需鉴权。
**响应示例**:
```json
{
"object": "list",
"data": [
{"id": "claude-sonnet-4-6", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-sonnet-4-6-nothinking", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-haiku-4-5", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-haiku-4-5-nothinking", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-opus-4-6", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-opus-4-6-nothinking", "object": "model", "created": 1715635200, "owned_by": "anthropic"}
],
"first_id": "claude-opus-4-6",
"last_id": "claude-3-haiku-20240307-nothinking",
"has_more": false
}
```
> 说明:示例仅展示部分模型;实际返回除当前主别名外,还包含 Claude 4.x snapshots、3.x 历史模型 ID 与常见别名,并为这些可映射模型额外提供 `-nothinking` 变体。
### `POST /anthropic/v1/messages`
**请求头**:
```http
x-api-key: your-api-key
Content-Type: application/json
anthropic-version: 2023-06-01
```
> `anthropic-version` 可省略,服务端会自动补为 `2023-06-01`。
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | ✅ | 例如 `claude-sonnet-4-6` / `claude-opus-4-6` / `claude-haiku-4-5`(兼容 `claude-sonnet-4-5`、`claude-3-5-haiku-latest`),并支持历史 Claude 模型 ID;若模型名带 `-nothinking` 后缀,则强制关闭 thinking / reasoning |
| `messages` | array | ✅ | Claude 风格消息数组 |
| `max_tokens` | number | ❌ | 缺省自动补 `8192`;当前实现不会硬性截断上游输出 |
| `stream` | boolean | ❌ | 默认 `false` |
| `system` | string | ❌ | 可选系统提示 |
| `tools` | array | ❌ | Claude tool 定义 |
| `thinking` | object | ❌ | Anthropic thinking 配置;会转译为下游 reasoning 控制,`-nothinking` 模型会忽略 |
| `temperature` | number | ❌ | 透传到下游;若同时提供 `top_p`,以 `temperature` 为准 |
| `top_p` | number | ❌ | 当未提供 `temperature` 时透传到下游 |
| `stop_sequences` | array | ❌ | 透传到下游停用序列 |
| `tool_choice` | string/object | ❌ | 支持 `auto` / `none` / `required` / `{"type":"function","name":"..."}`,并会转译为下游工具选择 |
> 说明:上述 `thinking`、`temperature`、`top_p`、`stop_sequences`、`tool_choice` 都会走兼容层转译;最终是否生效仍取决于当前模型和上游能力。`temperature` 与 `top_p` 同时存在时,`temperature` 优先。思考与联网的解析优先级与 OpenAI 接口完全一致,见 [思考与联网搜索解析策略](#思考与联网搜索解析策略)。若 `tools.enabled=false`(默认),请求里的 `tools` 会被忽略,响应不会包含 `tool_calls`(Claude 侧即 `tool_use` 块)。
#### 非流式响应
```json
{
"id": "msg_1738400000000000000",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-6",
"content": [
{"type": "text", "text": "回复内容"}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 34
}
}
```
若识别到工具调用,`stop_reason=tool_use`,`content` 中返回 `tool_use` block。
#### 流式响应(`stream=true`)
SSE 使用 `event:` + `data:` 双行格式,JSON 中保留 `type` 字段。
```text
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}}
event: ping
data: {"type":"ping"}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":12}}
event: message_stop
data: {"type":"message_stop"}
```
**说明**:
- 默认支持 thinking 的模型会输出 `thinking` block / `thinking_delta`;请求显式关闭 thinking 或使用 `-nothinking` 模型时不会输出
- 带 `-nothinking` 后缀的模型会强制关闭 thinking,即使请求显式传了 `thinking` / `reasoning` / `reasoning_effort` 也不会输出 `thinking_delta`
- 不会输出 `signature_delta`(上游 DeepSeek 未提供可验证签名)
- `tools` 场景优先避免泄露原始工具 JSON,不强制发送 `input_json_delta`
### `POST /anthropic/v1/messages/count_tokens`
**请求**:
```json
{
"model": "claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "你好"}
]
}
```
**响应**:
```json
{
"input_tokens": 5
}
```
---
## Gemini 兼容接口
支持路径:
- `/v1beta/models/{model}:generateContent`
- `/v1beta/models/{model}:streamGenerateContent`
- `/v1/models/{model}:generateContent`(兼容路径)
- `/v1/models/{model}:streamGenerateContent`(兼容路径)
鉴权方式同业务接口(`Authorization: Bearer <token>` 或 `x-api-key`)。
实现上统一走 OpenAI Chat Completions 解析与回译链路,避免多套解析逻辑分叉维护。
### `POST /v1beta/models/{model}:generateContent`
请求体兼容 Gemini `contents` / `tools` 字段,模型名可用 alias 自动映射到 DeepSeek 模型;若路径中的模型名带 `-nothinking` 后缀,则最终会映射到对应的无思考模型。思考与联网的解析优先级与 OpenAI 接口一致,见 [思考与联网搜索解析策略](#思考与联网搜索解析策略)。若 `tools.enabled=false`(默认),请求里的 `tools` 会被忽略,响应不会包含 `tool_calls`(Gemini 侧即 `functionCall`)。
响应为 Gemini 兼容结构,核心字段包括:
- `candidates[].content.parts[].text`
- `candidates[].content.parts[].thought=true`(thinking 输出)
- `candidates[].content.parts[].functionCall`(工具调用时)
- `usageMetadata`(`promptTokenCount` / `candidatesTokenCount` / `totalTokenCount`)
### `POST /v1beta/models/{model}:streamGenerateContent`
返回 SSE(`text/event-stream`),每个 chunk 为一条 `data: <json>`:
- 常规文本:持续返回增量文本 chunk
- thinking:持续返回 `parts[].thought=true` 的增量 chunk
- `tools` 场景:会缓冲并在结束时输出 `functionCall` 结构
- 结束 chunk:包含 `finishReason: "STOP"` 与 `usageMetadata`
- token 计数优先透传上游 DeepSeek SSE(如 `accumulated_token_usage` / `token_usage`);仅在上游缺失时回退本地估算
---
## Ollama 兼容接口
- `POST /api/show` 请求体:`{"model":"<model-id>"}`。
- `GET /api/tags` 只列出 flash 系列的 4 个模型 ID(`deepseek-v4-flash`、`deepseek-v4-flash-search` 及各自 `-nothinking` 变体),与 `/v1/models` 保持一致;已下线的 pro / vision 名字不再出现,但仍可作为 `model` 入参被请求。
- 响应字段使用小写 `id`(不是 `ID`),并返回 `capabilities` 数组,便于与 Ollama 风格客户端/严格 schema 对齐。
示例响应:
```json
{
"id": "deepseek-v4-flash",
"capabilities": ["tools", "thinking"]
}
```
## Admin 接口
### `POST /admin/login`
无需鉴权。
**请求**:
```json
{
"admin_key": "admin",
"expire_hours": 24
}
```
`expire_hours` 可省略,默认 `24`。
**响应**:
```json
{
"success": true,
"token": "<jwt>",
"expires_in": 86400
}
```
### `GET /admin/verify`
需要 JWT:`Authorization: Bearer <jwt>`
**响应**:
```json
{
"valid": true,
"expires_at": 1738400000,
"remaining_seconds": 72000
}
```
### `GET /admin/vercel/config`
返回 Vercel 预配置状态。优先读取环境变量,其次回退到已保存的 `vercel` 配置块。
```json
{
"has_token": true,
"token_preview": "vc****en",
"token_source": "config",
"project_id": "prj_xxx",
"team_id": null
}
```
### `GET /admin/config`
返回脱敏后的配置,包含 `keys` 与 `api_keys`。
```json
{
"keys": ["k1", "k2"],
"api_keys": [
{"key": "k1", "name": "主 Key", "remark": "生产流量"},
{"key": "k2", "name": "备用 Key", "remark": "压测"}
],
"env_backed": false,
"env_source_present": true,
"env_writeback_enabled": true,
"config_path": "/data/config.json",
"vercel": {
"has_token": true,
"token_preview": "vc****en",
"project_id": "prj_xxx",
"team_id": ""
},
"accounts": [
{
"identifier": "user@example.com",
"email": "user@example.com",
"mobile": "",
"has_password": true,
"has_token": true,
"token_preview": "abcde..."
}
],
"model_aliases": {
"claude-sonnet-4-6": "deepseek-v4-flash",
"claude-opus-4-6": "deepseek-v4-flash"
}
}
```
### `POST /admin/config`
只更新 `keys`、`api_keys`、`accounts`、`model_aliases`。
如果同时发送 `api_keys` 与 `keys`,优先保留 `api_keys` 中的结构化 `name` / `remark`;`keys` 仅作为旧格式兼容回退。
**请求**:
```json
{
"keys": ["k1", "k2"],
"api_keys": [
{"key": "k1", "name": "主 Key", "remark": "生产流量"},
{"key": "k2", "name": "备用 Key", "remark": "压测"}
],
"accounts": [
{"email": "user@example.com", "password": "pwd", "token": ""}
],
"model_aliases": {
"claude-sonnet-4-6": "deepseek-v4-flash",
"claude-opus-4-6": "deepseek-v4-flash"
}
}
```
### `api_keys[].thinking_injection`(单 Key 覆盖思考格式注入)
`api_keys` 的每一项都可以带 `thinking_injection`,用于给单个 API Key 定制注入提示词;`keys` 旧格式不支持该字段。
```json
{
"api_keys": [
{
"key": "k1",
"name": "主 Key",
"remark": "生产流量",
"thinking_injection": {
"custom": true,
"enabled": true,
"prompt": "回答前先列清楚已知条件,再给结论。"
}
},
{"key": "k2", "name": "备用 Key"}
]
}
```
| 字段 | 说明 |
| --- | --- |
| `custom` | 默认 `false`。为 `false`(或整段省略)时完全跟随全局 `thinking_injection` |
| `enabled` | 仅在 `custom=true` 时生效;省略表示继续跟随全局开关 |
| `prompt` | 仅在 `custom=true` 时生效;为空或全空白表示继续跟随全局提示词 |
全局开关与默认提示词见 [`GET /admin/settings`](#get-adminsettings) 的 `thinking_injection`;注入位置与默认提示词内容见 [docs/prompt-compatibility.md](docs/prompt-compatibility.md)。
### `api_keys[].prompt_profile`(单 Key 覆盖提示词特化层)
`api_keys` 的每一项都可以带 `prompt_profile`,用于给单个 API Key 指定特化层预设或正文,与 `thinking_injection`、`skills` 并列;`keys` 旧格式不支持该字段。该字段目前只在配置文件/导入导出里生效,后台面板只提供全局段(见下)。
```json
{
"api_keys": [
{
"key": "k1",
"name": "写作 Key",
"prompt_profile": {"custom": true, "enabled": true, "name": "writing"}
},
{"key": "k2", "name": "默认 Key"}
]
}
```
| 字段 | 说明 |
| --- | --- |
| `custom` | 默认 `false`;为 `false` 或整段省略时完全跟随全局 `prompt_profile` |
| `enabled` | 仅在 `custom=true` 时生效;为 `false` 表示该 Key 不注入特化层 |
| `name` | 仅在 `custom=true` 时生效;`default` / `writing`,非空时覆盖全局预设 |
| `prompt` | 仅在 `custom=true` 时生效;非空时覆盖预设正文(上限 20000 字符) |
全局 `prompt_profile.enabled=false` 时上表全部不生效,任何 Key 都不注入。
### `prompt_style`(内置提示词风格,全局)
顶层字符串字段,只影响内置文案,不影响你自己配置的提示词:
```json
{"prompt_style": "legacy"}
```
| 值 | 行为 |
| --- | --- |
| `v2`(缺省,也接受空串) | 历史拆分后的衔接指令带「执行要求」(上文要求逐条满足)并注入内置执行契约(思考注入) |
| `legacy` | 衔接指令换回 ce5a019 时代的短文案,且**不注入内置执行契约**;显式配置过 `thinking_injection.prompt` 时仍然注入自定义内容 |
未知值不会让配置加载失败:按 `v2` 处理并打一条告警。后台 `PUT /admin/settings` 传未知值直接返回 400。
### `model_profiles`(自定义模型列表,全局)
给同一个 flash 基模挂出多个对外模型名,每个名字带自己的提示词。客户端请求里写哪个模型名,就用哪一份提示词,互不影响。
```json
{
"model_profiles": [
{
"id": "deepseek-writer",
"base": "deepseek-v4-flash",
"name": "写作",
"remark": "长文与改稿",
"prompt": "## 写作要求\n…"
}
]
}
```
| 字段 | 说明 |
| --- | --- |
| `id` | 必填,对外模型名。字符集 `[A-Za-z0-9._-]`,长度 1-64,不可与内置模型名(如 `deepseek-v4-flash`)冲突,条目间不可重复(大小写不敏感) |
| `base` | 必填,基模,只能是 `deepseek-v4-flash` / `-search` / `-nothinking` / `-search-nothinking` |
| `name` / `remark` | 可选,仅用于后台与导出可读性 |
| `prompt` | 可选,该模型专属提示词,上限 20000 字符;为空表示不额外注入 |
解析与注入语义:
- 模型名解析优先于内置别名映射:`id` 命中自定义模型时按它的 `base` 调用上游(`/v1/models`、`/v1/models/{id}`、`/v1/chat/completions`、Claude / Gemini 入口共用同一套解析)。
- `/v1/models` 会把自定义模型一起列出来,`owned_by` 为 `ds2api`。
- 提示词注入点与 `prompt_profile` 相同(历史拆分之后、技能层之前),两者**互斥**:该模型配了非空 `prompt` 就用它,否则回落到全局 `prompt_profile`。
- 数量上限 50 条。接口见 [`GET` / `PUT /admin/model_profiles`](#get--put-adminmodel_profiles)。
### `api_keys[].skills`(单 Key 覆盖技能注入)
`api_keys` 的每一项都可以带 `skills`,用来指定这个 API Key 注入哪些技能,与 `thinking_injection` 并列;`keys` 旧格式不支持该字段。
```json
{
"api_keys": [
{"key": "k1", "name": "主 Key", "skills": {"custom": true, "enabled": true, "ids": ["sql-report"]}},
{"key": "k2", "name": "备用 Key"}
]
}
```
| 字段 | 说明 |
| --- | --- |
| `custom` | 默认 `false`;为 `false` 或整段省略时使用全部 `skills.items` |
| `enabled` | 仅在 `custom=true` 时生效;为 `false` 表示该 Key 不注入 |
| `ids` | 仅在 `custom=true` 时生效;非空时只用命中的技能,为空时该 Key 不注入 |
| `custom` | `enabled` | `ids` | 结果 |
| --- | --- | --- | --- |
| 省略或 `false` | 任意 | 任意 | 使用全部 `items` |
| `true` | `false` | 任意 | 不注入 |
| `true` | 省略或 `true` | 非空 | 只用命中的 `items` |
| `true` | 省略或 `true` | 空 | 不注入 |
全局 `skills.enabled=false` 时上表全部不生效,任何 Key 都不注入。技能正文与管理接口见 [`GET` / `PUT /admin/skills`](#get--put-adminskills)。
### `GET /admin/settings`
读取运行时设置与状态,返回:
- `success`
- `admin`(`has_password_hash`、`jwt_expire_hours`、`jwt_valid_after_unix`、`default_password_warning`)
- `runtime`(`account_max_inflight`、`account_max_queue`、`global_max_inflight`、`token_refresh_interval_hours`)
- `runtime.token_refresh_interval_hours` 的语义是「会话体检间隔」:到期只做一次无密码复核(`GET /api/v0/users/current`),只有服务端明确拒绝会话时才回退到密码登录
- `config_snapshot`:原始配置快照(`admin` / `runtime` / `responses` / `embeddings` / `auto_delete` / `current_input_file` / `thinking_injection` / `prompt_profile` / `prompt_style` / `model_profiles` / `model_aliases`),用于区分环境变量覆盖前的配置
- `responses` / `embeddings`
- `auto_delete`(`mode`:`none` / `single` / `all`;旧配置 `sessions=true` 仍按 `all` 处理)
- `current_input_file`(`enabled` 默认返回 `true`、`min_chars`)
- `thinking_injection`(`enabled` 缺省为 `true`、`prompt`、`default_prompt`);`prompt` 为空时使用内置默认提示词,单 Key 可用 `api_keys[].thinking_injection` 覆盖
- `prompt_profile`(`enabled` 缺省 `false`、`enabled_default` 固定 `false`、`name`、`prompt`、`presets`);`presets` 是 `[{name, body}]` 的内置预设清单,`body` 为该预设正文,供面板在输入框里显示「留空时使用的默认正文」
- `prompt_style`(`mode`:生效风格 `v2` / `legacy`;`modes`:可选值;`default`:`v2`;`value_raw`:配置里原样存的值)
- `model_profiles`(`count`、`max_count`、`base_models`、`max_id_chars`、`max_prompt_chars`);条目本身用 `/admin/model_profiles` 读写
- `model_aliases`
- `tools`(`enabled`:工具调用总开关,缺省 `false`;`default_enabled`:固定 `false`,供面板显示默认值)
- `env_backed`、`needs_vercel_sync`
- `toolcall` 策略已固定为 `feature_match + high`,不再通过 settings 返回或修改
### `PUT /admin/settings`
热更新运行时设置。支持更新:
- `admin.jwt_expire_hours`
- `runtime.account_max_inflight` / `runtime.account_max_queue` / `runtime.global_max_inflight` / `runtime.token_refresh_interval_hours`
- `responses.store_ttl_seconds`
- `embeddings.provider`
- `auto_delete.mode`
- `current_input_file.enabled` / `current_input_file.min_chars`
- `thinking_injection.enabled` / `thinking_injection.prompt`
- `prompt_profile.enabled` / `prompt_profile.name` / `prompt_profile.prompt`
- `prompt_style`
- `tools.enabled`
- `model_aliases`
- `toolcall` 策略已固定,不再作为可写入字段
`prompt_profile` 是本次新增的「提示词特化层」,缺省关闭;`name` 只接受 `default` / `writing`(其他值返回 400),`prompt` 上限 20000 字符(超限返回 400),只传部分字段时其余字段保持不变:
```json
{"prompt_profile": {"enabled": true, "name": "writing"}}
```
注入位置、拼接顺序与预设正文见 [docs/prompt-compatibility.md](docs/prompt-compatibility.md) 与 [docs/prompt-design.md](docs/prompt-design.md)。
`prompt_style` 是顶层字符串(不是对象),只接受 `v2` / `legacy` / 空串(空串等于回到 v2):
```json
{"prompt_style": "legacy"}
```
自定义模型列表不走 settings,用下面的 `/admin/model_profiles` 整体替换。
`tools.enabled` 只改请求体里传了的字段,其它设置的现有语义不变:
```json
{"tools": {"enabled": true}}
```
关闭(默认)时带 `tools` 的请求按纯对话处理;DSML 工具调用可能触发上游风控导致账号封禁,是否开启请自行评估,风险说明见 [docs/prompt-compatibility.md](docs/prompt-compatibility.md)。
### `POST /admin/settings/password`
更新管理密码并使旧 JWT 失效。
请求示例:
```json
{"new_password":"your-new-password"}
```
也兼容 `{"password":"your-new-password"}`。
### `GET` / `PUT /admin/skills`
纯文本技能库,实验性功能,缺省关闭(`skills.enabled=false`)。技能正文由服务端拼进 prompt,模型不需要工具调用就能按固定套路工作。
- 全局配置:`skills.enabled`(缺省 `false`)、`skills.max_chars`(缺省 `8000`)、`skills.items`
- 单 Key 覆盖:`api_keys[].skills`,语义见上文「api_keys[].skills(单 Key 覆盖技能注入)」
- 注入形态、注入顺序与「为什么不用工具调用」:[docs/prompt-compatibility.md](docs/prompt-compatibility.md) 的「技能注入(skills)」
**GET 响应**:
```json
{
"enabled": false,
"max_chars": 8000,
"items": [
{
"id": "sql-report",
"name": "SQL 报表",
"description": "按固定口径写 SQL",
"body": "# 技能正文 markdown\n..."
}
]
}
```
**PUT 请求**:与 GET 同形状,整体替换,不做增量合并。
```json
{
"enabled": true,
"max_chars": 8000,
"items": [
{
"id": "sql-report",
"name": "SQL 报表",
"description": "按固定口径写 SQL",
"body": "# 报表口径\n先按天聚合,再按渠道拆分。"
}
]
}
```
**PUT 成功响应**:
```json
{"success": true, "count": 1}
```
**校验规则**(任一不满足返回 `400`,响应体为 `{"detail": "..."}`):
| 字段 | 规则 |
| --- | --- |
| `items` | 最多 50 条 |
| `id` | 必填、唯一,字符集 `[a-z0-9_-]` |
| `body` | 必填;单条 ≤ 100000 字符,全部条目合计 ≤ 500000 字符 |
| `max_chars` | 缺省 `8000`;按字符(rune)截断,截断时追加「(技能内容已截断)」;`0` 表示不限制 |
校验在写入时执行,与 `enabled` 无关:先关着开关把内容配好、改好,再打开即可。超限、重复 `id`、非法 `id` 都不会被静默修正。
### `GET` / `PUT /admin/model_profiles`
自定义模型列表(模型名 + 基模 + 该模型专属提示词)。与技能库同样是独立接口、整体替换语义:PUT 请求里的 `items` 就是最终列表,传空数组表示清空。
**GET 响应**:
```json
{
"items": [
{"id": "deepseek-writer", "base": "deepseek-v4-flash", "name": "写作", "remark": "长文", "prompt": "## 写作要求\n…"}
],
"base_models": ["deepseek-v4-flash", "deepseek-v4-flash-search", "deepseek-v4-flash-nothinking", "deepseek-v4-flash-search-nothinking"],
"max_count": 50,
"max_id_chars": 64,
"max_prompt_chars": 20000
}
```
**PUT 请求**:
```json
{"items": [{"id": "deepseek-writer", "base": "deepseek-v4-flash", "prompt": "…"}]}
```
**校验规则**(任一条不满足返回 `400`,响应体为 `{"detail": "..."}`):
| 字段 | 规则 |
| --- | --- |
| `items` | 最多 50 条 |
| `id` | 必填、唯一(大小写不敏感),字符集 `[A-Za-z0-9._-]`,长度 1-64,不可与内置模型名冲突 |
| `base` | 必填,只能是 `deepseek-v4-flash` 系列的四个名字之一 |
| `prompt` | 可空;≤ 20000 字符 |
保存后立即生效:`/v1/models` 列表与模型名解析都会包含新条目,无需重启。
### `POST /admin/config/import`
导入完整配置,支持:
- `mode=merge`(默认)
- `mode=replace`
请求可直接传配置对象,或使用 `{"config": {...}, "mode":"merge"}` 包裹格式。
也支持在查询参数里传 `?mode=merge` / `?mode=replace`。
`replace` 模式会按完整配置结构替换(保留 Vercel 同步元信息);`merge` 模式会合并 `keys`、`api_keys`、`accounts`、`model_aliases`,并覆盖 `admin`、`runtime`、`responses`、`embeddings` 中的非空字段。`auto_delete`、`current_input_file` 建议通过 `/admin/settings` 或配置文件管理;`compat` 与 `toolcall` 相关字段会被忽略。
> 注意:`merge` 模式不会更新 `auto_delete`、`current_input_file`。
### `GET /admin/config/export`
导出完整配置,返回 `config`、`json`、`base64` 三种格式。
响应示例:
> 注:`_vercel_sync_hash` 和 `_vercel_sync_time` 为内部同步元数据字段,用于 Vercel 配置漂移检测。
### `POST /admin/keys`
```json
{"key": "new-api-key", "name": "主 Key", "remark": "生产流量"}
```
**响应**:`{"success": true, "total_keys": 3}`
### `PUT /admin/keys/{key}`
更新指定 API key 的 `name` / `remark`,路径参数中的 `key` 为只读标识,不可修改。
```json
{"name": "备用 Key", "remark": "压测"}
```
**响应**:`{"success": true, "total_keys": 3}`
### `DELETE /admin/keys/{key}`
**响应**:`{"success": true, "total_keys": 2}`
### `GET /admin/proxies`
列出代理配置(密码不回传,仅返回 `has_password` 标记)。
### `POST /admin/proxies`
新增代理。请求体支持 `id`(可选,未传则自动生成)、`name`、`type`(`http` / `socks5`)、`host`、`port`、`username`、`password`。
### `PUT /admin/proxies/{proxyID}`
更新指定代理。若请求中 `password` 为空字符串,则保留原密码。
### `DELETE /admin/proxies/{proxyID}`
删除代理,并自动清空所有引用该代理账号的 `proxy_id`。
### `POST /admin/proxies/test`
测试代理连通性:传 `proxy_id` 时测试已保存代理;不传时按请求体代理字段做临时连通性测试。
### `GET /admin/accounts`
**查询参数**:
| 参数 | 默认 | 范围 |
| --- | --- | --- |
| `page` | `1` | ≥ 1 |
| `page_size` | `10` | 1–5000 |
| `q` | 空 | 按 identifier / email / mobile 过滤 |
**响应**:
```json
{
"items": [
{
"identifier": "user@example.com",
"email": "user@example.com",
"mobile": "",
"has_password": true,
"has_token": true,
"token_preview": "abc...",
"test_status": "ok"
}
],
"total": 25,
"page": 1,
"page_size": 10,
"total_pages": 3
}
```
### `POST /admin/accounts`
```json
{"email": "user@example.com", "password": "pwd"}
```
**响应**:`{"success": true, "total_accounts": 6}`
### `PUT /admin/accounts/{identifier}`
更新指定账号的 `name` / `remark`。路径参数中的 `identifier` 可以是 email 或 mobile,且不可修改。
```json
{"name": "主账号", "remark": "团队共享"}
```
**响应**:`{"success": true, "total_accounts": 6}`
### `DELETE /admin/accounts/{identifier}`
`identifier` 可为 email、mobile,或 token-only 账号的合成标识(`token:<hash>`)。
**响应**:`{"success": true, "total_accounts": 5}`
### `PUT /admin/accounts/{identifier}/proxy`
更新指定账号绑定代理。
- 请求体:`{"proxy_id":"..."}`;
- `proxy_id` 传空字符串时表示解绑代理;
- `identifier` 支持 email / mobile / token-only 合成标识。
### `GET /admin/queue/status`
```json
{
"available": 3,
"in_use": 1,
"total": 4,
"available_accounts": ["a@example.com"],
"in_use_accounts": ["b@example.com"],
"max_inflight_per_account": 2,
"global_max_inflight": 8,
"recommended_concurrency": 8,
"waiting": 0,
"max_queue_size": 8
}
```
| 字段 | 说明 |
| --- | --- |
| `available` | 仍有剩余并发槽位的账号数 |
| `in_use` | 当前已占用的 in-flight 槽位数 |
| `total` | 总账号数 |
| `available_accounts` | 仍有剩余并发槽位的账号 ID 列表 |
| `in_use_accounts` | 当前处于使用中的账号 ID 列表 |
| `max_inflight_per_account` | 每账号并发上限 |
| `global_max_inflight` | 全局并发上限 |
| `recommended_concurrency` | 建议并发值(`total × max_inflight_per_account`) |
| `waiting` | 当前等待中的请求数 |
| `max_queue_size` | 等待队列上限 |
### `POST /admin/accounts/test`
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `identifier` | ✅ | email / mobile / token-only 合成标识 |
| `model` | ❌ | 默认 `deepseek-v4-flash` |
| `message` | ❌ | 空字符串时仅测试会话创建 |
**响应**:
```json
{
"account": "user@example.com",
"success": true,
"response_time": 1240,
"message": "API 测试成功(仅会话创建)",
"model": "deepseek-v4-flash",
"session_count": 0,
"config_writable": true,
"config_warning": ""
}
```
如果传入 `message`,还会附带 `thinking`(当上游返回思考内容时)。
当部署环境配置文件路径不可写(例如容器内默认 `/app/config.json` 只读)时,登录与会话测试仍可继续;此时会返回 `config_warning` 提示 token 仅保存在内存、重启后丢失。
### `POST /admin/accounts/test-all`
可选请求字段:`model`
```json
{
"total": 5,
"success": 4,
"failed": 1,
"results": [...]
}
```
内部并发上限当前固定为 5。
### `POST /admin/accounts/sessions/delete-all`
清空指定账号的所有 DeepSeek 会话。请求体示例:
```json
{"identifier":"user@example.com"}
```
响应:
```json
{"success": true, "message": "删除成功"}
```
如果账号不存在或删除失败,`success` 会是 `false`,`message` 会返回错误原因。
### `POST /admin/import`
批量导入 keys 与 accounts。
**请求**:
```json
{
"keys": ["k1", "k2"],
"accounts": [
{"email": "user@example.com", "password": "pwd", "token": ""}
]
}
```
**响应**:
```json
{
"success": true,
"imported_keys": 2,
"imported_accounts": 1
}
```
### `POST /admin/test`
测试当前 API 可用性(通过自身接口调用)。
| 字段 | 必填 | 默认值 |
| --- | --- | --- |
| `model` | ❌ | `deepseek-v4-flash` |
| `message` | ❌ | `你好` |
| `api_key` | ❌ | 配置中第一个 key |
**响应**:
```json
{
"success": true,
"status_code": 200,
"response": {"id": "..."}
}
```
### `POST /admin/dev/raw-samples/capture`
直接通过服务自身发起一次 `/v1/chat/completions` 请求,并把请求元信息和上游原始 SSE 保存到 `tests/raw_stream_samples/<sample-id>/`。
常用请求字段:
| 字段 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `message` | 否 | `你好` | 便捷单轮用户消息 |
| `messages` | 否 | 自动由 `message` 生成 | OpenAI 风格消息数组 |
| `model` | 否 | `deepseek-v4-flash` | 目标模型 |
| `stream` | 否 | `true` | 建议保留流式,以记录原始 SSE |
| `api_key` | 否 | 配置中第一个 key | 调用业务接口使用的 key |
| `sample_id` | 否 | 自动生成 | 样本目录名 |
成功时会在响应头里附带:
- `X-Ds2-Sample-Id`
- `X-Ds2-Sample-Dir`
- `X-Ds2-Sample-Meta`
- `X-Ds2-Sample-Upstream`
如果请求本身成功,但当前进程没有记录到新的上游抓包,会返回:
```json
{"detail":"no upstream capture was recorded"}
```
### `GET /admin/dev/raw-samples/query`
按关键词查询当前进程内存里的抓包记录,并按 `chat_session_id` 归并 `completion + continue` 链。
**查询参数**:
| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `q` | 空 | 按请求体/响应体关键词模糊匹配 |
| `limit` | `20` | 返回链条数上限 |
**响应字段**包含:
- `items[].chain_key`
- `items[].capture_ids`
- `items[].round_count`
- `items[].initial_label`
- `items[].request_preview`
- `items[].response_preview`
### `POST /admin/dev/raw-samples/save`
把当前内存中的某条抓包链落盘为 `tests/raw_stream_samples/<sample-id>/`。
支持以下任一种选中方式:
```json
{"chain_key":"session:xxxx","sample_id":"tmp-from-memory"}
```
```json
{"capture_id":"cap_xxx","sample_id":"tmp-from-memory"}
```
```json
{"query":"广州天气","sample_id":"tmp-from-memory"}
```
成功响应会返回 `sample_id`、`dir`、`meta_path`、`upstream_path`。
### `POST /admin/vercel/sync`
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `vercel_token` | ❌ | 空或 `__USE_PRECONFIG__` 则读环境变量,再回退到已保存配置 |
| `project_id` | ❌ | 空则读 `VERCEL_PROJECT_ID`,再回退到已保存配置 |
| `team_id` | ❌ | 空则读 `VERCEL_TEAM_ID`,再回退到已保存配置 |
| `auto_validate` | ❌ | 默认 `true` |
| `save_credentials` | ❌ | 默认 `true`;保存本次显式填写的 Vercel 凭据,供下次同步复用 |
| `config_override` | ❌ | 传入完整配置对象,覆盖当前内存配置用于同步 |
**成功响应**:
```json
{
"success": true,
"validated_accounts": 3,
"message": "配置已同步,正在重新部署...",
"deployment_url": "https://..."
}
```
或需要手动部署:
```json
{
"success": true,
"validated_accounts": 3,
"message": "配置已同步到 Vercel,请手动触发重新部署",
"manual_deploy_required": true
}
```
失败校验的账号会通过 `failed_accounts` 返回;成功保存到 Vercel 的凭据会通过 `saved_credentials` 返回。
### `GET /admin/vercel/status`
也支持 `POST /admin/vercel/status`,可提交 `config_override` 用于比对草稿是否与已同步配置一致。
```json
{
"synced": true,
"last_sync_time": 1738400000,
"has_synced_before": true,
"env_backed": false,
"config_hash": "....",
"last_synced_hash": "....",
"draft_hash": "....",
"draft_differs": false
}
```
`POST /admin/vercel/status` 还可以携带 `config_override`,用于对比“草稿配置”和当前已同步配置。
### `GET /admin/export`
```json
{
"json": "{...}",
"base64": "ey4uLn0="
}
```
该接口与 `GET /admin/config/export` 返回相同内容,只是路径更短。
### `GET /admin/version`
查询当前构建版本与 GitHub 最新 Release:
```json
{
"success": true,
"current_version": "3.0.0",
"current_tag": "v3.0.0",
"source": "file:VERSION",
"checked_at": "2026-03-29T00:00:00Z",
"latest_tag": "v3.0.0",
"latest_version": "3.0.0",
"release_url": "https://github.com/CJackHwang/ds2api/releases/tag/v3.0.0",
"published_at": "2026-03-28T12:00:00Z",
"has_update": false
}
```
如果 GitHub API 不可用,响应里会额外包含 `check_error`,但 HTTP 状态仍为 200。
### `GET /admin/dev/captures`
查看本地抓包状态与最近记录(需 Admin 鉴权):
- `enabled`
- `limit`
- `max_body_bytes`
- `items`
### `DELETE /admin/dev/captures`
清空抓包记录,返回:
```json
{"success":true,"detail":"capture logs cleared"}
```
### `GET /admin/chat-history`
查询服务器端对话记录,支持筛选与分页。
**查询参数**:
| 参数 | 默认 | 匹配方式 |
| --- | --- | --- |
| `caller` | 空 | 与 `caller_id` 全等(取值用响应里的 `callers[].caller_id`,不是 Key 名称) |
| `status` | 空 | 与记录状态不区分大小写全等:`streaming` / `success` / `error` / `stopped` |
| `surface` | 空 | 与来源接口不区分大小写全等,如 `openai.chat_completions`、`openai.responses`、`claude.messages`、`gemini.generate_content` |
| `q` | 空 | 不区分大小写包含匹配,命中 `user_input` / `preview` / `model` / `caller_label` / `error` |
| `offset` | `0` | 分页偏移;`offset` 与 `limit` 都为 `0` 表示不分页 |
| `limit` | `0` | 单页条数;`0` 表示不限制条数(不按 settings 的保留条数分页) |
非法或负数的 `offset` / `limit` 按 `0` 处理。
**响应**:
```json
{
"version": 2,
"limit": 20,
"revision": 128,
"total": 137,
"callers": [
{"caller_id": "caller:9f2c1a2b3c4d5e6f", "label": "主 Key", "configured": true, "count": 96},
{"caller_id": "caller:41ab0c9d8e7f6a5b", "label": "备用 Key", "configured": true, "count": 0}
],
"items": [
{
"id": "chat_20260418_093012_ab12",
"status": "success",
"caller_id": "caller:9f2c1a2b3c4d5e6f",
"caller_label": "主 Key",
"account_id": "user@example.com",
"surface": "openai.chat_completions",
"model": "deepseek-v4-flash",
"stream": true,
"user_input": "今天的新闻",
"preview": "……",
"status_code": 200,
"elapsed_ms": 4210,
"finish_reason": "stop",
"created_at": 1777000000000,
"updated_at": 1777000004000,
"detail_revision": 3
}
],
"path": "/data/chat_history.json"
}
```
- `total` 是筛选后的总条数,`items` 是分页后的当前页;不带任何筛选/分页参数时 `items` 仍是全部摘要。
- `caller_label` 是发起方 API Key 的可读标签(名称 / 备注 / 脱敏 Key),没有备注时退回脱敏值,避免面板里只剩一串哈希。
- `callers` 汇总记录里出现过的 `caller_id`,并补上配置里已登记但还没有记录的 Key(`count` 为 `0`,`configured=true`),按 `count` 倒序、`label` 升序排列。
- 时间戳字段(`created_at` / `updated_at` / `completed_at`)单位是毫秒。
- `If-None-Match` + `ETag` 的 `304` 协商只在**不带任何筛选/分页参数**时生效,因为带参数的响应内容本身随查询条件变化。
### `GET /admin/chat-history/{id}`
查看单条记录全文,响应为 `{"item": {...}}`。除摘要字段外还包含 `messages`、`history_text`(历史拆分上传的对话记录全文)、`final_prompt`、`reasoning_content`、`content`、`error`、`usage`、`file_infos`(上传文件 ID、审核结果、来源)等。同样支持 `ETag` / `304`。
### `DELETE /admin/chat-history`
清空全部服务器端记录,返回 `{"success": true}`。
### `DELETE /admin/chat-history/{id}`
删除单条记录,返回 `{"success": true}`;记录不存在返回 `404`。
### `PUT /admin/chat-history/settings`
更新保留条数。`limit` 接受 `0`..`500` 的任意整数,不再是固定档位。
```json
{"limit": 120}
```
| 取值 | 行为 |
| --- | --- |
| `0` | 关闭记录:不再写新记录 |
| `1`..`500` | 保留最近该数量的记录,超出部分按创建时间淘汰 |
| 其他 | 返回 `400`,`detail` 为 `unsupported chat history limit: <n>` |
响应带 `success`、`limit`、`revision` 与裁剪后的 `items`。
### `GET /admin/chat-history/export`
按与列表接口相同的筛选与分页参数(`caller` / `status` / `surface` / `q` / `offset` / `limit`)导出记录,响应是文件下载。
| 参数 | 说明 |
| --- | --- |
| `format=json`(默认) | JSON 附件,结构为 `{exported_at, count, filters, items}`;`exported_at` 为 RFC3339,`filters` 回显本次筛选条件,`items` 是完整记录(含 `messages` / `final_prompt` / `history_text` 等大字段) |
| `format=markdown` | Markdown 附件,标题 `# ds2api 响应记录导出`,逐条记录 `## N. <记录 ID>` 分节,含时间、接口、模型、账号、调用方、状态、耗时、结束原因,以及 `UserInput` / `FinalPrompt` / `ReasoningContent` / `Content` / `Error` 代码块 |
| 其他值 | 返回 `400`,`detail` 为 `unsupported export format: <format>` |
响应头带 `Content-Disposition: attachment`(文件名为 `ds2api-logs-<YYYYMMDD-HHMMSS>.json|.md`)与 `Cache-Control: no-store`。导出过程中若某条记录已被删除,该条会被跳过,`count` 反映实际导出的条数。
---
## 错误响应格式
兼容路由(`/v1/*`、`/anthropic/*`)统一使用以下结构:
```json
{
"error": {
"message": "...",
"type": "invalid_request_error",
"code": "invalid_request",
"param": null
}
}
```
Admin 接口保持 `{"detail":"..."}`。
Gemini 路由使用 Google 风格错误结构:
```json
{
"error": {
"code": 400,
"message": "invalid json",
"status": "INVALID_ARGUMENT"
}
}
```
建议客户端处理逻辑:检查 HTTP 状态码 + 解析 `error` 或 `detail` 字段。
**常见状态码**:
| 状态码 | 说明 |
| --- | --- |
| `401` | 鉴权失败(key/token 无效,或 Admin JWT 过期) |
| `429` | 请求过多(超出并发上限 + 等待队列,或上游账号 thinking-only 后仍无可见输出;托管账号模式会先尝试一次切号 fresh retry;当前不附带 `Retry-After` 头) |
| `503` | 模型不可用或上游服务异常 |
---
## cURL 示例
### OpenAI 非流式
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "你好"}],
"stream": false
}'
```
### OpenAI 流式
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "解释一下量子纠缠"}],
"stream": true
}'
```
### OpenAI Responses(流式)
```bash
curl http://localhost:5001/v1/responses \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.3-codex",
"input": "写一个 golang 的 hello world",
"stream": true
}'
```
### OpenAI Embeddings
```bash
curl http://localhost:5001/v1/embeddings \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"input": ["第一段文本", "第二段文本"]
}'
```
### OpenAI 带搜索
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash-search",
"messages": [{"role": "user", "content": "今天的新闻"}],
"stream": true
}'
```
### OpenAI Tool Calling
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "北京今天天气怎么样?"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
]
}'
```
### Gemini 非流式
```bash
curl "http://localhost:5001/v1beta/models/gemini-2.5-pro:generateContent" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"contents": [
{
"role": "user",
"parts": [{"text": "用三句话介绍 Go 语言"}]
}
]
}'
```
### Gemini 流式
```bash
curl "http://localhost:5001/v1beta/models/gemini-2.5-flash:streamGenerateContent" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"contents": [
{
"role": "user",
"parts": [{"text": "写一个简短摘要"}]
}
]
}'
```
### Claude 非流式
```bash
curl http://localhost:5001/anthropic/v1/messages \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "你好"}]
}'
```
### Claude 流式
```bash
curl http://localhost:5001/anthropic/v1/messages \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "解释相对论"}],
"stream": true
}'
```
### Admin 登录
```bash
curl http://localhost:5001/admin/login \
-H "Content-Type: application/json" \
-d '{"admin_key": "admin"}'
```
### 指定账号请求
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "X-Ds2-Target-Account: user@example.com" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "你好"}]
}'
```