Files
2026-08-23 02:12:08 +08:00

65 KiB
Raw Permalink Blame History

Kimi API(月之暗面 Moonshot

来源(官方文档站 .md 原文,采集于 2026-08-13):

2026-08-13 用真实 API key 实测校验过一遍(约 60 次请求,覆盖两个入口)。实测与文档冲突处均以实测为准。 实测环境:中国站 api.moonshot.cn,账号档位 msh-gid: freeTier 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 Codingapi.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.complatform.moonshot.cnplatform.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 仍是 88OpenAI 语义:含缓存部分)。中转站按哪套字段计费,账单会差一个缓存量。

🆕 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,遇到网关级错误会抛类型异常。

已知能力缺口:该端点不支持 WebFetchClaude 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-chatdeepseek-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 / parentOpenAI 早期 /models 的遗留字段(现已从 OpenAI 移除),Kimi 还留着,且内容全是空壳。
  • 🆕 think_effortsreasoning_efforts 内容完全相同的两个字段——显然是改名过程中的兼容遗留,两个都在返回。
  • 🆕 supports_dynamic_tools(仅 K3 为 true)、supports_thinking_type: "only"(仅 K3 有此字段)——这两个字段是判断能力的最可靠来源,比文档准。
  • 文档示例里 kimi-k2.5context_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 而非 2OpenAI 是 2)。

  3. 🆕 n > 1temperature 存在隐藏耦合——在 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_effortlow/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.typeenabled/disabled 200reasoning_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/mediumlow/high 的枚举映射(不会 400);坏消息是用户传错值不会有任何反馈,只会默默跑在默认的 max 档(最贵)。中转站若要给用户可预期的成本,应当自己校验枚举并拒绝非法值。

🆕 kimi-k2.6reasoning_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 相同。差异:

  1. role: "tool" 消息的 name 字段实际可选(推翻文档)。文档说"必须提供 tool_call_idname,Kimi 才能正确匹配",实测只给 tool_call_id 不给 name200 正常。中转站从 Anthropic 入口转换时不必回查函数名(Anthropic 的 tool_result 只有 tool_use_id)。

  2. tool_call_id 必须严格匹配:传一个不存在的 id →

    400 Invalid request: tool_call_id  is not found
    

    🆕 注意错误信息里 tool_call_idis not found 之间是两个空格——本该填 id 值的位置是空的,是个字符串拼接 bug。中转站不要指望从这条错误信息里解析出是哪个 id 出的问题。

  3. finish_reason: "tool_calls"content 可能非空(Kimi 会解释为什么调用)。按"有 tool_calls 就忽略 content"写的下游会丢内容。

  4. 🆕 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 并维护映射表。

  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,但模型没有调用工具,而是直接编造了结果

    // 请求: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 独有) 实测确认

{"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 内置工具:两套并存 /⚠️

abuiltin_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_idusage.total_tokens: 6674),但客户端仍要把 arguments 原样塞回 role: "tool" 消息再发一次。即"假装自己执行了"。
  • 🆕 注意首轮的 usage.total_tokens 只有 93,而 arguments 内嵌的 usage.total_tokens6674——搜索消耗的 token 不在响应的 usage 里,要从 arguments 里解析。中转站按响应 usage 计费会漏掉绝大部分搜索成本。
  • 计费:每次搜索按次收费,搜索结果计入下一轮 prompt_tokens

b)官方工具(Formula API ⚠️ 未实测 —— 另一套体系,12 个工具:convertweb-searchrethinkrandom-choicemewmemoryexceldatebase64fetchquickjscode-runner。走标准 function 协议 + Formula URImoonshot/{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

{"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].usageOpenAI SDK 的 chunk.usage 返回 None"。实测发现两种模式并存,取决于是否传 stream_options.include_usage

不传 include_usageKimi 私有行为):

// 最后一个 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 URIdata:image/png;base64,...
  • Kimi 自有文件协议:ms://<file_id>(先经 /v1/files 上传)

与 OpenAI(接受公网 URL)和 Anthropicsource.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。
  • 支持多模态 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_effortthinkingK2.7-code 只能 enabledK2.6/K2.5 用 thinking(§4)。
  • 自行校验 reasoning_effort 枚举(上游不校验,非法值会静默跑最贵档)(§4.2)。
  • prefillpartial: 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 URLhttps://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 差异总表

维度 platformapi.moonshot.cn Kimi For Codingapi.kimi.com/coding
Key 前缀 sk- sk-kimi-
账号体系 组织/项目/余额 个人账号(见 §17.4
模型名 kimi-k3kimi-k2.7-codemoonshot-v1-* k3k3-256kkimi-for-codingkimi-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-idserver-timingserver: nginxmsh-*
网关错误 中文 {"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 失效"逻辑去刷新凭据或告警,会被这个错误误导。

对中转站的三重后果

  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 promptkimi-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-codingkimi-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_startcontent_block_startcontent_block_delta ×N → content_block_stopmessage_deltamessage_stop
  • msg_ 前缀的 id、纯 Anthropic 的 usage 字段、thinking block 带 signature
  • POST /v1/messages/count_tokens(返回 {"input_tokens":9}
  • x-api-keyAuthorization: Bearer 都接受,anthropic-version 头非必需

对比 platform 的 /anthropic/v1/messagesid 是 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 泄漏 = 泄漏账号主人手机号等信息,不可作为共享凭据下发