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

967 lines
65 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.
# Kimi API(月之暗面 Moonshot
> 来源(官方文档站 `.md` 原文,采集于 2026-08-13):
> - API 参考:[overview](https://platform.kimi.com/docs/api/overview.md)、[chat](https://platform.kimi.com/docs/api/chat.md)、[models-overview](https://platform.kimi.com/docs/api/models-overview.md)、[list-models](https://platform.kimi.com/docs/api/list-models.md)、[errors](https://platform.kimi.com/docs/api/errors.md)、[estimate](https://platform.kimi.com/docs/api/estimate.md)、[balance](https://platform.kimi.com/docs/api/balance.md)、[batch-create](https://platform.kimi.com/docs/api/batch-create.md)
> - 指南:[models](https://platform.kimi.com/docs/models.md)、[k3-quickstart](https://platform.kimi.com/docs/guide/kimi-k3-quickstart.md)、[k2.7-code-quickstart](https://platform.kimi.com/docs/guide/kimi-k2-7-code-quickstart.md)、[thinking](https://platform.kimi.com/docs/guide/use-thinking-models.md)、[reasoning-effort](https://platform.kimi.com/docs/guide/use-reasoning-effort.md)、[partial](https://platform.kimi.com/docs/guide/use-partial-mode-feature-of-kimi-api.md)、[caching](https://platform.kimi.com/docs/guide/use-context-caching-feature-of-kimi-api.md)、[tool-choice](https://platform.kimi.com/docs/guide/use-tool-choice.md)、[dynamic-tools](https://platform.kimi.com/docs/guide/use-dynamic-tool-loading.md)、[tool-calls](https://platform.kimi.com/docs/guide/use-kimi-api-to-complete-tool-calls.md)、[web-search](https://platform.kimi.com/docs/guide/use-web-search.md)、[official-tools](https://platform.kimi.com/docs/guide/use-official-tools.md)、[response_format](https://platform.kimi.com/docs/guide/response_format.md)、[streaming](https://platform.kimi.com/docs/guide/utilize-the-streaming-output-feature-of-kimi-api.md)、[vision](https://platform.kimi.com/docs/guide/use-kimi-vision-model.md)、[file-qa](https://platform.kimi.com/docs/guide/use-kimi-api-for-file-based-qa.md)、[batch](https://platform.kimi.com/docs/guide/use-batch-api.md)、[claude-code](https://platform.kimi.com/docs/guide/claude-code-kimi.md)、[troubleshooting](https://platform.kimi.com/docs/guide/troubleshooting.md)
> - 计价与限流:[limits](https://platform.kimi.com/docs/pricing/limits.md)、[chat-k3](https://platform.kimi.com/docs/pricing/chat-k3.md)
> - 完整索引:https://platform.kimi.com/docs/llms.txt OpenAPIhttps://platform.kimi.com/docs/openapi.json
>
> **2026-08-13 用真实 API key 实测校验过一遍**(约 60 次请求,覆盖两个入口)。实测与文档冲突处均以实测为准。
> 实测环境:中国站 `api.moonshot.cn`,账号档位 `msh-gid: free`Tier 03 RPM / 1 并发),流式 `system_fingerprint` = `fpv0_489ccb45`。
>
> **标注约定**:✅ 文档记载且实测确认;❌ **实测推翻文档**;🆕 文档未记载、实测发现;⚠️ 未实测或存疑。
Kimi 对外声称 **OpenAI 协议兼容**OpenAI SDK 换 `base_url` 即可用。但实测下来它是本项目已调研的几家里**"最容易在第一个请求就 400"** 的一家:
1. **新模型把采样参数写死并硬 400**——`temperature` 不是 1.0 直接报错,实测确认不是静默忽略(§3)。带默认 `temperature=0.7` 的 OpenAI 客户端在 K3 上开箱即挂。
2. **同一模型在两个入口行为相反**——OpenAI 入口对 `temperature=0.7` 报 400Anthropic 入口 200 静默忽略(§3.2)。
3. **参数集按模型分裂成四套**——合法请求体取决于 `model` 值(§3、§4)。
4. **文档与实现大面积脱节**——实测推翻了 8 条文档明确写下的规则,其中包括"K3 思考恒开不可关"这样的核心设定(§14)。**照文档写代码会同时踩到"文档说能实际不能"和"文档说不能实际能"两个方向。**
5. **它其实是两个产品**——`platform`(本文 §1–§16)和 **Kimi For Coding**`api.kimi.com/coding`,见 **§17**)是**两套独立的服务栈**:账号体系、模型名、鉴权、限流、端点、id 格式全都不同,key 互不通用。**中转站必须当作两个上游分别配置。**
---
## 1. 端点、域名与鉴权
### 1.1 双站隔离 ✅
| 产品 / 站点 | 控制台 | API base URL | Key 前缀 |
| --- | --- | --- | --- |
| platform 中国站 | `platform.kimi.com` | `https://api.moonshot.cn/v1` | `sk-` |
| platform 国际站 | `platform.kimi.ai` | `https://api.moonshot.ai/v1` | `sk-` |
| **Kimi For Coding**(另一个产品,见 §17) | — | `https://api.kimi.com/coding/v1` | **`sk-kimi-`** |
两站账号、余额、API Key 完全独立,拿错区域的 key 返回 401。中转站的上游配置必须把 key 和 base URL 绑成一对。
**Kimi For Coding 与 platform 的 key 双向不通用**(实测)——且两边的 401 文案不同,可用来判断打错了哪个上游:
```
coding key → api.moonshot.cn : {"message":"Invalid Authentication","type":"invalid_authentication_error"}
platform key → api.kimi.com/coding : {"message":"The API Key appears to be invalid or may have expired. Please verify your credentials and try again.","type":"invalid_authentication_error"}
```
⚠️ **域名分层容易记错**:文档站/控制台已迁到 `kimi.com``platform.moonshot.cn``platform.kimi.com` 301),但 **API 数据面仍是 `api.moonshot.cn` / `api.moonshot.ai`**,没有迁到 `api.kimi.com`
### 1.2 端点清单 ✅(均实测可用)
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| POST | `/v1/chat/completions` | 对话补全 |
| GET | `/v1/models` | 模型列表 |
| POST | `/v1/tokenizers/estimate-token-count` | 官方 token 计数 |
| GET | `/v1/users/me/balance` | 余额查询 |
| POST/GET/DELETE | `/v1/files``/v1/files/{id}``/v1/files/{id}/content` | 文件管理 |
| POST/GET | `/v1/batches``/v1/batches/{id}``/v1/batches/{id}/cancel` | 批处理 |
鉴权统一 `Authorization: Bearer <api_key>` ✅。**没有智谱那种 JWT 历史包袱**,这点比多数国产厂商干净。
🆕 **非 chat 端点带业务包装层**`/v1/users/me/balance``/v1/tokenizers/estimate-token-count` 返回的**不是**裸对象,而是
```json
{"code":0,"data":{"total_tokens":87},"scode":"0x0","status":true}
```
即 HTTP 200 外面还套了一层 `code`/`scode`/`status`。而 `/v1/chat/completions``/v1/models` **没有**这层包装(直接 OpenAI 结构)。**同一个 API 里两种响应信封并存**——中转站的响应解析器不能一刀切。(对比 `china-providers.md` §0 里 MiniMax `base_resp` 那种"HTTP 200 藏业务错误",Kimi 是同一思路的半吊子版本:只在辅助端点上有。)
🆕 **未记载的调试响应头**(每个响应都带):
```
msh-request-id: 7b768543-96db-11f1-8fa0-624fe5b4e31b
msh-org-id: org-xxxxxxxx
msh-project-id: proj-xxxxxxxx
msh-uid: xxxxxxxxxxxxxxxxxxxx
msh-gid: free ← 账号档位,可用来自动探测 Tier
msh-trace-mode: on
```
`msh-request-id` 是报障时官方要的 request_id。⚠️ 但 `msh-org-id` / `msh-uid` / `msh-project-id` 是**账号标识**——中转站若原样透传响应头给下游,等于把自己的组织 ID 广播出去。**应当在响应头白名单里剥掉 `msh-*`**`msh-request-id` 可保留或改名)。
### 1.3 Anthropic 兼容入口 ✅(实测可用,但与 OpenAI 入口行为不一致)
```
ANTHROPIC_BASE_URL="https://api.moonshot.cn/anthropic"
ANTHROPIC_AUTH_TOKEN="<kimi api key>" # 注意是 AUTH_TOKEN 不是 API_KEY
```
实测结论:
| 项 | 结果 |
| --- | --- |
| `POST /anthropic/v1/messages` + `x-api-key` | ✅ 200 |
| `POST /anthropic/v1/messages` + `Authorization: Bearer` | ✅ 200**两种鉴权都接受** |
| `POST /anthropic/messages`(漏 `v1`) | ❌ 404,且**掉到网关层**(见下) |
| 不带 `anthropic-version` 头 | 🆕 200**不强制** |
| `temperature: 0.7` | 🆕 **200 静默忽略**(OpenAI 入口同样请求是 400 |
| `top_p: 0.5` | 🆕 200 静默忽略 |
| `tool_choice: {"type":"tool","name":...}` | ✅ 400 `tool_choice 'specified' is incompatible with thinking enabled`(与 OpenAI 入口一致) |
| `thinking: {"type":"enabled","budget_tokens":2000}` | ✅ 200 接受 |
| `system` + `cache_control: {"type":"ephemeral"}` | 🆕 200 接受但 `cache_creation_input_tokens: 0`,**未实际建缓存**(自动缓存机制接管,见 §10) |
| `model: "kimi-k3[1m]"` | ❌ **404**(见下) |
> **两个入口对采样参数的处理相反,但对 `tool_choice` 的限制一致**——不是"Anthropic 入口更宽松",而是**选择性地不一致**。中转站不能假设"打不通就换个入口试试"。
🆕 **Anthropic 入口的 `usage` 同时并存两套字段**
```json
"usage": {
"input_tokens": 0, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 88,
"output_tokens": 151, "output_tokens_details": {"thinking_tokens": 135},
"service_tier": "standard", "inference_geo": "not_available",
"prompt_tokens": 88, "cached_tokens": 88, "completion_tokens": 151, "total_tokens": 239
}
```
Anthropic 风格(`input_tokens`/`cache_read_input_tokens`/`output_tokens`)和 OpenAI 风格(`prompt_tokens`/`cached_tokens`/`completion_tokens`)**同时存在**,且语义不同:缓存命中时 `input_tokens` 变成 **0**(Anthropic 语义:不含缓存部分),而 `prompt_tokens` 仍是 **88**(OpenAI 语义:含缓存部分)。**中转站按哪套字段计费,账单会差一个缓存量。**
🆕 **thinking block 没有 `signature` 字段**——Anthropic 官方的 thinking block 带加密签名,Kimi 这个是纯明文 `{"type":"thinking","thinking":"..."}`。好处是不用当 opaque blob 搬运,坏处是**下游若按 Anthropic 规范校验 signature 会失败**。
**`kimi-k3[1m]` 是错的**——官方 Claude Code 配置页把 `ANTHROPIC_MODEL` 等六个变量全设成 `kimi-k3[1m]`,但实测**两个入口都 404**
```
OpenAI 入口: {"error":{"message":"Not found the model kimi-k3[1m] or Permission denied","type":"resource_not_found_error"}}
Anthropic 入口: {"error":{"type":"resource_not_found_error","message":"Not found the model kimi-k3[1m] or Permission denied"},"request_id":"...","type":"error"}
```
`[1m]` 是 Claude Code 客户端侧的上下文变体后缀,Kimi 服务端并不认。**正确的模型名是 `kimi-k3`。** 官方文档这段配置照抄会直接失败。
🆕 **三种错误格式并存**(这是中转站错误处理的隐藏坑):
```jsonc
// 1. OpenAI 入口 API 级错误
{"error":{"message":"...","type":"invalid_request_error"}}
// 2. Anthropic 入口 API 级错误 —— 多了 request_id 和顶层 type
{"error":{"type":"resource_not_found_error","message":"..."},"request_id":"...","type":"error"}
// 3. 网关级错误(路径不存在时)—— 完全不同的结构,中文消息,且回显 UA 和 URL
{"code":5,"error":"url.not_found","message":"没找到对象","method":"POST","scode":"0x5","status":false,"ua":"curl/8.7.1","url":"/anthropic/messages"}
```
第三种连 `error` 字段的类型都变了(从 object 变成 string)。**中转站解析上游错误时若假设 `error` 恒为 object,遇到网关级错误会抛类型异常。**
✅ 已知能力缺口:该端点**不支持 WebFetch**Claude Code 里报 `temporarily unavailable`)。
---
## 2. 模型与能力矩阵
### 2.1 在售模型 ✅
| 模型 | 上下文(实测 `/v1/models` | 默认 max output | 思考 | 视觉 | 视频 |
| --- | --- | --- | --- | --- | --- |
| `kimi-k3` | 1,048,576 | 131,072(上限 1,048,576 | 恒开但**可关**(§4.1 | ✓ | ✓ |
| `kimi-k2.7-code` | 262,144 | 32,768 | **不可关** | ✓ | ✓ |
| `kimi-k2.7-code-highspeed` | 262,144 | 32,768 | **不可关** | ✓ | ✓ |
| `kimi-k2.6` | 262,144 | ⚠️ 未记载 | 可关 | ✓ | ✓ |
| `kimi-k2.5` | 262,144 | ⚠️ 未记载 | 可关 | ✓ | ✓ |
| `moonshot-v1-{8k,32k,128k}` | 8192/32768/131072 | ⚠️ 未记载 | 无 | ✗ | ✗ |
| `moonshot-v1-{8k,32k,128k}-vision-preview` | 同上 | ⚠️ 未记载 | 无 | ✓ | ✗ |
| 🆕 `moonshot-v1-auto` | 131072 | — | 无 | ✗ | ✗ |
🆕 **`moonshot-v1-auto` 文档完全没提**,但 `/v1/models` 里有且可调用。它是**自动路由模型**:请求 `moonshot-v1-auto`**响应的 `model` 字段返回 `moonshot-v1-8k`**(实测两次都是)。
> 这是 DeepSeek `deepseek-chat` → `deepseek-v4-flash` 静默映射的同类问题:**请求模型名 ≠ 响应模型名 ≠ 账单模型名**。中转站若按响应 `model` 字段记账,用户请求 auto 会看到一个自己没点过的模型名;若按请求名记账,又对不上实际计费。**建议两个都记。**
`kimi-k2.7-code-highspeed` 是同模型高速档(约 180 tok/s),能力相同——可当作同一逻辑模型的 QoS 变体。
⚠️ 文档提到 moonshot-v1 系列"8 月 31 日下线"但未给年份,而 `/v1/models` 仍在返回。需持续关注。
### 2.2 已下线模型:无别名兜底 ✅(实测确认)
```
kimi-k2 → 404 Not found the model kimi-k2 or Permission denied
kimi-latest → 404 Not found the model kimi-latest or Permission denied
```
与 DeepSeek 把旧名静默映射到新模型**相反**,Kimi 是直接 404。中转站若面向存量客户需自建旧名映射表,并显式告知用户(别学 DeepSeek 静默换模型导致账单对不上)。
### 2.3 `/v1/models` 的真实返回:比文档丰富得多 🆕
文档给的示例只有 7 个字段,实测返回:
```json
{
"id": "kimi-k3", "object": "model", "created": 1786362236, "owned_by": "moonshot",
"permission": [{"created":0,"id":"","object":"","organization":"moonshot","group":"moonshot","is_blocking":false}],
"root": "", "parent": "",
"supports_image_in": true, "supports_video_in": true, "supports_reasoning": true,
"supports_dynamic_tools": true,
"think_efforts": {"support": true, "valid_efforts": ["low","high","max"], "default_effort": "max"},
"reasoning_efforts": {"support": true, "valid_efforts": ["low","high","max"], "default_effort": "max"},
"supports_thinking_type": "only",
"context_length": 1048576
}
```
- `permission[]` / `root` / `parent`**OpenAI 早期 `/models` 的遗留字段**(现已从 OpenAI 移除),Kimi 还留着,且内容全是空壳。
- 🆕 `think_efforts``reasoning_efforts` **内容完全相同的两个字段**——显然是改名过程中的兼容遗留,两个都在返回。
- 🆕 `supports_dynamic_tools`(仅 K3 为 true)、`supports_thinking_type: "only"`(仅 K3 有此字段)——**这两个字段是判断能力的最可靠来源**,比文档准。
- ❌ 文档示例里 `kimi-k2.5``context_length` 写的是 `128000`,实测是 **262144**
> 这是少见的"上游自带能力矩阵",中转站可以自动同步能力表而非硬编码。但注意 `supports_thinking_type: "only"` 的值是字符串 `"only"` 而非布尔,且**只有 K3 有这个字段**——解析时要容忍字段缺失。
---
## 3. 采样参数:新模型写死且**硬 400**(实测确认,本文件最大的坑)
### 3.1 OpenAI 入口:逐个报错 ✅
实测逐参数验证,**全部是 400,不是静默忽略**:
| 请求 | 实际响应 |
| --- | --- |
| `kimi-k3` + `temperature: 0.7` | `400 invalid temperature: only 1 is allowed for this model` |
| `kimi-k3` + `top_p: 0.5` | `400 invalid top_p: only 0.95 is allowed for this model` |
| `kimi-k3` + `n: 2` | `400 invalid n: only 1 is allowed for this model` |
| `kimi-k3` + `presence_penalty: 1` | `400 invalid presence_penalty: only 0 is allowed for this model` |
| `kimi-k2.7-code` + `temperature: 0.7` | `400 invalid temperature: only 1 is allowed for this model` |
| `moonshot-v1-8k` + `temperature: 1.5` | `400 Invalid request: temperature must not be greater than 1.000000` |
| `moonshot-v1-8k` + `temperature: 0.7` | ✅ 200 |
| 参数 | `moonshot-v1` | `kimi-k3` / `kimi-k2.7-code` |
| --- | --- | --- |
| `temperature` | 01,默认 **0** | 固定 `1.0`**传其他值 400** |
| `top_p` | 01,默认 1 | 固定 `0.95`**传其他值 400** |
| `n` | 15,默认 1 | 固定 `1`**传其他值 400** |
| `presence_penalty` / `frequency_penalty` | 22,默认 0 | 固定 `0`**传其他值 400** |
**三条独立的怪癖:**
1. **`temperature` 默认值跨代反转**moonshot-v1 默认 **0**,新模型固定 **1.0**。"同一家 API,不传 temperature 时的行为随模型跳变"。中转站若为对齐 OpenAI 而主动补默认 `temperature`,在 v1 上改变原有行为,在 K3 上直接 400。**结论:绝不给未传的参数补默认值。**
2. **`temperature` 上限是 1 而非 2**OpenAI 是 2)。
3. 🆕 **`n > 1``temperature` 存在隐藏耦合**——在 moonshot-v1 上传 `n: 3` 而不动 temperature
```
400 Invalid request: n should not be greater than 1 when temperature is less than or equal to 1e-5
```
因为 v1 的 temperature 默认是 0**要用 `n > 1` 就必须同时把 temperature 设为 > 0**。文档从未提及这个约束。中转站转发 `n>1` 请求时得连带检查 temperature,否则报出的错会让用户完全摸不着头脑。
✅ 其他约束实测确认:`stop` 数组最多 5 个——传 6 个报 `400 stop array too long. Expected an array with maximum length 5, but got an array with length 6 instead`(错误信息质量不错)。
🆕 **未知参数静默忽略**:传 `"foo_bar": 123` → 200 正常返回。即"未知字段宽松、已知字段严格"——这与多数厂商一致,但和它对已知参数的严苛形成反差。
### 3.2 Anthropic 入口:同样的参数被静默忽略 🆕
```
OpenAI 入口 kimi-k3 + temperature=0.7 → 400
Anthropic 入口 kimi-k3 + temperature=0.7 → 200(正常返回,参数被忽略)
Anthropic 入口 kimi-k3 + top_p=0.5 → 200
```
**同一个模型、同一个参数、两个入口结果相反。** 这是 DeepSeek "三入口行为不一致"的翻版(见 `deepseek.md`)。
> 中转站落地建议:为 Kimi 上游维护**按模型的参数白名单**,对新模型剥离 `temperature/top_p/n/presence_penalty/frequency_penalty` 后再转发。**这是本文件最该优先实现的一条规则**——它同时解决了两个入口的差异(剥离后两边行为一致)。
### 3.3 `max_tokens` vs `max_completion_tokens` ✅(实测厘清)
两个名字都能用。同时传时 **`max_completion_tokens` 优先**
```
{"max_tokens": 1500, "max_completion_tokens": 30} → completion_tokens: 30, finish_reason: "length"
```
文档的参数表只列 `max_completion_tokens`,正文却反复用 `max_tokens`,两者是新旧别名关系(文档未声明)。中转站两个都要能发,且注意优先级。
---
## 4. 思考模型:文档与实现脱节最严重的一节
| 模型 | 开关参数 | 能否关闭 | 实测 |
| --- | --- | --- | --- |
| `kimi-k3` | `reasoning_effort``low`/`high`/`max`,默认 `max`**和** `thinking.type` | ❌ **文档说不能,实测能** | 见 §4.1 |
| `kimi-k2.7-code` | `thinking.type` | ✅ 确实不能 | `400 invalid thinking: only type=enabled is allowed for this model` |
| `kimi-k2.6` | `thinking.type``enabled`/`disabled` | ✅ 能 | 200`reasoning_content: ""` |
| `kimi-k2.5` | `thinking.type` | ⚠️ 未测 | — |
### 4.1 ❌ K3 的思考**可以**关闭(推翻文档)
文档(K3 quickstart、thinking 指南、troubleshooting 三处)都说"K3 始终开启思考模式,不可关闭,只能调 `reasoning_effort`"。实测:
```jsonc
// 请求
{"model":"kimi-k3","messages":[{"role":"user","content":"说:好"}],"max_tokens":1500,"thinking":{"type":"disabled"}}
// 响应 200
{"choices":[{"message":{"role":"assistant","content":"好"},"finish_reason":"stop"}],
"usage":{"prompt_tokens":21,"completion_tokens":11,"total_tokens":32}}
```
- **没有 `reasoning_content` 字段**(不是空字符串,是字段不存在)。
- **`prompt_tokens` 从 88 掉到 21**。同一条 user 消息,思考开启时 88、关闭时 21——差值约 67 token 是**思考模式注入的隐藏 system prompt**。这也解释了为什么 `estimate-token-count` 对四个汉字的消息返回 87:**官方 tokenizer 把这段隐藏 prompt 也算进去了**。
🆕 **`thinking.disabled` 优先于 `reasoning_effort`**:两个同时传(`thinking:{"type":"disabled"}` + `reasoning_effort:"max"`)→ 思考仍然关闭,`prompt_tokens: 21`。
> 对中转站的影响是正面的:**"关闭思考"这个语义在 K3 上是可表达的**,能力降级表里不必再写"无法关闭"。但因为文档明说不行,这条**属于未文档化行为,随时可能变**——建议在实现里做成可开关的特性,并在上游探测时验证。
### 4.2 ❌ `reasoning_effort` 的枚举校验形同虚设(推翻文档)
`/v1/models` 明确声明 `valid_efforts: ["low","high","max"]`,但:
```
reasoning_effort: "medium" → 200 正常思考(OpenAI 的枚举值)
reasoning_effort: "banana" → 200 正常思考(完全非法的值)
```
**任意字符串都被接受并静默忽略。** 好消息是中转站不必做 `minimal`/`medium` → `low`/`high` 的枚举映射(不会 400);坏消息是**用户传错值不会有任何反馈**,只会默默跑在默认的 `max` 档(最贵)。中转站若要给用户可预期的成本,应当自己校验枚举并拒绝非法值。
🆕 `kimi-k2.6` 传 `reasoning_effort: "low"` → 200,但仍正常思考(`reasoning_tokens: 151`)——同样是被忽略。
### 4.3 `reasoning_content` 的回传要求 ✅ 部分推翻
- 响应里思考内容在 `choices[].message.reasoning_content`**明文**,无签名)。
- ✅ 流式中 `reasoning_content` **确实先于 `content` 出现**(实测 186 个 chunk 全是 reasoning_content 后才出 content)。`content` 开始即代表思考结束,是可靠的状态机切换信号。
- ✅ `reasoning_content` 计入 `completion_tokens`,并单独报在 `completion_tokens_details.reasoning_tokens`。
- ❌ **文档说"K3/K2.7-code 多轮缺少 `reasoning_content` 会报错"——实测不报错**:
```
多轮 assistant 消息只带 content + tool_calls、不带 reasoning_content → 200 正常继续
```
中转站不必强制存档回放 `reasoning_content`(虽然存了对模型质量更好)。这条推翻降低了实现负担——**但也意味着 Preserved Thinking 的实际语义比文档描述的松**,⚠️ 建议在长对话+多步工具链场景再验一次。
### 4.4 `max_tokens` 太小导致空 `content` ✅(实测确认)
```jsonc
{"model":"kimi-k3","max_completion_tokens":30,...}
→ 200 {"message":{"content":"","reasoning_content":"The user asked..."},"finish_reason":"length"}
```
token 全烧在思考上,`content` 为空字符串。官方建议 `max_tokens >= 16000`。
> **"200 OK + 空 content"是 Kimi 的正常返回,不能当上游故障重试**——重试只会再烧一遍思考 token。应原样透传让下游看到 `finish_reason: "length"`。
---
## 5. Partial Mode(前缀续写)✅
```json
{"role": "assistant", "content": "[\"红\",", "partial": true, "name": "可选"}
```
实测 200,返回 `" \"绿\", \"蓝\"]"`——**响应不含前缀**,需客户端自己拼。
| 维度 | Kimi | DeepSeek | Anthropic |
| --- | --- | --- | --- |
| 字段名 | **`partial: true`** | `prefix: true` | 无字段,末条 assistant 即前缀 |
| 端点 | 正式端点 | 需 `/beta` | 正式端点(Claude 4.6+ 已禁用) |
**同一能力三家三个名字**——中转站 IR 里应有单一 `prefill` 标志(见 `api-design.md`),落地时翻译成对应字段。
❌ **`partial` + `response_format: json_object` 不冲突**(推翻 `chat.md` 里那条警告):
```
{"partial":true,"content":"{"} + response_format:{"type":"json_object"} → 200,正常续写出完整 JSON
```
这是实践中最常用的组合(前缀写 `{` 强制 JSON 开头),可以放心用。
⚠️ `name` 字段是 Kimi 独有语义:OpenAI 的 `name` 是"多说话人区分"Kimi 在 partial 场景当"人设锚点"。转发时不要想当然丢弃。
⚠️ 与思考叠加的陷阱(文档提示,未实测):K3 思考开启时若 `max_tokens` 太小,会在思考阶段截断导致 `content` 为空,**下一次续写等于从零重来**。
---
## 6. 工具调用
### 6.1 与 OpenAI 的差异 ✅/❌
`tools[] / tool_calls[] / role:"tool"` 结构、流式 `delta.tool_calls[].index` 累积规则均与 OpenAI 相同。差异:
1. ❌ **`role: "tool"` 消息的 `name` 字段实际可选**(推翻文档)。文档说"必须提供 `tool_call_id` 和 `name`,Kimi 才能正确匹配",实测只给 `tool_call_id` 不给 `name` → **200 正常**。中转站从 Anthropic 入口转换时不必回查函数名(Anthropic 的 `tool_result` 只有 `tool_use_id`)。
2. ✅ **`tool_call_id` 必须严格匹配**:传一个不存在的 id →
```
400 Invalid request: tool_call_id is not found
```
🆕 注意错误信息里 `tool_call_id` 和 `is not found` 之间是**两个空格**——本该填 id 值的位置是空的,是个字符串拼接 bug。**中转站不要指望从这条错误信息里解析出是哪个 id 出的问题。**
3. ✅ `finish_reason: "tool_calls"` 时 `content` 可能非空(Kimi 会解释为什么调用)。按"有 tool_calls 就忽略 content"写的下游会丢内容。
4. 🆕 **`tool_calls[].id` 不是随机串,而是 `{函数名}_{序号}`**
```json
{"index":0,"id":"get_weather_0","type":"function","function":{"name":"get_weather",...}}
```
OpenAI 是 `call_abc123...` 的不透明随机串。这个格式**可预测且与函数名耦合**。风险有两层:中转站若按 id 反查函数名会"碰巧能用",形成隐式依赖,换上游即崩;且多轮对话里**同名函数会产生重复 id**(第二轮又是 `get_weather_0`),若中转站用 id 做全局去重键会误判。**建议中转站重写 tool_call id 为自己的不透明 id 并维护映射表。**
5. ✅ `finish_reason` 枚举为 `stop` / `length` / `tool_calls`**没有国产厂商常见的 `sensitive`**——内容安全走 400 `content_filter`,不污染枚举。
### 6.2 `tool_choice` 与思考的互斥 ✅,但有一个危险的例外 🆕
| 模型 / 条件 | `required` | 具体函数 |
| --- | --- | --- |
| `kimi-k3`(思考开) | ✅ **200 正常工作** | ❌ 400 `tool_choice 'specified' is incompatible with thinking enabled` |
| `kimi-k3`(思考关) | ⚠️ 未测 | 🆕 **200,但见下** |
| `kimi-k2.7-code`(思考不可关) | ❌ 400 `tool_choice 'required' is incompatible with thinking enabled` | ❌ 400 |
| `kimi-k2.6`(思考关) | ⚠️ 未测 | ✅ 200 正常调用工具 |
| Anthropic 入口 `kimi-k3` | ⚠️ 未测 | ❌ 400(同 OpenAI 入口) |
- ✅ K3 思考开启时 `required` **可用**(文档未明说,实测正常返回 tool_calls);但 K2.7-code 的 `required` **报错**,错误信息是"与思考不兼容"。同样是"思考开启",两个模型对 `required` 的判定不同。
- 🆕 **最危险的一条**:K3 关闭思考 + `tool_choice` 指定函数 → **HTTP 200,但模型没有调用工具,而是直接编造了结果**:
```jsonc
// 请求:tool_choice 强制 get_weatherthinking disabled
// 响应 200,无 tool_callsfinish_reason: "stop"
{"content":"北京现在的天气情况如下:\n- **天气**:晴\n- **温度**28°C\n- **湿度**45%\n- **风向**:南风..."}
```
**`tool_choice` 被静默无视,模型直接幻觉出一份完整的天气数据。** 这比 400 危险得多——下游代码等着解析 `tool_calls`,拿到的却是一段看起来很真的假数据。**中转站不应把"关闭思考"作为绕过 `tool_choice` 限制的变通方案**,那样只是把显式失败变成静默错误。
> 结论:思考开启时无法强制指定函数,这个缺口**无法用参数改写安全绕过**。能力降级表建议**显式报错**而非降级。
### 6.3 动态工具加载(K3 独有)✅ 实测确认
```json
{"role": "system", "tools": [{"type":"function","function":{...}}]}
```
- 这条 system 消息**没有 `content` 字段**——OpenAI 协议里不存在的消息形状。实测 K3 上 200 正常,模型正确调用了工具。
- ✅ `kimi-k2.6` 上 → `400 Invalid request: tokenization failed`(一个极不直观的报错,实测确认)。
- 消息位置决定工具从哪轮起可见;与顶层 `tools` 可共存。
- 官方指出顶层 `tools` 不影响前缀缓存,动态工具因进了消息历史所以会影响。
> 中转站风险:这条消息**无法用 OpenAI/Anthropic 语义表达**。若做严格消息 schema 校验("system 必须有 content")会把合法请求拒掉。**IR 里要给消息保留 passthrough 扩展字段,别做白名单式裁剪。**
### 6.4 内置工具:两套并存 ✅/⚠️
**a`builtin_function` 协议**(实测确认):
```json
{"type": "builtin_function", "function": {"name": "$web_search"}}
```
实测响应:
```json
{"tool_calls":[{"id":"t-web_search-6a7d5fde","type":"builtin_function",
"function":{"name":"$web_search","arguments":"{\"search_result\":{\"search_id\":\"dda31b08...\"},\"usage\":{\"total_tokens\":6674}}"}}]}
```
- 🆕 `tool_calls[].type` 回传的是 **`builtin_function`**,不是 OpenAI 枚举里的 `function`。**下游若按 OpenAI 枚举做校验会拒掉这个响应。**
- 🆕 id 格式又是另一套:`t-web_search-{短hash}`(与普通工具的 `get_weather_0` 不同)。
- ✅ 反直觉之处确认:**模型内部已执行搜索**(`arguments` 里直接带 `search_id` 和 `usage.total_tokens: 6674`),但客户端仍要把 `arguments` **原样**塞回 `role: "tool"` 消息再发一次。即"假装自己执行了"。
- 🆕 注意首轮的 `usage.total_tokens` 只有 93,而 arguments 内嵌的 `usage.total_tokens` 是 **6674**——**搜索消耗的 token 不在响应的 usage 里**,要从 arguments 里解析。中转站按响应 usage 计费会漏掉绝大部分搜索成本。
- ✅ 计费:每次搜索按次收费,搜索结果计入下一轮 `prompt_tokens`。
**b)官方工具(Formula API)** ⚠️ 未实测 —— 另一套体系,12 个工具:`convert`、`web-search`、`rethink`、`random-choice`、`mew`、`memory`、`excel`、`date`、`base64`、`fetch`、`quickjs`、`code-runner`。走标准 `function` 协议 + Formula URI`moonshot/{tool-name}:latest`),搜索类返回 `encrypted_output``----MOONSHOT ENCRYPTED BEGIN----...`)需原样回传。
⚠️ **`web-search`(连字符,官方工具)与 `$web_search`(美元符号,builtin_function)是两个不同的东西**,文档没交代关系。已实测的是后者。
> 中转站建议:内置工具**不要跨厂商翻译**(OpenAI `web_search_preview`、Anthropic `web_search_20250305`、Kimi `$web_search` 的执行模型和计费都不同),按 opaque 透传,并在文档标明"内置工具与上游绑定"。
---
## 7. 结构化输出 ✅(文档为准,部分实测)
`response_format` 支持 `text` / `json_object` / `json_schema`。
```json
{"type": "json_schema", "json_schema": {"name": "...", "strict": true, "schema": {...}}}
```
- ✅ `json_object` 实测可用,且可与 partial 叠加(§5)。
- ⚠️ `strict: true` 走 token 级约束解码,要求 schema 符合 **MFJSMoonshot Flavored JSON Schema**——**自造方言**,与 OpenAI Structured Outputs 子集不是同一套。文档未给完整规则页。**未实测。**
- ⚠️ 文档罕见坦诚地承认支持度**按模型分层**:`kimi-k2.7-code` 最全(`anyOf`/`oneOf`/`$ref`/`additionalProperties`);`kimi-k3` 稳定;**`kimi-k2.6` 复杂 schema 不稳定——`$ref` 可能返回 Markdown 代码块、`oneOf` 可能被忽略**。
- ✅ JSON Mode 只保证生成 JSON **Object**(不会返回顶层数组/标量)。
> 建议:`json_schema` **不做跨家 schema 转换**,透传 + 记录失败率。K2.6 可能吐 Markdown 围栏意味着"保证返回合法 JSON"的承诺需要中转站自己剥围栏——那属于改写响应体,需明确取舍。
---
## 8. 流式输出:`usage` 位置**由 `include_usage` 决定** ❌(推翻文档)
文档说 usage "只出现在最后一个 chunk 的 `choices[0].usage`OpenAI SDK 的 `chunk.usage` 返回 None"。实测发现**两种模式并存,取决于是否传 `stream_options.include_usage`**
**不传 `include_usage`**Kimi 私有行为):
```jsonc
// 最后一个 chunk —— usage 嵌在 choices[0] 里,与 finish_reason 同层
data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop",
"usage":{"prompt_tokens":88,"completion_tokens":211,"total_tokens":299,
"completion_tokens_details":{"reasoning_tokens":186}}}],"system_fingerprint":"fpv0_489ccb45"}
data: [DONE]
```
**传 `stream_options: {"include_usage": true}`**(与 OpenAI 完全一致):
```jsonc
// 独立的最后一个 chunk —— choices 为空数组,usage 在顶层
data: {"id":"...","object":"chat.completion.chunk","choices":[],
"usage":{"prompt_tokens":88,"completion_tokens":182,"total_tokens":270,"cached_tokens":88,
"completion_tokens_details":{"reasoning_tokens":166},"prompt_tokens_details":{"cached_tokens":88}}}
data: [DONE]
```
> **中转站结论(比文档的推论简单得多):总是显式传 `stream_options.include_usage = true`**,就能拿到 OpenAI 标准位置的 usage,不必改写响应体。代价从"改写响应"降为"改写请求"。
>
> 两个附带差异:① 不传时**也有** usage(OpenAI 是不传就完全没有),所以"不传就没计费数据"的假设在 Kimi 上不成立;② **只有传了 `include_usage` 的那个 chunk 才带 `cached_tokens`**——不传时的 `choices[0].usage` 里没有缓存字段,会漏掉缓存计费信息。
其余流式行为 ✅ 与 OpenAI 一致:`data:` 前缀、`chat.completion.chunk`、**增量** delta(不是阿里那种全量累积)、`role` 只在首 chunk、中途有 `"delta":{}` 空 chunk、`data: [DONE]` 收尾。官方要求**以 `[DONE]` 判断结束而非 `finish_reason`**。
🆕 每个 chunk 都带 `system_fingerprint`(如 `fpv0_489ccb45`),**非流式响应里没有这个字段**——两种模式的响应字段集不一致。
### 自动断线重连 ⚠️
官方那篇《自动断线重连》**只是应用层 try/except 重试循环**(最多 100 次、间隔 1s),**没有任何协议级续传**——没有断点 offset,没有 resume id,重连就是整个请求重发。标题容易让人误以为有 resumable stream。中转站不要指望它做流式续传。
---
## 9. 多模态:**不支持 http(s) 图片 URL** ✅(实测确认)
```
{"type":"image_url","image_url":{"url":"https://statics.moonshot.cn/.../logo.png"}}
→ 400 Invalid request: unsupported image url: https://statics.moonshot.cn/.../logo.png
```
注意这个 URL 还是 Kimi 自家域名的图片,同样被拒。只接受两种形式:
- base64 data URI`data:image/png;base64,...`
- **Kimi 自有文件协议:`ms://<file_id>`**(先经 `/v1/files` 上传)
> 与 OpenAI(接受公网 URL)和 Anthropic`source.type: "url"`)的硬性差异。中转站要么替用户下载转 base64(引入出网、体积、超时、**SSRF** 风险),要么显式报错。**建议默认报错,把"自动下载转码"做成可选开关**——代下载会让中转站变成任意 URL 的抓取代理。
其他 ✅(文档,未逐条实测):
- `image_url` 既接受 `{"url": "..."}` 对象也接受**直接字符串**,比 OpenAI 宽松。
- 请求体上限 **100MB**;图片张数无硬限,受体积约束。建议图片 ≤4K、视频 ≤1080p。
- **`content` 必须是真数组,不能是序列化后的 JSON 字符串**——文档特意警告"序列化成字符串后各模型行为不可预测"。中转站做消息归一化时若曾把 content 压成字符串(OpenAI 单文本场景的常见优化),多模态消息上必须禁用。
- 视频输入(`video_url`)仅 K2.5/K2.6/K2.7-code/K3 支持——**OpenAI 协议里没有的模态**,跨家映射无对应物。
---
## 10. Context Caching:全自动 ✅,但 256 token 阈值 ❌
⚠️ **历史怪癖已消失**:Kimi 早期的显式 Cache 对象 API 已被移除,现行是**全自动前缀缓存**,无需创建或引用 cache id。(`china-providers.md` §3 原先标注的"需查证"由此作废。)
❌ **文档说"prompt tokens > 256 才能命中前缀缓存"——实测不成立**:
```
prompt_tokens: 88 → cached_tokens: 88 (命中)
prompt_tokens: 21 → cached_tokens: 21 (命中,仅 21 token
```
21 token 的请求也完整命中。实际阈值远低于 256,或该限制已取消。
🆕 **`cached_tokens` 在响应里出现两次**(同一个值):
```json
"usage": {"prompt_tokens":88, "completion_tokens":211, "total_tokens":299,
"cached_tokens": 88, // 扁平(Kimi 私有)
"prompt_tokens_details": {"cached_tokens": 88}} // 嵌套(OpenAI 兼容)
```
文档只提到扁平那个。**中转站直接用 `prompt_tokens_details.cached_tokens` 即可对齐 OpenAI,不需要做字段映射**(比文档描述的情况好)。同理输出侧有 `completion_tokens_details.reasoning_tokens`。
⚠️ 但注意**首次请求的响应里完全没有缓存字段**(未命中时字段不出现,而非返回 0)——解析时要容忍缺失。
✅ 生命周期由系统管理,TTL 未公开。✅ 计价差异极大(K3):**命中 ¥2 / 1M,未命中 ¥20 / 1M**,输出 ¥100 / 1M。
> **中转站影响(被低估的一条)**:10 倍输入价差意味着"保持前缀稳定"是硬性成本要求。任何对 prompt 前缀的改写——注入 system 提示、重排消息、规范化空白、动态插入时间戳——都会让缓存失效,成本瞬间 10 倍。**规则:对 Kimi 上游的请求,前缀必须逐字节稳定。** 同理动态工具进消息历史会影响缓存,顶层 `tools` 不会(§6.3)。
🆕 **`prompt_cache_key` 实测被接受**(200,不报错),但因为缓存本来就自动命中,无法从外部观测它是否真的分裂了缓存命名空间。⚠️ 语义仍未知,文档的 Context Caching 指南完全没提这个参数。
🆕 **Anthropic 入口的 `cache_control: {"type":"ephemeral"}` 被接受但不生效**:返回 `cache_creation_input_tokens: 0`。自动缓存机制接管了,显式缓存断点是空操作。
---
## 11. 文件、Batch 与辅助端点
### 11.1 文件问答 ✅(文档,未实测)
流程是"上传 → 抽取 → **把抽取出的文本当作 system 消息塞进 messages**",官方强调放的是**文件内容而不是 file id**。模型侧没有"引用 file id 做问答"的通道(`ms://` 只用于多模态图片/视频块)。
- 单用户最多 **1000 个文件**,单文件 **100MB**,空文件报错。
- ⚠️ **`purpose` 取值文档自相矛盾**:错误码页说"仅接受 `file-extract`"batch 创建页要求 `purpose="batch"`。至少两个合法值。
- 抽取行为:文本原样抽;图片走 OCR(**无文字则失败**);PDF 直接抽文本,纯图 PDF 走 OCR。
- 文件类 API **限时免费**。
### 11.2 Batch API ✅(文档,未实测)
- `POST /v1/batches`,字段 `input_file_id` / `endpoint`(仅 `/v1/chat/completions`/ `completion_window` / `metadata`(≤16 对,key ≤64 字符,value ≤512 字符)。
- ⚠️ `completion_window` 取值两处不一致:API 页说"最小 12h 最大 7d",指南页说 `24h`/`3d`/`7d`。
- **省 40%** 推理费。**仅支持 `kimi-k2.6` / `kimi-k2.5`,不支持 `kimi-k3`**。
- batch 请求体里**不得包含** `temperature` / `top_p` / `n` / 两个 penalty——§3 那条规则在 batch 上变成"不许出现"而非"必须等于固定值"。
- JSONL 每行需 `custom_id`(文件内唯一)/ `method`(固定 POST/ `url`(固定 `/v1/chat/completions`/ `body`,文件 ≤100MB。
- 支持多模态 batchbase64 或 `ms://`)。
### 11.3 官方 tokenizer ✅(实测)
```
POST /v1/tokenizers/estimate-token-count
{"model":"kimi-k3","messages":[{"role":"user","content":"你好世界"}]}
→ {"code":0,"data":{"total_tokens":87},"scode":"0x0","status":true}
```
🆕 **四个汉字算出 87 token**——因为它把 §4.1 那段思考模式的隐藏 system prompt(约 67 token)也算进去了,与实际 chat 请求的 `prompt_tokens: 88` 基本吻合。
> 这其实是好事:**官方 tokenizer 的口径与计费口径一致**,中转站做成本预估/限流应当用它,而不是套 tiktoken(Kimi 不是 OpenAI 词表)。但要知道它包含隐藏开销,别拿它的结果去和"用户可见文本长度"做对比,会显得离谱。代价是每次预估多一次网络往返。
### 11.4 余额 ✅(实测)
```
GET /v1/users/me/balance
→ {"code":0,"data":{"available_balance":15,"voucher_balance":15,"cash_balance":0},"scode":"0x0","status":true}
```
`cash_balance` **可为负数表示欠费**`available_balance <= 0` 时无法调用推理 API。可作上游健康检查与余额预警(对应 DeepSeek 的 `/user/balance`,路径和字段名都不同)。
❌ **文档说"K3 需最低充值 ¥10 解锁,注册代金券不解锁 K3"——不成立**:实测账号 `cash_balance: 0`、余额全是代金券,K3 调用完全正常。
---
## 12. 错误码与限流
### 12.1 错误体 ✅/🆕
OpenAI 入口:`{"error": {"message": "...", "type": "..."}}`——**没有 OpenAI 的 `code` / `param` 字段**。中转站若要输出严格 OpenAI 错误结构需补 `code: null` / `param: null`。
另有 Anthropic 入口格式和网关格式,共三种,见 §1.3。
### 12.2 错误码表 ✅(打勾项为实测触发)
| HTTP | `type` | 触发 |
| --- | --- | --- |
| 400 | `invalid_request_error` | ✅ 参数越界/固定值不符/schema 错/`tokenization failed`/`unsupported image url`/`tool_call_id is not found` |
| 400 | `content_filter` | 内容安全拦截 |
| 401 | `invalid_authentication_error` / `incorrect_api_key_error` | 鉴权头格式错 / key 无效(**含拿错区域的 key** |
| 403 | `permission_denied_error` | API 未开通 / 越权 / IP 不在组织白名单 |
| 404 | `resource_not_found_error` | ✅ 模型不存在(下线模型、`kimi-k3[1m]` |
| 429 | `engine_overloaded_error` | ✅ **上游过载**——退避重试 |
| 429 | `exceeded_current_quota_error` | **余额不足/欠费**——重试无用 |
| 429 | `rate_limit_reached_error` | ✅ **并发/RPM/TPM/TPD 超限**——降频 |
| 499 | `client_closed_request` | 客户端提前断开 |
| 500 | `server_error` / `unexpected_output` | 服务端错误 |
| 503 | `server_unavailable` | 扩缩容/维护 |
| 504 | — | **900 秒无响应**;官方建议长请求开 `stream: true` |
**429 必须按 `type` 分流** ✅——三种 429 处置完全相反:过载要退避、欠费重试纯属浪费、限流要降并发。只看状态码的通用重试逻辑在欠费时会打爆上游。实测两种 429 都遇到了,`engine_overloaded_error` 在正常低频调用下也会偶发。
🆕 **429 的错误信息泄漏账号标识**:
```
Your account org-xxxxxxxx<ak-xxxxxxxxxxxxxxxx> request reached organization max RPM: 3, please try again after 1 seconds
```
**org id 和 ak idAPI key 的标识部分)直接出现在 message 里。** 中转站若把上游错误原样透传给下游,等于把自己的账号标识和 key 标识广播给所有用户。**这是必须在错误改写层拦截的信息泄漏点**,比 §1.2 的响应头更严重(响应头容易被整体过滤,错误 message 往往被原样转发)。
其他:
- ✅ **499 是非标准状态码**nginx 系)。中转站的 HTTP 客户端和监控要能处理,别当 4xx 计入用户失败率。
- ✅ **504 的 900 秒超时**——中转站自身的上游超时必须 ≥900s,否则会在 Kimi 还在算时先断开,浪费已消耗的 token。
- ✅ 官方提醒:**OpenAI SDK 的自动重试会把 1 次请求放大成 2–3 次真实调用**,吃掉 RPM 配额。中转站应关掉 SDK 层重试,自己实现按 type 分流的重试。
### 12.3 限流响应头 🆕(文档完全未记载)
实测 429 响应头:
```
retry-after: 1
x-retry-after: 1
msh-cooldown-seconds: 1
```
**同一个信息重复了三份**(标准头 + 两个私有头),但**没有任何 `x-ratelimit-*` 头**——即**无法在请求成功时得知剩余配额**,只能等撞到 429 才知道。
> 中转站的配额管理因此只能靠本地计数 + 429 反馈,做不了"提前避让"。`retry-after` 是标准头,直接用它做退避即可。
### 12.4 限流分档 ✅
按累计充值金额定档(代金券不算):
| Tier | 累计充值 | 并发 | RPM | TPM | TPD |
| --- | --- | --- | --- | --- | --- |
| 0 | ¥0 | 1 | 3 | 500K | 1.5M |
| 1 | ¥50 | 50 | 200 | 2M | 无限 |
| 2 | ¥100 | 100 | 500 | 3M | 无限 |
| 3 | ¥500 | 200 | 5K | 3M | 无限 |
| 4 | ¥5K | 400 | 5K | 4M | 无限 |
| 5 | ¥20K | 1K | 10K | 5M | 无限 |
✅ **Tier 0 的 3 RPM 是真实且严格的**——本次实测全程必须每请求间隔 21 秒,任何并发都立刻 429。联调时若发现大量 `rate_limit_reached_error`,先看档位而不是查代码。
🆕 响应头 `msh-gid` 直接给出档位(实测为 `free`),**中转站可用它自动探测上游档位并据此设置本地并发上限**,无需人工配置。
---
## 13. 计价(K3,人民币)✅
| 项 | 单价 / 1M tokens |
| --- | --- |
| 输入(缓存命中) | ¥2 |
| 输入(缓存未命中) | ¥20 |
| 输出 | ¥100 |
- 1M = 1,000,000。无按上下文长度分档。
- **输出是命中输入的 50 倍**。加上思考默认 `max` 档、隐藏 system prompt、Preserved ThinkingK3 的实际输出 token 量显著高于同类模型——实测一句"说:好"能烧掉 **310 个 completion tokens**(其中 287 是 reasoning),而关闭思考后只要 **11 个**。
- **同一请求关不关思考差 28 倍输出成本**,这是 §4.1"K3 思考实际可关"这条发现的直接价值。
- Batch 省 40%(不支持 K3);联网搜索按次另计费,且**搜索 token 不在响应 usage 里**(§6.4)。
---
## 14. 实测推翻的文档结论(速查)
| # | 文档说法 | 实测结果 |
| --- | --- | --- |
| 1 | K3 思考恒开、不可关闭、不使用 `thinking` 参数 | ❌ `thinking:{"type":"disabled"}` 生效,`prompt_tokens` 88→21,无 `reasoning_content` |
| 2 | 流式 usage 只在 `choices[0].usage` | ❌ 传 `include_usage:true` 时在顶层 + `choices:[]`,与 OpenAI 一致 |
| 3 | `reasoning_effort` 仅接受 `low`/`high`/`max` | ❌ `"medium"`、`"banana"` 都返回 200,非法值静默忽略 |
| 4 | K3/K2.7-code 多轮缺 `reasoning_content` 会报错 | ❌ 200 正常继续 |
| 5 | `role:"tool"` 消息必须带 `name` | ❌ 不带也 200 |
| 6 | Partial Mode 与 `json_object` 冲突 | ❌ 200 正常工作 |
| 7 | 缓存需 prompt > 256 token 才命中 | ❌ 21 token 也命中 |
| 8 | K3 需最低充值 ¥10、代金券不解锁 | ❌ 纯代金券账号正常调用 K3 |
| 9 | Claude Code 用 `kimi-k3[1m]` | ❌ 两个入口都 404,正确名是 `kimi-k3` |
| 10 | `kimi-k2.5` context_length 128000(示例) | ❌ 实际 262144 |
**文档说不能、实际能**(1、3、4、5、6、7、8)意味着照文档写会**过度限制**;**文档说能、实际不能**(9)意味着照抄配置会**直接失败**。两个方向都有。
---
## 15. 仍待验证(下一轮实测)
1. **`json_schema` + `strict: true` 的 MFJS 实际约束**(§7)——本轮未测,是结构化输出的核心。
2. **K2.6 复杂 schema 吐 Markdown 围栏**的复现条件(§7)。
3. **官方工具(Formula API)与 `$web_search` 的关系**(§6.4)。
4. **`prompt_cache_key` 是否真的分裂缓存命名空间**(§10)——需要构造缓存命中/未命中对照。
5. **文件上传 `purpose` 的合法枚举全集**、Batch 端到端(§11.1、§11.2)。
6. **K3 关闭思考后 `tool_choice: "required"` 的行为**(§6.2)——指定函数已知会静默幻觉,`required` 未测。
7. **长对话 + 多步工具链下 `reasoning_content` 是否真的可省**(§4.3)——短对话已验证可省,长链路需复验。
8. **国际站 `api.moonshot.ai`** 的行为是否与国内站一致(本轮只测了国内站)。
9. **`kimi-k2.5`** 的思考开关与 max output(本轮未覆盖该模型)。
---
## 16. 中转站接入 checklist
**必须做的请求改写**
- [ ] 按模型剥离 `temperature`/`top_p`/`n`/`presence_penalty`/`frequency_penalty`(§3)——**最高优先级**,同时消除两个入口的行为差异。
- [ ] 流式请求一律补 `stream_options.include_usage = true`(§8)——用改写请求换取 OpenAI 标准的 usage 位置和缓存字段。
- [ ] thinking 参数按模型分派:K3 可用 `reasoning_effort` 或 `thinking`K2.7-code 只能 `enabled`K2.6/K2.5 用 `thinking`(§4)。
- [ ] 自行校验 `reasoning_effort` 枚举(上游不校验,非法值会静默跑最贵档)(§4.2)。
- [ ] `prefill` → `partial: true`(§5)。
- [ ] `n > 1` 时连带检查 `temperature > 0`,否则上游报的错无法理解(§3.1)。
- [ ] 模型名 `kimi-k3[1m]` 之类的方括号后缀要拒绝或剥离(§1.3)。
**必须做的响应/错误改写**
- [ ] **剥掉 `msh-org-id` / `msh-uid` / `msh-project-id` 响应头**(§1.2,账号标识泄漏)。
- [ ] **过滤 429 错误 message 里的 `org-xxx<ak-xxx>`**(§12.2,账号+key 标识泄漏)。
- [ ] 重写 `tool_calls[].id`(上游是 `{函数名}_{序号}`,多轮会重复)(§6.1)。
- [ ] 错误体补 `code`/`param` 以对齐 OpenAI;解析上游错误时容忍 `error` 字段可能是 string(网关格式)(§12.1、§1.3)。
- [ ] 非 chat 端点要剥掉 `{"code","scode","status","data"}` 业务包装(§1.2)。
- [ ] `tool_calls[].type` 可能是 `builtin_function`,不要按 OpenAI 枚举拒绝(§6.4)。
**必须保持不动的**
- [ ] prompt 前缀逐字节稳定,不注入、不重排、不规范化空白(§10,**10 倍价差**)。
- [ ] `content` 数组不要压成字符串(§9)。
- [ ] 内置工具与 `encrypted_output` 按 opaque 透传(§6.4)。
- [ ] 不给未传参数补默认值(§3.1)。
- [ ] 消息保留 passthrough 扩展字段(动态工具的 `{"role":"system","tools":[...]}` 无 content)(§6.3)。
**能力降级表**
- [ ] 思考开启时**无法强制调用指定函数**——应**显式报错**,不要靠关闭思考绕过(§6.2,会静默幻觉出假数据)。
- [ ] K2.7-code 思考不可关、`required` 也不可用(§4、§6.2)。
- [ ] 不支持 http(s) 图片 URL(§9)。
- [ ] 新模型不支持 `n > 1`(§3.1)。
- [ ] 动态工具加载仅 K3(§6.3)。
**运维配置**
- [ ] 上游超时 ≥900s(§12.2)。
- [ ] 关闭 SDK 层自动重试,按 error `type` 分流(过载退避 / 欠费停止 / 限流降并发)(§12.2)。
- [ ] 用 `retry-after` 头做退避;**没有 `x-ratelimit-*`,配额只能本地计数**(§12.3)。
- [ ] 用响应头 `msh-gid` 自动探测账号档位并设置本地并发上限(§12.4)。
- [ ] key 与区域 base URL 绑定成对(§1.1)。
- [ ] "200 OK + 空 content + `finish_reason: length`" 不触发重试(§4.4)。
- [ ] 用 `/v1/tokenizers/estimate-token-count` 做预估(口径含隐藏 system prompt,与计费一致)(§11.3)。
**Kimi For Coding 走单独一套配置**(§17):模型别名表、鉴权、限流、错误改写都不能与 platform 共用。
---
## 17. Kimi For Coding:同一家公司的另一套栈 ✅(2026-08-13 实测)
> Base URL`https://api.kimi.com/coding/v1`(根路径 `https://api.kimi.com/coding/` 返回 `{"message":"Welcome to the Kimi For Coding API!"}`
> Key 形如 `sk-kimi-...`。面向 Claude Code / 编码 Agent 的**订阅制**产品,不是 platform 的一个套餐。
**底层推理栈相同**(流式 `system_fingerprint` 两边都是 `fpv0_489ccb45`),**但接入层是完全独立的两套实现**。官方 troubleshooting 只用一句"API Platform keys ≠ Kimi Code keys"带过,实际差异远不止鉴权。
### 17.1 差异总表
| 维度 | platform`api.moonshot.cn` | Kimi For Coding`api.kimi.com/coding` |
| --- | --- | --- |
| Key 前缀 | `sk-` | **`sk-kimi-`** |
| 账号体系 | 组织/项目/余额 | **个人账号**(见 §17.4 |
| 模型名 | `kimi-k3`、`kimi-k2.7-code`、`moonshot-v1-*` | **`k3`、`k3-256k`、`kimi-for-coding`、`kimi-for-coding-highspeed`** |
| 模型名校验 | 严格,未知名 404 | ❌ **几乎不校验**(见 §17.3) |
| OpenAI 入口 | `/v1/chat/completions` | `/v1/chat/completions` |
| Anthropic 入口 | `/anthropic/v1/messages` | **`/v1/messages`**(与 chat 同前缀,无 `/anthropic` |
| thinking `signature` | ❌ 无 | ✅ **有真实签名** |
| Anthropic usage 字段 | 混入 OpenAI 字段(双套并存) | **纯 Anthropic 字段** |
| Anthropic 响应 id | `chatcmpl-...`(不规范) | **`msg_...`**(规范) |
| SSE 事件分隔 | `event: message_start`(有空格) | `event:message_start`**无空格** |
| `count_tokens` | ❌ 无 | ✅ `POST /v1/messages/count_tokens` |
| `estimate-token-count` | ✅ 有 | ❌ 404 |
| 余额查询 | ✅ `/v1/users/me/balance` | ❌ **404,无余额端点** |
| 账号信息端点 | ❌ 无 | 🆕 **`/v1/me`(返回手机号等 PII** |
| `tool_calls[].id` | `get_weather_0`(可预测、会重复) | **`tool_jfSEsjJ6HvGswLR2r8XaBjnc`**(随机串,规范) |
| `$web_search` 的 id | `t-web_search-{hash}` | `tool_{随机串}` |
| 响应 id | `chatcmpl-6a7d5c23...`(十六进制) | `chatcmpl-JfEbxqHjKWC5fADqUduALJfG`(大小写混合) |
| 限流 | Tier 0 = **3 RPM / 1 并发** | ✅ **并发 10 全部 200**,无可观测的 RPM 限制 |
| 调试响应头 | `msh-request-id` / `msh-org-id` / `msh-uid` / `msh-gid` | **`x-trace-id`**、`server-timing`、`server: nginx`**无 `msh-*`** |
| 网关错误 | 中文 `{"code":5,"error":"url.not_found","message":"没找到对象"}` | 统一 API 格式 `{"error":{"message":"The requested resource was not found","type":"resource_not_found_error"}}` |
| `/v1/models` 字段 | `owned_by`/`permission`/`root`/`parent` | **`display_name`/`created_at`/`type`** + 分页字段 `first_id`/`last_id`/`has_more` |
| `reasoning_effort` 默认 | `max` | **`high`**(省钱档) |
**行为一致的部分**(说明共用推理层):采样参数硬 400(`invalid temperature: only 1 is allowed for this model` 逐字相同)、`tool_choice` 指定函数与思考互斥的 400、K3 `thinking:{"type":"disabled"}` 可关(`prompt_tokens` 同样 21)、partial mode、`$web_search` 的 builtin_function 协议、流式 usage 由 `include_usage` 控制、`system_fingerprint` 值相同。
🆕 但**同一个校验的错误文案两边不同**,说明校验逻辑各写了一遍:
```
n=2 platform : invalid n: only 1 is allowed for this model
n=2 coding : Your request body contains invalid value for param n
```
### 17.2 模型列表(实测 `GET /coding/v1/models`
| id | display_name | context | 实测备注 |
| --- | --- | --- | --- |
| `kimi-for-coding` | K2.7 Coding | 262144 | **未知模型名的 fallback 目标**(§17.3.1);无隐藏 system prompt |
| `kimi-for-coding-highspeed` | K2.7 Coding Highspeed | 262144 | ⚠️ 与上一行**实测无法区分**(指纹、上限、速度全同) |
| `k3` | K3 | 1048576 | `default_effort: "high"`;带 79 token 隐藏 system prompt |
| `k3-256k` | K3-256k | 262144 | 实测是**网关层给 `k3` 加的 256K 限制**,非独立模型;`supports_video_in: false` |
四个模型全部 `supports_thinking_type: "only"`。**没有 moonshot-v1 系列**。
> ⚠️ 注意 `k3-256k` 是**同模型的短上下文 SKU**——这与 platform 用 `moonshot-v1-8k/32k/128k` 切窗口是同一思路,但命名规则又不一样。中转站的别名表要为两个上游各维护一份。
### 17.3 ❌ 最危险的差异:`model` 字段几乎不校验
实测 Kimi For Coding 对未知模型名**不报错,而是照常返回 200 并把请求的名字原样回显**:
```jsonc
{"model":"gpt-4", ...} → 200,响应 "model":"gpt-4",但内容带 reasoning_content(实际是 Kimi 在跑)
{"model":"claude-sonnet-4-5",...} → 200,响应 "model":"claude-sonnet-4-5"
{"model":"moonshot-v1-8k", ...} → 200,响应 "model":"moonshot-v1-8k"(该模型在此产品里根本不存在)
{"model":"foobar123", ...} → 200,响应 "model":"foobar123"
{"model":"", ...} → 200,响应 "model":""(空字符串也接受)
```
**唯一被拒的是带方括号的名字**,而且报错方式很离谱:
```
{"model":"k3[1m]"} → HTTP 401
{"error":{"message":"Your model id does not exist, recognized as other:k3[1m]. Please set model id as `k3`.",
"type":"invalid_authentication_error"}}
```
**模型不存在却返回 401 + `invalid_authentication_error`**——状态码和错误类型都与语义不符(platform 同样情况返回的是 404 `resource_not_found_error`)。中转站若按 401 触发"key 失效"逻辑去刷新凭据或告警,会被这个错误误导。
> **对中转站的三重后果**:
> 1. **无法用模型名做路由校验**——上游不会告诉你名字写错了,只会默默用 fallback 模型跑完并收钱。
> 2. **响应的 `model` 字段不可信**——它是请求值的回显,不是实际推理的模型。platform 那边至少 `moonshot-v1-auto` 会回显真实模型名(§2.1),这边连这个都没有。
> 3. **下游可能被误导**:用户传 `gpt-4` 拿到 200 和一个 `"model":"gpt-4"` 的响应,会以为真的调用了 GPT-4。**中转站必须自己维护白名单并拒绝未知模型名**,不能依赖上游校验。
### 17.3.1 反推:模型名确实参与路由,未知名 fallback 到 `kimi-for-coding` ✅
响应只回显请求值,无法直接观测实际模型。用**两个独立的侧信道**反推,结论一致。
**方法一:上下文上限边界。** 构造 270,015 token 的 prompt(经 `count_tokens` 校准,介于 262144 与 1048576 之间),分别打各模型名:
| 请求的 model | 结果 |
| --- | --- |
| `k3` | ✅ **200 正常返回**(真的吃下了 270K) |
| `k3-256k` | `k3-256k supports only 256K context.` |
| `kimi-for-coding` | `Invalid request: Your request exceeded model token limit: 262144 (requested: 270016)` |
| `gpt-4` | **同上,逐字相同** |
| `foobar123` | **同上,逐字相同** |
**方法二:隐藏 system prompt 的 token 指纹。** §4.1 发现思考模式会注入一段隐藏 system prompt,各模型注入量不同,可作指纹。同一句 `"说:好"`
| 请求的 model | `prompt_tokens` |
| --- | --- |
| `k3` | **89** |
| `k3-256k` | **89** |
| `kimi-for-coding` | **10** |
| `kimi-for-coding-highspeed` | **10** |
| `gpt-4` / `foobar123` / `""` / `claude-sonnet-4-5` | **10** |
两个方法交叉印证:
- ✅ **模型名确实参与路由**——`k3` 走 1M 上下文且带 79 token 的隐藏 system prompt`kimi-for-coding` 系列走 256K 且**无隐藏 prompt**。二者是两条不同的推理路径。
- ✅ **所有无法识别的模型名(含空字符串)一律 fallback 到 `kimi-for-coding` 系列****不是** `k3`。
- ✅ `k3-256k` 是**网关层对 `k3` 加的 256K 限制**,不是独立模型:它的隐藏 prompt 指纹与 `k3` 相同(89),但超限错误是一条**人工写的独立文案**(不含 `requested:` 数字),与 `kimi-for-coding` 那条模型层通用错误明显不同源。
> **中转站可直接用的结论**:用户若传了拼错的模型名,实际跑的是 K2.7 Coding(256K),而不是他以为的 K3——**上下文能力从 1M 悄悄降到 256K**,长上下文任务会在毫无提示的情况下超限失败。这使"自建模型名白名单"从建议变成必需。
⚠️ 仍无法区分的是 `kimi-for-coding` 与 `kimi-for-coding-highspeed`:两者 token 指纹相同(均为 10)、上下文上限相同(262144)、**实测输出速度也无差异**33.2 vs 34.9 tok/s,重复一轮后 highspeed 反而更慢)。官方宣称 highspeed 约 180 tok/s**实测两者都只有约 33 tok/s**——要么高速档在当前负载/订阅层级下未生效,要么二者本就是同一后端。
### 17.4 🆕 `/coding/v1/me` 返回个人身份信息(隐私暴露面)
platform 只有余额端点(返回金额),Kimi For Coding 则有一个 `/v1/me`
```jsonc
{
"user_id": "...", "global_id": "...",
"nickname": "登月者XXXX",
"avatar": "https://avatar.moonshot.cn/avatar/default/...",
"phone": {"country_code": "86", "number": "1XX****XXXX"}, // ← 部分脱敏的手机号(实测返回真实号码的首三位+后四位)
"status": "USER_STATUS_NORMAL", "region": "REGION_CN",
"user_level": 25, "user_level_name": "Allegretto",
"domain": 1, "domain_name": "DOMAIN_NEXUS",
"created_time": "...", "last_login_time": "..."
}
```
> **任何持有这个 key 的人都能读到账号主人的手机号(部分脱敏)、昵称、注册与最后登录时间。** 对中转站的含义:
> - **把 Kimi For Coding 的 key 交给第三方使用 = 交出账号主人的部分身份信息**,风险等级远高于 platform 的 key(那边最多泄漏余额)。多租户中转站尤其要注意。
> - 中转站**不应把 `/v1/me` 透传给下游**。若要做上游健康检查,用它是可行的(无余额端点可用),但响应必须裁剪,只保留 `status`。
> - 反过来,这也是**唯一能确认 coding key 是否有效**的轻量端点(没有余额接口)。
### 17.5 计费与配额:中转站看不见
- ❌ **没有余额/用量端点**`/balance`、`/usage`、`/quota`、`/subscription` 全部 404)。订阅制产品,用量只能在网页端看。
- 因此**中转站无法对 Kimi For Coding 做余额预警或成本核算**——只能自己按 usage 累加统计,且没有官方口径可对账。这与 platform(有 `/v1/users/me/balance`,可做健康检查与预警)是运维层面的实质差别。
- 限流宽松(10 并发实测无 429),但**限额规则未公开**,撞到限制时的错误类型未知(本轮未触发)。⚠️ 中转站应做好"上游可能在没有任何预警的情况下拒绝服务"的准备。
### 17.6 Anthropic 入口质量反而更高
Kimi For Coding 的 `/coding/v1/messages` 是**本次调研里最接近 Anthropic 官方规范的实现**
- ✅ 规范的事件序列:`message_start` → `content_block_start` → `content_block_delta` ×N → `content_block_stop` → `message_delta` → `message_stop`
- ✅ `msg_` 前缀的 id、纯 Anthropic 的 usage 字段、**thinking block 带 `signature`**
- ✅ 有 `POST /v1/messages/count_tokens`(返回 `{"input_tokens":9}`
- ✅ `x-api-key` 和 `Authorization: Bearer` 都接受,`anthropic-version` 头非必需
对比 platform 的 `/anthropic/v1/messages`id 是 `chatcmpl-`、usage 混两套字段、thinking 无 signature、无 count_tokens。
> **中转站若要做 Anthropic 格式的上游,Kimi For Coding 明显是更合适的一侧。**
### 17.6.1 反推:`signature` 是**装饰品**,服务端不校验 ✅
Anthropic 真机会校验 thinking 的 `signature`(篡改会报错),这是"推理内容不可跨家搬运"结论的根基(见 README §2 第 2 条)。实测 Kimi For Coding 的 signature(长达 12,946 字符,看起来很像真的):
| 多轮回传时对 thinking block 的处理 | 结果 |
| --- | --- |
| 原样回传 | 200 |
| **signature 全部替换成 `XXXX...`**(等长) | **200** |
| **完全删除 `signature` 字段** | **200** |
| **保留原 signature,但把 `thinking` 文本换成伪造内容** | **200** |
**四种情况全部正常返回,没有任何校验。**
> 这条对中转站是**减负**Kimi For Coding 的 thinking block **不必当 opaque blob 搬运**,可以自由丢弃 `signature`、截断甚至改写 thinking 内容——与 platform 那侧(明文无签名)的处理方式可以统一。
>
> 但反过来它是个**安全提示**:这个 signature 只是长得像密码学签名,实际不提供任何完整性保证。**中转站不要把它当作"上游已验证推理内容"的证据**,也不要在自己的对外 API 里模仿这种"假签名"——下游若据此信任内容,就是被误导。
>
> ⚠️ 注意方向性:这条结论**只适用于 Kimi**。转发到 Anthropic 真机时,signature 仍必须原样搬运。
### 17.7 结论:必须分开处理
**是的,必须当作两个完全独立的上游。** 具体到实现:
| 层面 | 要点 |
| --- | --- |
| 上游配置 | 独立的 base URL + key + 模型别名表;key 前缀(`sk-` vs `sk-kimi-`)可用于自动识别配错 |
| 模型路由 | **中转站自建白名单**——coding 侧不校验模型名,**错名静默 fallback 到 `kimi-for-coding`(256K),上下文能力从 1M 悄悄降级**(§17.3.1 |
| thinking | coding 侧的 `signature` 不参与校验,可自由丢弃/改写(§17.6.1);转发到 Anthropic 真机时则必须原样搬运 |
| 响应处理 | coding 侧的 `model` 字段是回显值,**不可用于记账**;platform 侧 `moonshot-v1-auto` 也有类似问题 |
| 错误处理 | coding 侧模型不存在返回 **401**,不能触发凭据刷新逻辑;两侧错误文案不同 |
| 限流 | platform 需按 Tier 严格限速(Tier 0 = 3 RPM);coding 侧宽松但规则不透明 |
| 运维 | platform 用 `/v1/users/me/balance` 做健康检查;coding 只能用 `/v1/me`**响应需裁剪,含 PII** |
| Anthropic 格式 | 优先用 coding 侧;但其 thinking `signature` 必须 opaque 搬运,platform 侧则不用 |
| 隐私 | coding key 泄漏 = 泄漏账号主人手机号等信息,**不可作为共享凭据下发** |