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

282 lines
20 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.
# 国产 AI Provider 的怪癖
> 采集于 2026-08-12。
>
> **可信度标注**:本文件里 ✅ 表示来自厂商官方文档页面;⚠️ 表示来自搜索结果聚合或第三方资料,**落地前需要用真实请求验证**。国产厂商文档改版频繁(本次采集就遇到 moonshot.cn → kimi.com、volcengine.com → docs.volcengine.com 两处 301),且很多"历史怪癖"在新版本里已被修掉,不要照抄老博客。
国产 API 的怪癖**和欧美三家不是一个类型**。OpenAI/Anthropic/DeepSeek 的坑主要在**语义层**thinking 怎么回传、tool_result 放哪);国产的坑主要在**协议层和运维层**:鉴权方式五花八门、HTTP 200 里藏业务错误、流式默认全量返回、内容安全是一等公民。
---
## 0. 跨厂商的共性怪癖(最重要)
这五条是中转站架构里必须提前留出的抽象层,逐个厂商去补会很痛苦。
### 0.1 HTTP 200 内嵌业务错误 ⚠️→✅
**MiniMax 是典型**:无论成功失败都返回 **HTTP 200**,真正的状态在 body 的 `base_resp.status_code`0 = 成功)。
```json
{"base_resp": {"status_code": 1002, "status_msg": "rate limit"}}
```
对中转站的直接后果:**只看 HTTP 状态码的限流检测会完全失效**——限流返回的是 200 而不是 429。重试逻辑、熔断器、计费统计全都要能读 body。
建议:网关的上游响应处理层做成"HTTP 状态码 + body 业务码"双通道判定,而不是假设 HTTP 语义可靠。
### 0.2 内容安全是一等公民
欧美三家只有 `content_filter` / `refusal` 一个笼统信号,国产普遍把内容安全做成结构化字段:
| 厂商 | 表现形式 |
| --- | --- |
| 智谱 GLM ✅ | `finish_reason: "sensitive"`(OpenAI 枚举里没有这个值) |
| MiniMax ✅ | `input_sensitive` / `output_sensitive` 布尔,外加 `input_sensitive_type` / `output_sensitive_type`17 的违规分类),还有 `mask_sensitive_info` 参数 |
`finish_reason: "sensitive"` 会直接**打破 OpenAI 客户端的枚举校验**——严格的 SDK(尤其是 Go/Rust 这类强类型的)会反序列化失败。中转站必须把它映射成 `content_filter`,同时用扩展字段保留原值。
### 0.3 鉴权方式没有一个统一的
| 方式 | 厂商 |
| --- | --- |
| `Authorization: Bearer <key>` | 智谱(现行)✅、Kimi ✅、豆包/火山方舟 ✅、腾讯混元兼容端点 ✅、DeepSeek |
| Bearer + 额外 header | 百度千帆 v2`Bearer bce-v3/ALTAK-...` + `appid` header)⚠️ |
| JWT 签名(key 形如 `{id}.{secret}`,需自行签发带 exp 的 token) | 智谱(历史方式,现在仍支持)⚠️ |
| `access_token` 放 query string(用 API Key + Secret Key 先换取) | 百度千帆 v1 ⚠️ |
| TC3-HMAC-SHA256 云厂商签名 | 腾讯混元原生接口 ✅ |
| HMAC-SHA256 签名拼进 **WebSocket URL** | 讯飞星火原生接口 ✅ |
| `AK:SK` 拼接格式 | 讯飞星火 OpenAI 兼容端点 ⚠️ |
中转站的凭据存储不能假设"一个 key 一个字符串"——至少要支持 (key, secret, appid, region) 四元组,且签名逻辑要能按 provider 插拔。
### 0.4 流式默认可能是全量累积
见 §1.2 的阿里 `incremental_output`。这是**最容易在生产上炸**的一条:不设参数时每个 SSE chunk 返回的是**从头累积的完整文本**,直接按 OpenAI 语义拼接会得到指数级重复的内容。
### 0.5 非标参数塞 `extra_body`
国产厂商的 OpenAI 兼容模式普遍保留一批自有参数(思考开关、联网搜索、重复惩罚),官方推荐用 OpenAI SDK 的 `extra_body` 透传。中转站需要一个**参数白名单 + 透传通道**,而不是严格按 OpenAI schema 校验后丢弃未知字段。
---
## 1. 阿里 · 通义千问 / 百炼(DashScope)✅
> 来源:https://help.aliyun.com/zh/model-studio/use-qwen-by-calling-api 、https://help.aliyun.com/zh/model-studio/qwen-api-via-dashscope 、https://www.alibabacloud.com/help/zh/model-studio/qwen-api-via-openai-chat-completions
### 1.1 四套接口并存
百炼同时提供**四种**入口,这本身就是中转站的最佳参考:
1. **OpenAI 兼容 Chat Completions** — 迁移成本最低
2. **OpenAI 兼容 Responses** — 内置联网搜索、代码解释器、网页提取,自动管理对话历史
3. **Anthropic 兼容 Messages** — 支持 thinking 和工具调用
4. **DashScope 原生** — "提供最完整的功能集和参数支持"
**功能集不等价**:原生接口是超集。做中转站时若只对接 OpenAI 兼容模式,会拿不到部分能力。
### 1.2 `incremental_output`:最大的坑 ⚠️
DashScope 原生流式下:
| 取值 | 行为 |
| --- | --- |
| `true` | 每个 chunk 只含**新增**内容(OpenAI 语义) |
| `false`(部分场景默认) | 每个 chunk 返回**从头累积的完整文本** |
如果按 OpenAI 的"delta 累加"逻辑处理 `incremental_output=false` 的流,输出会变成 `我我是我是通我是通义…` 这样的雪球。
**中转站务必显式传 `incremental_output=true`,不要依赖默认值。**
### 1.3 原生接口是嵌套结构
```jsonc
// DashScope 原生
POST /api/v1/services/aigc/text-generation/generation
{
"model": "qwen-plus",
"input": { "messages": [...] }, // ← 嵌套
"parameters": { "temperature": 0.7, "result_format": "message" }
}
// 响应:output.choices[].message ← 比 OpenAI 多一层 output
```
`result_format` 取值 `text` / `message`——**取 `text` 时响应结构完全不同**(不是 choices 数组)。中转站应固定用 `message`
### 1.4 其他
- **原生 HTTP 流式必须加 header `X-DashScope-SSE: enable`** ⚠️,只设 `stream` 参数不生效。
- **base_url 按地域 + workspace 分片** ✅:`https://{WorkspaceId}.{region}.maas.aliyuncs.com/compatible-mode/v1`region 含 cn-beijing / ap-southeast-1 / cn-hongkong / eu-central-1 / ap-northeast-1,另有 `https://dashscope-us.aliyuncs.com/compatible-mode/v1`。中转站的上游配置不能是单一 URL 常量。
- OpenAI 兼容模式下需走 `extra_body` 的非标参数 ✅:`enable_thinking``thinking_budget``top_k``repetition_penalty`0.012.0)、`enable_search` / `search_options``preserve_thinking` / `clear_thinking``enable_code_interpreter``tool_stream``vl_high_resolution_images`
- **QwQ 等思考模型需要流式输出** ✅,非流式体验会因长推理阶段而劣化。
- `seed` 范围 `[0, 2³¹-1]`,与 OpenAI 的 int64 范围不同 ⚠️。
---
## 2. 智谱 GLMBigModel)✅
> 来源:https://docs.bigmodel.cn/api-reference/模型-api/对话补全
端点:`POST https://open.bigmodel.cn/api/paas/v4/chat/completions`(注意路径是 `/api/paas/v4/`,不是 `/v1/`
| 怪癖 | 详情 |
| --- | --- |
| **`do_sample`** | 布尔,默认 `true`。为 `false` 时走贪心解码,**`temperature` / `top_p` 全部失效**。OpenAI 无此参数——中转站收到 `temperature: 0` 时应考虑翻译成 `do_sample: false` 而不是直接透传 |
| **`temperature` 范围 `[0.0, 1.0]`** | 不是 OpenAI 的 0~2,超出需 clamp |
| **`top_p` 范围 `[0.01, 1.0]`,默认 0.95** | 下界不是 0,`top_p: 0` 会被拒 |
| **`finish_reason: "sensitive"`** | 内容被安全拦截。**不在 OpenAI 枚举内**,会打破严格客户端 |
| `thinking` | `{"type": "enabled"\|"disabled", "clear_thinking": bool}`,仅 GLM-4.5+ |
| `reasoning_content` | 思维链输出字段(DeepSeek 风格的事实标准) |
| 工具上限 | 最多 128 个 function,另支持 web_search / 知识库检索 / MCP 工具 |
| 鉴权 | 现行推荐 `Authorization: Bearer <api_key>`;**历史 JWT 方式仍支持** ⚠️——API Key 形如 `{id}.{secret}`,需按点号拆分后签发含 `api_key`/`exp`/`timestamp` 的 JWT |
默认模型为 `glm-5.2`。流式同样以 `data: [DONE]` 结束。
---
## 3. 月之暗面 KimiMoonshot)✅
> **已拆分为独立文件:[kimi.md](./kimi.md)**2026-08-13 全量重采集,**并用真实 API key 实测校验**)。本节仅保留跨厂商对比时用得上的摘要,细节以那边为准——尤其 §14 列出了 10 条被实测推翻的官方文档结论。
>
> ⚠️ **域名分层**:文档站/控制台 `platform.moonshot.cn` → `platform.kimi.com`301),但 **API 数据面仍是 `api.moonshot.cn`**,没有迁到 `api.kimi.com`。另有国际站 `platform.kimi.ai` / `api.moonshot.ai`**账号与 key 与国内站完全隔离**。
| 怪癖 | 详情 |
| --- | --- |
| **采样参数硬 400**(实测) | K3 / K2.7-code 把 `temperature`(1.0) / `top_p`(0.95) / `n`(1) / 两个 penalty 写死,**传其他值 400 而不是静默忽略**(实测 `invalid temperature: only 1 is allowed for this model`)。而 moonshot-v1 的 `temperature` 默认是 **0**、上限是 **1**OpenAI 是 2),且 **`n>1` 要求 temperature>0**(隐藏耦合,文档未提) |
| **同参数两入口行为相反**(实测) | 同一个 `temperature=0.7`OpenAI 入口 **400**,自家 Anthropic 入口 **200 静默忽略**。但 `tool_choice` 的限制两边一致——**是选择性的不一致**,不能"打不通就换入口" |
| **流式 `usage` 位置可控**(实测) | 不传 `stream_options.include_usage` 时藏在 **`choices[0].usage`**OpenAI SDK 的 `chunk.usage` 恒为 `None`,会漏计费);**传 `include_usage:true` 则回到顶层 + `choices:[]`,与 OpenAI 一致**。中转站补这个参数即可,无需改写响应 |
| **Partial Mode(前缀续写)** | 在 messages 末尾加 `{"role": "assistant", "content": "...", "partial": true}`,模型从该前缀继续。**字段名是 `partial`DeepSeek 叫 `prefix`** —— 同一能力三家三个名字(另见 Anthropic 的 prefill,且 Claude 4.6+ 已禁用) |
| **`n` 最多 5** | 且**仅 Moonshot V1 系列支持**,新模型固定为 1 |
| 思考模型分裂成两套参数 | K3 用顶层 `reasoning_effort``low`/`high`/`max`,默认最贵的 `max`);K2.6/K2.5 用 `thinking` 对象;K2.7-code 用 `thinking` 但只接受 `"enabled"`(实测关闭 → 400)。⚠️ **文档称 K3 恒开不可关,实测 `thinking:{"type":"disabled"}` 有效**`prompt_tokens` 88→21,思考模式带约 67 token 隐藏 system prompt)。⚠️ `reasoning_effort``"banana"` 也返回 200——**枚举校验形同虚设,非法值静默跑默认的 max 档** |
| `reasoning_content` 明文回传 | 明文(非 Anthropic 那种加密 signature)。⚠️ **文档称 K3/K2.7-code 多轮缺失会报错,实测不报错**200 正常继续) |
| `tool_choice` 与思考互斥 | 指定具体函数 + 思考开启 → **400 `tool_choice 'specified' is incompatible with thinking enabled`**(两个入口一致)。⚠️ **但关闭思考后再指定函数 → 200 且模型不调用工具、直接幻觉出假数据**——静默错误比 400 更危险,不要把关思考当变通方案 |
| tool_call id 可预测 | 格式是 **`{函数名}_{序号}`**(如 `get_weather_0`),不是 OpenAI 的随机串。多轮同名函数会**重复 id**,用它做去重键会误判 |
| 多模态**不收 http URL** | 只接受 base64 data URI 和 `ms://<file_id>`。与 OpenAI/Anthropic 的硬性差异 |
| 动态工具加载 | 工具可声明在 `{"role":"system","tools":[...]}`**无 `content` 字段**)里,位置决定可见轮次。仅 K3 支持,其他模型报 `tokenization failed` |
| `finish_reason` | `stop` / `length` / `tool_calls`,与 OpenAI 一致,**没有 `sensitive`**——内容安全走 400 `content_filter`,比多数国产厂商干净 |
| 错误体缺字段 + 三种格式 | OpenAI 入口只有 `error.type`/`error.message`**无 `code`/`param`**);Anthropic 入口多 `request_id` 和顶层 `type`**路径写错会掉到网关层**,返回 `{"code":5,"error":"url.not_found","message":"没找到对象","ua":"curl/8.7.1",...}`——连 `error` 字段的类型都从 object 变成 string |
| 三种 429 语义相反 | `engine_overloaded_error`(退避重试)/ `exceeded_current_quota_error`(欠费,重试无用)/ `rate_limit_reached_error`(降并发)。只看状态码的通用重试会在欠费时打爆上游 |
| **错误信息泄漏账号标识** | 429 的 message 原文含 `Your account org-xxx<ak-xxx> request reached organization max RPM: 3`——**org id 和 key 标识直接暴露**。响应头另有 `msh-org-id`/`msh-uid`/`msh-project-id`。中转站原样透传 = 把自己的账号广播给所有下游用户 |
| 无 `x-ratelimit-*` 头 | 只在 429 时给 `retry-after` / `x-retry-after` / `msh-cooldown-seconds`(同一信息重复三份)。**成功时无法得知剩余配额**,配额管理只能本地计数 |
| **两个产品两套栈** | `platform``api.moonshot.cn`key `sk-`)与 **Kimi For Coding**`api.kimi.com/coding`key `sk-kimi-`**key 互不通用**,模型名(`kimi-k3` vs `k3`)、Anthropic 入口路径、限流、错误语义、tool_call id 格式全不同。⚠️ **coding 侧几乎不校验 `model`**:传 `gpt-4`/空字符串都返回 200 并原样回显,只有带方括号的名字被拒(且返回 **401** 而非 404)。详见 [kimi.md](./kimi.md) §17 |
无状态 API,多轮需自行拼接历史。
**context caching 已查证(原"需另行查证"结论作废)**:早期的独立 cache 对象 API 已被移除,**现行是全自动前缀缓存**——无需创建或引用 cache id。⚠️ 文档称"prompt > 256 token 才命中"**实测 21 token 也完整命中**。命中量同时报在 `usage.cached_tokens`Kimi 私有)和 `usage.prompt_tokens_details.cached_tokens`(OpenAI 兼容,**中转站直接用后者即可**);未命中时字段不出现而非返回 0。K3 命中 ¥2/1M、未命中 ¥20/1M,**10 倍价差**意味着中转站任何对 prompt 前缀的改写都会带来 10 倍成本。
---
## 4. 字节 · 豆包 / 火山方舟(Ark)✅
> 来源:https://doubao.apifox.cn/265892759e0 、搜索结果
- Base URL`https://ark.cn-beijing.volces.com/api/v3`OpenAI SDK 直接可用。
- **`model` 字段的历史怪癖** ⚠️:早期必须先在控制台创建"推理接入点"拿到 **Endpoint ID(形如 `ep-2024...`** 填进 `model`,而不是模型名。现在也支持直接填模型名(如 `doubao-pro-32k-240615`)。
中转站的模型别名表必须允许"一个逻辑模型名 → 一个 ep-xxx 字符串"的映射,且**不同用户的 ep id 不同**(接入点是账号级资源)。这意味着模型路由表可能需要**按租户隔离**,而不是全局共享一张表。
- usage 中含缓存相关明细字段。
- ⚠️ 抓取到的 OpenAPI 规范页**未列出 thinking 参数和完整限制**,深度思考模型(doubao-1.5-thinking 等)的参数需另查。
---
## 5. 百度 · 文心 / 千帆 ⚠️
> 来源:https://ai.baidu.com/ai-doc/WENXINWORKSHOP/0m2vwrjwsv2 通告,✅)+ 搜索结果(v1 约束,⚠️)
百度是"新旧两代差异最大"的一家。
### v1(历史,仍在运行)的经典怪癖 ⚠️
- **每个模型一个独立 URL**,模型名在路径里,没有 `model` 参数。
- **鉴权用 `access_token` 放 query string**:先用 API Key + Secret Key 调 `grant_type=client_credentials` 换取。
- **`messages` 必须是奇数条**。
- **role 必须 user / assistant 严格交替**:奇数位是 `user`(或 `function`),偶数位是 `assistant`,且**第一条不能是 `function`**。
- `system` 是**独立顶层字段**,不能放进 messages(和 Anthropic 一样)。
这套约束意味着**任何带连续同角色消息的 OpenAI 请求都无法直通 v1**,中转站必须做消息合并(连续 user 合并成一条)。
### v2(现行,已 OpenAI 兼容)✅
- 端点统一为 `https://qianfan.baidubce.com/v2/chat/completions`,用 `model` 参数选模型。
- 鉴权改为 **IAM Bearer Token**`Authorization: Bearer bce-v3/ALTAK-...`,并需要额外的 `appid` header ⚠️。
- 官方称"完全兼容 OpenAI SDK"。
- v1 在过渡期内继续可用,TPM 配额在两个版本间共享。
**中转站建议直接对接 v2**,但要注意 v2 是否仍保留 messages 交替约束——通告未明确说明,需实测验证。
---
## 6. 腾讯混元 ✅
> 来源:https://cloud.tencent.com/document/product/1729/111007
两套接口差异极大:
### 原生接口
- **腾讯云 TC3-HMAC-SHA256 签名**(云厂商通用签名 v3),需要多步 HMAC 派生密钥(SecretDate → SecretService → SecretSigning)。POST 支持最大 10MB。
- ⚠️ 字段命名是 **PascalCase 大驼峰**`Messages` / `Role` / `Content`),不是 OpenAI 的 snake_case。这是腾讯云全平台 API 的统一风格。
### OpenAI 兼容接口(推荐)
- Base URL`https://api.hunyuan.cloud.tencent.com/v1`
- 直接用 API Key + `Authorization: Bearer`OpenAI SDK 无需改代码。
**中转站结论**:走兼容端点,不要碰 TC3 签名——那套签名实现成本高且容易在时钟偏移、URL 编码细节上出错。
---
## 7. MiniMax ✅
> 来源:https://platform.minimax.io/docs/api-reference/text-post
| 怪癖 | 详情 |
| --- | --- |
| **端点路径非标** | `https://api.minimax.io/v1/text/chatcompletion_v2`**不是** `/v1/chat/completions` |
| **HTTP 200 + `base_resp`** | 业务错误也返回 200,状态在 `base_resp.status_code`0 = 成功) |
| 业务状态码 | `1002` 限流 / `1004` 鉴权失败 / `1008` 余额不足 / `1039` token 超限 / `2013` 参数无效 |
| 内容安全字段 | `input_sensitive``output_sensitive``input_sensitive_type` / `output_sensitive_type`17 分类)、`mask_sensitive_info` |
| `temperature` 范围 | 01,非 OpenAI 的 02 |
| `top_p` 默认 | 0.95 |
| `response_format` | JSON schema **仅 MiniMax-Text-01 支持** |
| 特有响应字段 | `reasoning_content``audio_content``correct_rate`JSON schema 模式下的置信度) |
| usage | 单独统计 `reasoning_tokens` |
`base_resp` 这一条值得再强调:**限流不返回 429**,任何依赖 HTTP 状态码的重试/熔断中间件在 MiniMax 上都是失效的。
---
## 8. 讯飞星火 ✅
> 来源:https://www.xfyun.cn/doc/spark/ 系列页面
- **原生接口是 WebSocket,不是 HTTP** ✅。鉴权方式是用 APPID + APIKey + APISecret 做 HMAC-SHA256,把签名**拼进 WebSocket 连接 URL** 里。这在中转站架构里是完全异类——需要独立的连接管理、心跳、重连逻辑,无法复用 HTTP 连接池。
- **OpenAI 兼容 HTTP 端点**`https://spark-api-open.xf-yun.com/v1`,用 APIPassword 认证;最新的 Spark-X2 还支持 `AK:SK` 拼接格式 ⚠️。
**中转站结论**:只对接 OpenAI 兼容 HTTP 端点。WebSocket 原生接口的接入成本远高于收益,除非需要它独有的能力(如实时语音)。
---
## 9. 给中转站的设计清单
从上面这些怪癖倒推,网关需要的抽象层:
1. **双通道错误判定** — HTTP 状态码 + body 业务码(MiniMax `base_resp`)。不能假设 HTTP 语义可靠。
2. **可插拔签名器** — Bearer / JWT 自签 / query token / TC3-HMAC / WebSocket 签名 URL 至少五种。凭据模型要支持 (key, secret, appid, region) 而非单字符串。
3. **流式规范化层** — 统一处理"全量累积 vs 增量"(阿里 `incremental_output`)、`[DONE]` 有无、SSE header 要求(`X-DashScope-SSE`)。**这一层是国产接入的核心价值**。
4. **finish_reason 枚举映射**`sensitive``content_filter`,并用扩展字段保留原值,避免打破强类型客户端。
5. **非标参数透传白名单**`extra_body` 风格的旁路通道,而不是按 OpenAI schema 严格校验后丢弃。
6. **租户级模型路由表** — 火山方舟的 Endpoint ID 是账号级资源,模型别名映射不能全局共享。
7. **多地域 base_url** — 阿里按 region + workspace 分片,上游地址不能是常量。
8. **消息结构归一化** — 连续同角色消息合并(百度 v1 交替约束、Anthropic 单 system),作为通用前置处理。
---
## 10. 未覆盖 / 待验证
- **零一万物 Yi、百川 Baichuan、阶跃星辰 StepFun、商汤日日新** — 未收集。
- **火山方舟深度思考模型的 thinking 参数** — 官方 OpenAPI 页未列出,需另查。
- ~~**Kimi 的 context caching**~~ — ✅ 已查证(2026-08-13):早期的独立 cache 对象 API 已移除,现为全自动前缀缓存,见 [kimi.md](./kimi.md) §10。Kimi 的其余待验证项已转入 [kimi.md](./kimi.md) §14。
- **百度 v2 是否仍保留 messages 奇数/交替约束** — 通告未明说,**必须实测**。
- 所有标 ⚠️ 的条目 — 来自搜索结果聚合,建议用真实请求逐条验证后再固化进代码。