Files
zend-token/docs/deepseek.md
T
2026-08-23 02:12:08 +08:00

405 lines
26 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.
# 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 全部 200393217 才报错) |
| 模型用 `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` 静默接受 | 用户以为生效了 |