# 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 ;OpenAPI:https://platform.kimi.com/docs/openapi.json > > **2026-08-13 用真实 API key 实测校验过一遍**(约 60 次请求,覆盖两个入口)。实测与文档冲突处均以实测为准。 > 实测环境:中国站 `api.moonshot.cn`,账号档位 `msh-gid: free`(Tier 0,3 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` 报 400,Anthropic 入口 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 ` ✅。**没有智谱那种 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="" # 注意是 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` | 0–1,默认 **0** | 固定 `1.0`,**传其他值 400** | | `top_p` | 0–1,默认 1 | 固定 `0.95`,**传其他值 400** | | `n` | 1–5,默认 1 | 固定 `1`,**传其他值 400** | | `presence_penalty` / `frequency_penalty` | −2–2,默认 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_weather,thinking disabled // 响应 200,无 tool_calls,finish_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 符合 **MFJS(Moonshot 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://`**(先经 `/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。 - 支持多模态 batch(base64 或 `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 request reached organization max RPM: 3, please try again after 1 seconds ``` **org id 和 ak id(API 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 Thinking,K3 的实际输出 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`**(§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 泄漏 = 泄漏账号主人手机号等信息,**不可作为共享凭据下发** |