65 KiB
Kimi API(月之暗面 Moonshot)
来源(官方文档站
.md原文,采集于 2026-08-13):
- API 参考:overview、chat、models-overview、list-models、errors、estimate、balance、batch-create
- 指南:models、k3-quickstart、k2.7-code-quickstart、thinking、reasoning-effort、partial、caching、tool-choice、dynamic-tools、tool-calls、web-search、official-tools、response_format、streaming、vision、file-qa、batch、claude-code、troubleshooting
- 计价与限流:limits、chat-k3
- 完整索引: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"** 的一家:
- 新模型把采样参数写死并硬 400——
temperature不是 1.0 直接报错,实测确认不是静默忽略(§3)。带默认temperature=0.7的 OpenAI 客户端在 K3 上开箱即挂。 - 同一模型在两个入口行为相反——OpenAI 入口对
temperature=0.7报 400,Anthropic 入口 200 静默忽略(§3.2)。 - 参数集按模型分裂成四套——合法请求体取决于
model值(§3、§4)。 - 文档与实现大面积脱节——实测推翻了 8 条文档明确写下的规则,其中包括"K3 思考恒开不可关"这样的核心设定(§14)。照文档写代码会同时踩到"文档说能实际不能"和"文档说不能实际能"两个方向。
- 它其实是两个产品——
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 返回的不是裸对象,而是
{"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 同时并存两套字段:
"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。 官方文档这段配置照抄会直接失败。
🆕 三种错误格式并存(这是中转站错误处理的隐藏坑):
// 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 个字段,实测返回:
{
"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 |
三条独立的怪癖:
-
temperature默认值跨代反转:moonshot-v1 默认 0,新模型固定 1.0。"同一家 API,不传 temperature 时的行为随模型跳变"。中转站若为对齐 OpenAI 而主动补默认temperature,在 v1 上改变原有行为,在 K3 上直接 400。结论:绝不给未传的参数补默认值。 -
temperature上限是 1 而非 2(OpenAI 是 2)。 -
🆕
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"。实测:
// 请求
{"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 ✅(实测确认)
{"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(前缀续写)✅
{"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 相同。差异:
-
❌
role: "tool"消息的name字段实际可选(推翻文档)。文档说"必须提供tool_call_id和name,Kimi 才能正确匹配",实测只给tool_call_id不给name→ 200 正常。中转站从 Anthropic 入口转换时不必回查函数名(Anthropic 的tool_result只有tool_use_id)。 -
✅
tool_call_id必须严格匹配:传一个不存在的 id →400 Invalid request: tool_call_id is not found🆕 注意错误信息里
tool_call_id和is not found之间是两个空格——本该填 id 值的位置是空的,是个字符串拼接 bug。中转站不要指望从这条错误信息里解析出是哪个 id 出的问题。 -
✅
finish_reason: "tool_calls"时content可能非空(Kimi 会解释为什么调用)。按"有 tool_calls 就忽略 content"写的下游会丢内容。 -
🆕
tool_calls[].id不是随机串,而是{函数名}_{序号}:{"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 并维护映射表。 -
✅
finish_reason枚举为stop/length/tool_calls,没有国产厂商常见的sensitive——内容安全走 400content_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,但模型没有调用工具,而是直接编造了结果:// 请求: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 独有)✅ 实测确认
{"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 协议(实测确认):
{"type": "builtin_function", "function": {"name": "$web_search"}}
实测响应:
{"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、Anthropicweb_search_20250305、Kimi$web_search的执行模型和计费都不同),按 opaque 透传,并在文档标明"内置工具与上游绑定"。
7. 结构化输出 ✅(文档为准,部分实测)
response_format 支持 text / json_object / json_schema。
{"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 私有行为):
// 最后一个 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 完全一致):
// 独立的最后一个 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 在响应里出现两次(同一个值):
"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<ak-xxxxxxxxxxxxxxxx> 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. 仍待验证(下一轮实测)
json_schema+strict: true的 MFJS 实际约束(§7)——本轮未测,是结构化输出的核心。- K2.6 复杂 schema 吐 Markdown 围栏的复现条件(§7)。
- 官方工具(Formula API)与
$web_search的关系(§6.4)。 prompt_cache_key是否真的分裂缓存命名空间(§10)——需要构造缓存命中/未命中对照。- 文件上传
purpose的合法枚举全集、Batch 端到端(§11.1、§11.2)。 - K3 关闭思考后
tool_choice: "required"的行为(§6.2)——指定函数已知会静默幻觉,required未测。 - 长对话 + 多步工具链下
reasoning_content是否真的可省(§4.3)——短对话已验证可省,长链路需复验。 - 国际站
api.moonshot.ai的行为是否与国内站一致(本轮只测了国内站)。 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 并把请求的名字原样回显:
{"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 失效"逻辑去刷新凭据或告警,会被这个错误误导。
对中转站的三重后果:
- 无法用模型名做路由校验——上游不会告诉你名字写错了,只会默默用 fallback 模型跑完并收钱。
- 响应的
model字段不可信——它是请求值的回显,不是实际推理的模型。platform 那边至少moonshot-v1-auto会回显真实模型名(§2.1),这边连这个都没有。- 下游可能被误导:用户传
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:
{
"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 泄漏 = 泄漏账号主人手机号等信息,不可作为共享凭据下发 |