405 lines
26 KiB
Markdown
405 lines
26 KiB
Markdown
# DeepSeek API
|
||
|
||
> 来源:
|
||
> - https://api-docs.deepseek.com/api/create-chat-completion
|
||
> - https://api-docs.deepseek.com/guides/thinking_mode
|
||
> - https://api-docs.deepseek.com/guides/tool_calls
|
||
> - https://api-docs.deepseek.com/guides/json_mode
|
||
> - https://api-docs.deepseek.com/guides/kv_cache
|
||
> - https://api-docs.deepseek.com/guides/chat_prefix_completion
|
||
> - https://api-docs.deepseek.com/guides/fim_completion
|
||
> - https://api-docs.deepseek.com/guides/responses_api
|
||
> - https://api-docs.deepseek.com/guides/anthropic_api
|
||
> - https://api-docs.deepseek.com/quick_start/pricing
|
||
> - https://api-docs.deepseek.com/quick_start/error_codes
|
||
>
|
||
> 文档采集于 2026-08-12。**2026-08-13 用真实 API key 全量实测校验过一遍**,实测与文档冲突处均以实测为准并标注 ⚠️。
|
||
> 实测环境 `system_fingerprint`:flash = `fp_a18b46594c_prod0820_fp8_kvcache_20260402`,pro = `fp_v4pro_20260812_prod0820_fp8_kvcache_20260402`。
|
||
|
||
DeepSeek 的定位是 **OpenAI 格式兼容**,但有一批自有扩展和"看起来支持其实忽略"的参数。它同时提供三种入口格式(OpenAI / Anthropic / Responses),本身就是一个"中转站"的现成参考——**而且是一个反面参考**:实测发现它的三个入口在思考参数、图片处理、错误格式、tool_choice 上行为都不一致。
|
||
|
||
Base URL:
|
||
- `https://api.deepseek.com` — OpenAI Chat Completions 格式 + Responses API 格式
|
||
- `https://api.deepseek.com/beta` — Beta 功能(prefix completion、FIM、strict tool calls)
|
||
- `https://api.deepseek.com/anthropic` — Anthropic Messages 格式
|
||
|
||
实测路径别名(文档未列):
|
||
- Responses:`/responses` 与 `/v1/responses` 等价
|
||
- Anthropic:`/anthropic/messages` 与 `/anthropic/v1/messages` 等价
|
||
- Anthropic 入口鉴权:`x-api-key` 与 `Authorization: Bearer` **都接受**
|
||
|
||
额外端点(文档未列):`GET /user/balance` → `{"is_available":true,"balance_infos":[{"currency":"CNY","total_balance":"...","granted_balance":"...","topped_up_balance":"..."}]}`。中转站可用它做上游健康检查与余额预警。
|
||
|
||
---
|
||
|
||
## 1. 模型与能力矩阵
|
||
|
||
`GET /models` 实测只返回两个模型:
|
||
|
||
| 模型 | 上下文 | 最大输出 |
|
||
| --- | --- | --- |
|
||
| `deepseek-v4-flash` | 1,048,576 | 393,216 |
|
||
| `deepseek-v4-pro` | 1,048,576 | 393,216 |
|
||
|
||
> 实测精确值,来自越界报错:`This model's maximum context length is 1048576 tokens`、`the valid range of max_tokens is [1, 393216]`。
|
||
|
||
⚠️ **模型名别名**:`deepseek-chat` 和 `deepseek-reasoner`(旧模型名)会被**静默映射到 `deepseek-v4-flash`**,响应的 `model` 字段返回 `deepseek-v4-flash`。其他未知名字(`gpt-4`、`deepseek-v3`、空串等)会明确报错:`The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but you passed X.`
|
||
> 中转站注意:用户传 `deepseek-chat` 拿到的是 v4-flash 且无任何提示,账单模型名与请求模型名对不上。
|
||
|
||
| 能力 | v4-flash | v4-pro |
|
||
| --- | --- | --- |
|
||
| Tool Calls | ✓ | ✓ |
|
||
| JSON Output(仅 `json_object`) | ✓ | ✓ |
|
||
| FIM Completion | ✓ | ✓ |
|
||
| Chat Prefix Completion | ✓ | ✓ |
|
||
| Thinking Mode | ✓(默认开) | ✓(默认开) |
|
||
| Responses API | ✓ | ✓ ⚠️ |
|
||
| 内置 `web_search`(仅 Responses 入口) | ✓ | ✓ |
|
||
| Anthropic API | ✓ | ✓ |
|
||
|
||
⚠️ 文档称 Responses API "不支持 v4-pro,计划 2026-08 初支持"——**实测已支持**,`deepseek-v4-pro` 在 `/v1/responses` 正常返回。
|
||
⚠️ 文档称 FIM "仅非思考模式"且"模型用 v4-pro"——**实测 flash 和 pro 都支持 FIM**,见 §7。
|
||
|
||
价格(每 1M token):
|
||
|
||
| 模型 | 缓存命中 | 缓存未命中 | 输出 |
|
||
| --- | --- | --- | --- |
|
||
| v4-flash | $0.0028 | $0.14 | $0.28 |
|
||
| v4-pro | $0.003625 | $0.435 | $0.87 |
|
||
|
||
> 官方页面明确预告将大幅涨价,价格不要硬编码。
|
||
|
||
---
|
||
|
||
## 2. Chat Completions(`POST /chat/completions`)
|
||
|
||
必填:`messages`、`model`。
|
||
|
||
| 参数 | 类型 | 默认 | 实测行为 |
|
||
| --- | --- | --- | --- |
|
||
| `thinking` | object | `{type:"enabled"}` | **DeepSeek 自有扩展**;`type: enabled/disabled/adaptive` ⚠️,`reasoning_effort: low/high/max` |
|
||
| `reasoning_effort` | string | — | ⚠️ **顶层 OpenAI 写法也生效**(文档未列),`"none"` 可关闭思考 |
|
||
| `max_tokens` | int | — | 范围 `[1, 393216]`,越界 400 |
|
||
| `temperature` | number | 1 | 范围 `[0, 2]`,越界 400(非静默忽略) |
|
||
| `top_p` | number | 1 | 范围 `(0, 1.0]`,越界 400 |
|
||
| `response_format` | object | `{type:"text"}` | 仅 `text` / `json_object`;`json_schema` 报 400 |
|
||
| `stream` | bool | false | |
|
||
| `stream_options` | object | — | `include_usage` |
|
||
| `stop` | string\|array | — | 最多 16 个,17 个报 `Stop string array too long: 17` |
|
||
| `tools` | array | — | `type` 只接受 `function`(`web_search` 等报 400) |
|
||
| `tool_choice` | — | `auto` | ⚠️ 思考模式下**只支持 `none`/`auto`**,见 §4 |
|
||
| `logprobs` | bool | false | ⚠️ 思考模式下返回结构不同,见下 |
|
||
| `top_logprobs` | int | — | 0 ~ 20,需 `logprobs=true` |
|
||
| `user_id` | string | — | ≤512 字符,文档称字符集 `[a-zA-Z0-9\-_]`,**实测不校验**(传 `用户@1` 也 200) |
|
||
| `n` | int | 1 | **只支持 1**,`n=2` 报 `Invalid n value (currently only n = 1 is supported)` |
|
||
|
||
**已废弃**:`frequency_penalty`、`presence_penalty` — 实测传了不报错,静默忽略。
|
||
**静默忽略**(传了不报错也不生效):`seed`、OpenAI 标准的 `user`(注意与 DeepSeek 自有的 `user_id` 不是同一个参数)。
|
||
|
||
**硬报错的兼容性坑**:
|
||
- `role: "developer"`(OpenAI 新标准)→ 400 `unknown variant 'developer'`。中转站必须把 `developer` 降级成 `system`。
|
||
- `content` 数组中的 `image_url` → 400 `unknown variant 'image_url', expected ...`。**注意这与 Anthropic / Responses 入口的静默降级行为不一致**,见 §8。
|
||
- 纯文本的 `content: [{"type":"text","text":"..."}]` 数组形式 → 正常支持。
|
||
|
||
**消息序列约束极宽松**(实测均返回 200):连续两条 `user`、以 `assistant` 结尾(无 `prefix`)、`system` 出现在中间、只有 `system` 一条——全部接受。唯一报错的是空数组 `messages: []` → `Empty input messages`。
|
||
|
||
响应字段基本对齐 OpenAI,额外有:
|
||
- `message.reasoning_content` — 思考内容(字符串)
|
||
- `usage.prompt_cache_hit_tokens` / `usage.prompt_cache_miss_tokens`
|
||
- `usage.prompt_tokens_details.cached_tokens`(与 `prompt_cache_hit_tokens` 同值,两套字段并存)
|
||
- `usage.completion_tokens_details.reasoning_tokens` — ⚠️ **仅思考模式存在**;非思考模式下整个 `completion_tokens_details` 对象都不出现
|
||
- `finish_reason` 多一个取值:**`insufficient_system_resource`**
|
||
- ⚠️ 非流式响应的 `tool_calls[]` 里**也带 `index` 字段**(OpenAI 非流式不带)
|
||
|
||
### ⚠️ logprobs 在思考模式下结构不同
|
||
|
||
非思考模式:`logprobs.content[]`(同 OpenAI)。
|
||
思考模式:返回的是 **`logprobs.reasoning_content[]`** ——这个 key 在 OpenAI 规范里不存在(OpenAI 只有 `content` 和 `refusal`)。严格按 OpenAI schema 反序列化的客户端会拿到空的 logprobs 或直接解析失败。
|
||
|
||
---
|
||
|
||
## 3. Thinking Mode 的怪癖
|
||
|
||
### ⚠️ 三个入口的参数写法互不相同——且与官方文档记载相反
|
||
|
||
文档给出的映射表(Anthropic 用 `reasoning.effort`、Responses 用 `output_config.effort`)**实测是错的**。以"关闭思考"为探针跑的完整交叉矩阵(观察 `reasoning_content`/`thinking` block 是否出现、`prompt_tokens` 是 5 还是 84):
|
||
|
||
| 入口 | 写法 | 实测结果 |
|
||
| --- | --- | --- |
|
||
| **OpenAI** | `{"thinking":{"type":"disabled"}}` | ✅ 生效(prompt=5) |
|
||
| **OpenAI** | `{"reasoning_effort":"none"}` | ✅ 生效(prompt=5)——文档未记载 |
|
||
| **OpenAI** | `{"reasoning":{"effort":"none"}}` | ❌ 静默忽略(prompt=84,仍思考) |
|
||
| **Anthropic** | `{"thinking":{"type":"disabled"}}` | ✅ 生效(input=5) |
|
||
| **Anthropic** | `{"reasoning":{"effort":"none"}}` | ❌ 静默忽略(input=84)——**文档说这个才对,实测无效** |
|
||
| **Anthropic** | `{"reasoning_effort":"none"}` | ❌ 静默忽略(input=84) |
|
||
| **Responses** | `{"reasoning":{"effort":"none"}}` | ✅ 生效(reasoning_tokens=0) |
|
||
| **Responses** | `{"output_config":{"effort":"none"}}` | ❌ 静默忽略——**文档说这个才对,实测无效** |
|
||
| **Responses** | `{"thinking":{"type":"disabled"}}` | ❌ 静默忽略 |
|
||
|
||
**结论**:Chat 与 Anthropic 入口统一用 `thinking.type`;Responses 入口用 OpenAI 标准的 `reasoning.effort`。所有"不生效"的写法都是**静默忽略、不报错**——中转站若照文档实现关思考,会得到一个照常思考并照常计费的响应。
|
||
|
||
### ⚠️ `thinking.type` 有第三个取值 `adaptive`(文档完全未记载)
|
||
|
||
从 400 报错里泄露的枚举:`unknown variant 'banana', expected one of 'adaptive', 'enabled', 'disabled'`。
|
||
|
||
实测 `adaptive` 会按问题难度调节思考量("hi" → 24 reasoning tokens;"严格证明 √2 无理" → 262),但**不会完全关闭思考**,简单问题仍产出 `reasoning_content`。
|
||
|
||
### ⚠️ 思考模式会注入约 79 token 的隐藏 system prompt,并计入你的账单
|
||
|
||
同一条 `"hi"` 请求:
|
||
|
||
| | `prompt_tokens` |
|
||
| --- | --- |
|
||
| `thinking.type = disabled` | **5** |
|
||
| `thinking.type = enabled`(默认) | **84** |
|
||
|
||
这 79 token 的差额是 DeepSeek 服务端注入的思考指令,按输入价计费,且**三个入口都一样**(Anthropic 入口 `input_tokens` 同为 84)。默认开启意味着中转站转发的每一条短请求都在为它买单。
|
||
|
||
### reasoning_effort 的实际效果存疑
|
||
|
||
`reasoning_effort` 的**非法值不报错**(`"banana"` 照常返回 200),只有 `thinking.type` 是强类型枚举。`none`/`minimal`/`xhigh`/`medium` 等文档外取值也一律静默接受。
|
||
|
||
在 flash 上对同一道题各档采样 5 次的 `reasoning_tokens`:
|
||
|
||
| effort | 样本 | 均值 |
|
||
| --- | --- | --- |
|
||
| `low` | 116, 80, 118, 68, 118 | 100.0 |
|
||
| `high` | 48, 71, 120, 73, 90 | 80.4 |
|
||
| `max` | 101, 56, 111, 89, 79 | 87.2 |
|
||
|
||
**方差远大于档位间差异,flash 上看不出 effort 有可观测效果。** v4-pro 上采样(low ≈ 513、high ≈ 799)倾向于正相关,但样本量不足以定论。文档所称"flash 直通、pro 上 high 与 xhigh 都映射为 high"**未能证实也未能证伪**——不要基于这个假设做路由决策。
|
||
|
||
> 例外:Responses 入口的 `reasoning.effort: "none"` 是唯一能观测到确定效果的取值(`reasoning_tokens` 精确为 0)。
|
||
|
||
### ⚠️ reasoning_content 多轮回传是硬性要求(不回传直接 400)
|
||
|
||
文档表述为"必须回传",实测是**服务端强校验**:
|
||
|
||
```
|
||
{"error":{"message":"The `reasoning_content` in the thinking mode must be passed back to the API.",
|
||
"type":"invalid_request_error","code":"invalid_request_error"}}
|
||
```
|
||
|
||
精确的触发条件(实测逐一验证):
|
||
|
||
| 场景 | 不回传 `reasoning_content` |
|
||
| --- | --- |
|
||
| 思考模式 + 历史含 `tool_calls` | **400 报错** |
|
||
| 思考模式 + 无 `tool_calls` 的普通多轮 | 正常,不报错 |
|
||
| 非思考模式 + 历史含 `tool_calls` | 正常,不报错 |
|
||
|
||
传空串 `"reasoning_content": ""` **可以过校验**——这是中转站从 Anthropic/OpenAI 格式转过来、丢失了原始思考内容时的可用降级手段(模型质量可能下降,但不会 400)。
|
||
|
||
这和 Anthropic 的规则(thinking block 必须原样、带 signature 回传)以及 OpenAI 的规则(encrypted_content / reasoning item)都不同,中转站需要按上游分别处理。
|
||
|
||
### 流式响应的两处非 OpenAI 行为
|
||
|
||
1. **`usage` 挂在最后一个正常 chunk 上**,而不是像 OpenAI 那样单独发一个 `choices: []` 的 usage-only chunk。DeepSeek 把 usage 塞进了带 `finish_reason` 的那个 chunk 里,之后直接 `data: [DONE]`。按 OpenAI 语义等待独立 usage chunk 的实现会永远拿不到 usage。
|
||
2. **delta 中未使用的字段是显式 `null` 而非省略**:`{"delta":{"content":null,"reasoning_content":"We"}}` / `{"delta":{"content":"4","reasoning_content":null}}`。对 `null` 与"字段缺失"处理不同的客户端需注意。
|
||
|
||
思考内容通过 `delta.reasoning_content` 增量下发,先出完 `reasoning_content` 再出 `content`。
|
||
|
||
---
|
||
|
||
## 4. Tool Calls 的怪癖
|
||
|
||
- 结构与 OpenAI 一致:`tools[].{type:"function", function:{name, description, parameters}}`,回传用 `role: "tool"` + `tool_call_id`。`tools[].type` 只接受 `function`。
|
||
- **思考模式下 `tool_choice` 只支持 `auto` 和 `none`**(⚠️ 文档未记载):
|
||
|
||
| `tool_choice` | thinking enabled | thinking disabled |
|
||
| --- | --- | --- |
|
||
| `"auto"` | ✅ | ✅ |
|
||
| `"none"` | ✅ | ✅ |
|
||
| `"required"` | ❌ **400** | ✅ |
|
||
| `{"type":"function","function":{"name":...}}` | ❌ **400** | ✅ |
|
||
|
||
报错信息:`Thinking mode does not support this tool_choice`。由于**思考模式是默认开启的**,中转站转发一个带 `tool_choice: "required"` 的普通 OpenAI 请求会直接 400——必须同时下发 `thinking.type: "disabled"`,或把 `required` 降级为 `auto`。
|
||
|
||
- **`strict` 是 Beta 功能**,文档称必须切到 `https://api.deepseek.com/beta`。实测在主 base_url 上传 `strict: true` **不报错**(静默接受并正常返回),无法从响应上区分是否生效——属于典型的静默失效。
|
||
- strict 模式支持的 schema 类型:object、string、number、integer、boolean、array、enum、anyOf、`$ref`、`$def`。实测传入 `pattern`、`minLength` 等不支持的关键字也不报错(静默接受,约束不保证生效)。
|
||
- 思考模式下的工具调用从 DeepSeek-V3.2 起支持,实测思考 + 工具调用可同时返回 `reasoning_content` 与 `tool_calls`,`finish_reason: "tool_calls"`。
|
||
|
||
---
|
||
|
||
## 5. JSON Output 的怪癖
|
||
|
||
- 只有 `response_format: {"type": "json_object"}`。`json_schema` 明确报错:`This response_format type is unavailable now`(措辞暗示未来会支持,但目前中转站必须拦截或降级)。
|
||
- **必须在 system 或 user prompt 中出现 "json" 字样**,否则硬报错:
|
||
`Prompt must contain the word 'json' in some form to use 'response_format' of type 'json_object'.`
|
||
中转站从其他上游格式转发时,如果用户 prompt 里没有 "json" 字样,会拿到一个用户完全无法理解的 400。
|
||
- 官方承认 API 偶尔会返回空 content,建议通过调整 prompt 措辞规避。中转站需要有重试策略。
|
||
- `max_tokens` 给小了会截断成非法 JSON。
|
||
- 与思考模式可共存,`reasoning_content` 与 JSON `content` 同时返回。
|
||
|
||
---
|
||
|
||
## 6. Context Caching 的怪癖
|
||
|
||
- **全自动,无 `cache_control` 之类的手动参数。** 与 Anthropic 的显式断点模型完全不同——不能双向映射。
|
||
- ⚠️ **实测缓存粒度为 64 token**(文档称"未说明最小可缓存粒度")。两组独立实测的 `prompt_cache_hit_tokens` 均为 64 的整数倍:3328 = 52×64、1920 = 30×64。命中量向下对齐到 64 的倍数,尾部不足 64 的部分计为 miss。
|
||
- ⚠️ **缓存即时生效**(文档称"缓存构建需要数秒")。实测第 1 次请求 miss 全量,**紧接着的第 2 次请求立即命中**,无需等待:
|
||
|
||
| 请求 | prompt_tokens | hit | miss |
|
||
| --- | --- | --- | --- |
|
||
| 第 1 次 | 3379 | 0 | 3379 |
|
||
| 第 2 次 | 3379 | 3328 | 51 |
|
||
| 第 3 次 | 3379 | 3328 | 51 |
|
||
|
||
- **公共前缀缓存已验证**:两个只有尾部一个词不同的请求(2006 / 2007 token)交替发送,双方都稳定命中共同前缀的 1920 token。
|
||
- 缓存不再使用后自动清除,通常在**数小时到数天**内。
|
||
- 统计字段:`prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`,以及 `prompt_tokens_details.cached_tokens`(同值)。Anthropic 入口映射为 `cache_read_input_tokens`,`cache_creation_input_tokens` **实测恒为 0**(DeepSeek 没有"写入缓存"这个计费概念,官方 Anthropic 的缓存写入溢价在此不存在)。
|
||
|
||
---
|
||
|
||
## 7. Beta 功能(需 `base_url=https://api.deepseek.com/beta`)
|
||
|
||
### Chat Prefix Completion
|
||
|
||
在 `messages` 最后一条设 `{"role": "assistant", "content": "...", "prefix": true}`,模型从该前缀继续生成。
|
||
|
||
- ⚠️ 在主 base_url 上传 `prefix` 是**明确报错**(不是静默忽略):
|
||
`prefix is only available when using beta api (set base_url="https://api.deepseek.com/beta")`
|
||
- 最后一条 message 的 role 必须是 `assistant`。
|
||
- 常与 `stop` 配合,例如前缀 ` ```python\n ` + `stop=["```"]` 强制只出代码。
|
||
- **中转站注意**:这个能力无法映射到 Claude 4.6+(明确禁止 prefill),也不是 OpenAI 标准能力。
|
||
|
||
### FIM Completion
|
||
|
||
端点是 `/beta/completions`(不是 chat),参数 `prompt` + `suffix`。在主 base_url 上调用明确报错:`completions api is only available when using beta api (...)`。
|
||
|
||
⚠️ 文档的三条限制实测**全部不成立**:
|
||
|
||
| 文档说法 | 实测 |
|
||
| --- | --- |
|
||
| `max_tokens` 上限 4K | **上限 393216**(4096/8192/65536/393216 全部 200,393217 才报错) |
|
||
| 模型用 `deepseek-v4-pro` | **flash 和 pro 都可用** |
|
||
| 仅非思考模式支持 | 传 `thinking` 参数不报错、正常返回(该参数在此端点无意义) |
|
||
|
||
其他实测:支持 legacy completions 的 `echo` 与 `logprobs`(int 型),但**两者互斥**——同时传报 `echo should not be used with logprobs`。响应是 `object: "text_completion"`,`choices[].text`,**没有 `completion_tokens_details`**。
|
||
|
||
---
|
||
|
||
## 8. 官方兼容层(DeepSeek 自己做的中转)
|
||
|
||
### Anthropic 格式(`https://api.deepseek.com/anthropic`)
|
||
|
||
模型名自动映射(实测确认):
|
||
- `claude-opus*` → `deepseek-v4-pro`
|
||
- `claude-haiku*` / `claude-sonnet*` → `deepseek-v4-flash`
|
||
- 直接传 `deepseek-v4-pro` / `deepseek-v4-flash` 也接受
|
||
- ⚠️ 其他未知名字(如 `gpt-4`)**实测是报错,不是静默兜底**:`The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but you passed gpt-4.`(此处修正了文档"静默兜底到 flash"的说法)
|
||
|
||
支持:`model`、`max_tokens`、`system`、`temperature`(**`[0, 2]`**,比官方 Anthropic 的 0~1 宽,2.5 报 400)、`stream`、`stop_sequences`、`tools`、`thinking`(`budget_tokens` 被忽略)。
|
||
|
||
流式格式规范,事件序列为 `message_start` → `content_block_start` → `ping` → `content_block_delta`(×N) → `content_block_stop` → …… → `message_delta` → `message_stop`,含 `thinking_delta` / `signature_delta` / `text_delta` 三种 delta 类型。
|
||
|
||
⚠️ **实测发现的四个兼容性缺陷**:
|
||
|
||
1. **错误响应用的是 OpenAI/DeepSeek 格式,不是 Anthropic 格式。**
|
||
实际返回 `{"error":{"message":"...","type":"authentication_error","param":null,"code":"invalid_request_error"}}`,
|
||
而 Anthropic SDK 期望 `{"type":"error","error":{"type":"authentication_error","message":"..."}}`。
|
||
顶层缺 `"type":"error"`、多出 `param`/`code` 字段——官方 Anthropic SDK 的错误反序列化会失败或降级成通用异常。
|
||
|
||
2. **`max_tokens` 不是必填。** 官方 Anthropic API 中 `max_tokens` 必填,此处省略不报错,静默用默认值。依赖该校验的客户端会行为不一致。
|
||
|
||
3. **thinking block 的 `signature` 是伪造的——它就是本次响应的 `id`。**
|
||
```
|
||
{"id": "a7f8579c-ef83-49fb-b93c-bda7d513c07b",
|
||
"content": [{"type": "thinking", "thinking": "...",
|
||
"signature": "a7f8579c-ef83-49fb-b93c-bda7d513c07b"}]}
|
||
```
|
||
不是真正的加密签名,不具备任何校验意义,也不能与真实 Anthropic 上游互换。
|
||
|
||
4. **`tool_choice` 行为与 OpenAI 入口不一致。**
|
||
|
||
| `tool_choice` | Anthropic 入口 | 对应的 OpenAI 入口写法 |
|
||
| --- | --- | --- |
|
||
| `{"type":"auto"}` | ✅ | `"auto"` ✅ |
|
||
| `{"type":"any"}` | ⚠️ **静默降级为 auto**(实测明确要求"不要用工具"时模型确实不调工具,`stop_reason: "end_turn"`) | `"required"` ❌ 思考模式下 400 |
|
||
| `{"type":"tool","name":"..."}` | ❌ 400 `Thinking mode does not support this tool_choice` | 同样 400 |
|
||
|
||
同一个语义(强制调用工具),一个入口静默失效、另一个入口硬报错。
|
||
|
||
忽略 / 不支持:`anthropic-beta` 与 `anthropic-version` header、`top_k`、大部分 metadata 字段(例外:`metadata.user_id` 支持,用于限流隔离)。
|
||
|
||
⚠️ **image content 不是"不支持",而是静默替换成占位文本**(文档表述为不支持,实测是降级)。传入 `{"type":"image","source":{...}}` 不报错,模型的思考内容里暴露了它实际收到的是 `[Unsupported Image]` 占位符,然后回答"我看不到图片"。用户拿到的是一个 HTTP 200 的错误答案。
|
||
|
||
### Responses 格式(`https://api.deepseek.com/v1/responses`)
|
||
|
||
- ⚠️ **v4-flash 和 v4-pro 都已支持**(文档称仅 flash)。
|
||
- **未支持的参数一律静默忽略,不报错**,且响应里会把它们**回显成默认值**而非你传入的值——这是判断是否生效的可靠手段:
|
||
`store: true` → 回显 `false`;`previous_response_id: "resp_123"` → 回显 `null`;`conversation` 不支持。
|
||
- 思考参数用 `reasoning.effort`(见 §3),`effort: "none"` 可确定关闭思考。响应中 `reasoning` 字段会回显你传入的 effort。
|
||
- 上下文缓存自动进行,无手动参数(`prompt_cache_key` 恒 `null`)。
|
||
- ⚠️ **image 和 file 输入被替换成字面占位文本 `[Unsupported Image]`**(而不是报错)——这个行为对中转站尤其危险,用户会得到一个"成功但内容错误"的响应。与 Anthropic 入口行为一致,但与 OpenAI 入口(硬报错)不一致。
|
||
- usage 结构是 Responses 风格:`input_tokens` / `input_tokens_details.cached_tokens` / `output_tokens` / `output_tokens_details.reasoning_tokens` / `total_tokens`。
|
||
- 输出为 `output[]` 数组,思考是 `{"type":"reasoning","content":[{"type":"reasoning_text","text":"..."}],"summary":[]}`,正文是 `{"type":"message","phase":"final_answer",...}`(⚠️ `phase` 是非 OpenAI 标准字段)。
|
||
- 流式事件完整:`response.created` / `response.in_progress` / `response.output_item.added` / `response.content_part.added` / `response.reasoning_text.delta` / `response.reasoning_text.done` / `response.output_text.delta` / `response.output_text.done` / `response.content_part.done` / `response.output_item.done` / `response.completed`。
|
||
|
||
#### ⚠️ 内置 `web_search` 工具(文档完全未记载,且只有这一个入口有)
|
||
|
||
传 `"tools":[{"type":"web_search"}]` 实测**真的会执行联网搜索**,输出数组中出现:
|
||
|
||
```json
|
||
{"type":"web_search_call","id":"call_00_...","status":"completed",
|
||
"action":{"type":"search","queries":["capital of France","ws_call_id=call_00_..."]}}
|
||
```
|
||
|
||
回显的工具定义为 `{"type":"web_search","search_context_size":null,"user_location":null}`。搜索结果注入会显著抬高 `input_tokens`(实测同一问题从 84 涨到 2403)。
|
||
|
||
跨入口对比:
|
||
|
||
| 入口 | `web_search` |
|
||
| --- | --- |
|
||
| Responses | ✅ 真实执行 |
|
||
| OpenAI Chat | ❌ 400 `unknown variant 'web_search', expected 'function'` |
|
||
| Anthropic(`web_search_20250305`)| ⚠️ 静默接受但不执行,模型直接凭知识作答 |
|
||
|
||
---
|
||
|
||
## 9. 错误码
|
||
|
||
| Code | 含义 | 处理 |
|
||
| --- | --- | --- |
|
||
| 400 | 请求体格式非法 | 按错误信息调整 |
|
||
| 401 | API key 错误 | 实测消息体:`Authentication Fails, Your api key: ****-bad is invalid`,`type: "authentication_error"` |
|
||
| 402 | **余额不足** | OpenAI/Anthropic 无此码,兼容层需决定映射到 429 还是 403 |
|
||
| 422 | 参数非法 | |
|
||
| 429 | 限流 | |
|
||
| 500 | 服务端故障 | 重试 |
|
||
| 503 | 服务器过载 | 稍后重试 |
|
||
|
||
错误响应体统一为 OpenAI 风格 `{"error":{"message","type","param","code"}}`——**包括 Anthropic 入口**(见 §8 缺陷 1)。
|
||
|
||
`type` 实测取值:`invalid_request_error`(400/422)、`authentication_error`(401)。注意 401 的 `code` 字段仍是 `invalid_request_error`,与 `type` 不一致,不要用 `code` 判断错误类别。
|
||
|
||
**限流**:实测 30 并发短请求全部 200,未触发 429,具体阈值未知。文档未说明限流时是否有 keep-alive 空行等连接层行为。
|
||
|
||
反序列化错误(Rust serde 风格)会泄露内部枚举,可用于探测未文档化的取值——`adaptive` 思考模式就是这么发现的:
|
||
```
|
||
Failed to deserialize the JSON body into the target type: thinking.type: unknown variant `banana`,
|
||
expected one of `adaptive`, `enabled`, `disabled` at line 1 column 100
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 中转站实现要点速查
|
||
|
||
按危险程度排序的实测结论:
|
||
|
||
| # | 陷阱 | 后果 |
|
||
| --- | --- | --- |
|
||
| 1 | 思考默认开启且注入 ~79 token 隐藏 prompt | 每条请求静默多计 79 输入 token |
|
||
| 2 | 关思考的参数名三个入口各不相同,文档还写错了两个 | 照文档实现 = 关不掉、照常计费 |
|
||
| 3 | `tool_choice: required` + 默认思考模式 = 400 | 标准 OpenAI 请求直接失败 |
|
||
| 4 | 思考模式下 `reasoning_content` 不回传 = 400(仅工具场景) | 跨格式转发丢思考内容即中断;空串可降级过关 |
|
||
| 5 | image 在 Anthropic/Responses 入口被换成 `[Unsupported Image]` | HTTP 200 + 错误答案,最难排查 |
|
||
| 6 | Anthropic 入口错误体不是 Anthropic 格式 | 官方 SDK 错误解析失败 |
|
||
| 7 | Anthropic 入口 `tool_choice: any` 静默降级为 auto | 强制工具调用失效且无提示 |
|
||
| 8 | `deepseek-chat`/`deepseek-reasoner` 静默映射到 v4-flash | 账单模型名与请求不符 |
|
||
| 9 | 流式 usage 在最后一个正常 chunk,非独立 chunk | 按 OpenAI 语义等待 = 永远拿不到 usage |
|
||
| 10 | 思考模式 logprobs 返回 `logprobs.reasoning_content` | 非 OpenAI schema,解析失败 |
|
||
| 11 | `json_object` 要求 prompt 含 "json" 字面量 | 跨格式转发时用户看不懂的 400 |
|
||
| 12 | `role: "developer"` 硬报错 | 必须降级为 `system` |
|
||
| 13 | `strict` / `seed` / `user` / 非法 `reasoning_effort` 静默接受 | 用户以为生效了 |
|