用 MoonPalace 调试 Kimi API 调用

2026-08-25

数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。

MoonPalace 的定位其实很单纯:它是一个跑在你本机的 HTTP 代理,你把 base_url 从 Kimi 官方端点改成它监听的本地地址,它就把每一次请求原样转发出去,同时把请求头、响应头、请求体、响应体一并落进一个本地 sqlite 库。调试阶段最难受的两件事——“报错了但现场没留下来”和”这一次调用到底对应服务端的哪条记录”——它都给出了答案:前者靠完整捕获,后者靠 request_id 与 chatcmpl_id 两个可检索的 ID。官方文档明确建议把它当成代码编写和调试阶段的 API “供应商”。真正值得记住的不是安装命令,而是三个检索维度的对应关系,以及 --force-stream 这个选项背后的判断——官方把它写成了一段对连接超时成因的推测,而不是一句功能说明。

它是什么,官方怎么定位

按官方文档的说法,MoonPalace(月宫)是由 Moonshot AI 月之暗面提供的 API 调试工具,特点列了五条:全平台支持(Mac、Windows、Linux);启动后把 base_url 替换成本地地址即可开始调试;能捕获完整请求,包括网络错误时的”事故现场”;可以通过 request_id、chatcmpl_id 快速检索、查看请求信息;以及一键导出 BadCase 的结构化上报数据,官方说明这是用来帮助 Kimi 完善模型能力的。

“捕获事故现场”这一条比听上去有用。API 调用出问题的时候,很多语言的 SDK 只会往上抛一个异常字符串,原始的响应头早就被吞掉了;而 Kimi 的排障链路又高度依赖响应头里的 request_id——官方错误码文档里,收到 500 的处置是先稍后重试,如果持续出现,则”附带 request_id 联系支持团队”。你的应用代码不专门去打这个字段,出了事就只能干瞪眼。挂一层本地代理,等于把这件事从”每个项目都要写一遍日志”变成了”启动一个进程”。

装:两条路,都不需要额外依赖

第一条是 Go 工具链。如果本机已经装了 go,直接执行:

go install github.com/MoonshotAI/moonpalace@latest

官方说明这条命令会把编译后的二进制装到 $GOPATH/bin/ 目录,之后运行 moonpalace 检查是否安装成功。如果这时候仍然找不到二进制,官方给的处置是把 $GOPATH/bin/ 加进 $PATH 环境变量——这属于 Go 安装的老问题,不是这个工具特有的。

第二条是从项目的 Releases 页面直接下预编译产物。官方列出的文件名有四个:moonpalace-linuxmoonpalace-macos-amd64(对应 Intel 版本的 Mac)、moonpalace-macos-arm64(对应 Apple Silicon 版本的 Mac)、moonpalace-windows.exe。下载后需要放到已经被 $PATH 包含的目录里,把文件改名为 moonpalace,最后赋予可执行权限。Mac 用户注意别下错架构,这两个后缀是官方文档里自己标注的对应关系。

装好后敲一下裸命令,会打印出可用的子命令清单:cleanup(清理请求记录)、completion(生成 shell 自动补全脚本)、export(导出一次请求)、helpinspect(查看某次请求的具体内容)、list(按条件查询请求)、start(启动代理服务器)。全局标志只有 -h/--help-v/--version 两个。日常真正会用到的是 start、list、inspect、export 这四个,剩下的属于顺手。

启动,并把 base_url 换过去

启动命令是 moonpalace start --port <PORT>--port 指定本地监听端口,官方文档写明默认值为 9988(以官方文档当前版本为准)。启动成功时它会打印一行提示,告诉你把 base_url 改成 http://127.0.0.1:9988/v1。用了自定义端口,就照它打印出来的地址改,别自己拼。

这里有个容易被忽略的小功能:--key 参数。启动时用它设定一个默认的 api_key,之后请求就不用手动带 key 了,MoonPalace 会在转发给 Kimi API 时替你补上。调试阶段这一手挺实用——你可以让本地代码里干脆不出现任何密钥,密钥只活在启动命令里。当然这也意味着那条启动命令本身成了敏感信息,别顺手贴进聊天窗口或提交进仓库,密钥的整体管理思路可以参考API 密钥安全管理

调用成功后,它会在命令行里按日志形式吐出请求细节。官方给的样例包含这些内容:请求方法与路径、HTTP 状态、请求头里的 Content-Type、响应头里的 Msh-Request-IdServer-TimingMsh-UidMsh-Gid,以及响应体里的 idprompt_tokenscompletion_tokenstotal_tokens,最后是一行 New Row Inserted: last_insert_id=<N>。文档也提到,想把日志持久化,把 stderr 重定向到文件即可。

Server-Timing 这个字段在导出的响应头样例里写作 Server-Timing: inner; dur=<数值>,而在 list 命令的表格里被单列成一列。文档没有解释这个数值的具体口径,所以别把它当成”接口耗时”去下结论——它是服务端返回的一个计时字段,能横向比较同一批请求,仅此而已。

还有一条提示值得记:非流式输出模式下(stream=False),如果 MoonPalace 判断 max_tokens 设小了,会在日志里给出提示,建议你设一个更大的值。输出被截断这类问题往往表现得很像”模型不听话”,有这条提示至少能少绕一圈。

三个 ID 的对应关系,这是全篇最该记的

官方在日志说明后面加了一条注,把日志字段和命令行参数一一对上了:

  • 响应头里 Msh-Request-Id 的值,对应检索和导出命令里 --requestid 参数的值;
  • 响应体里的 id,对应 --chatcmpl 参数的值;
  • 日志末尾 last_insert_id 的值,对应 --id 参数的值。

为什么强调这个?因为这三个 ID 的来源完全不同:--id 是 MoonPalace 本地数据库的自增主键,只在你这台机器上有意义;--chatcmpl 来自模型响应体,是这次补全的标识;--requestid 来自服务端响应头,是跨到 Kimi 那一侧唯一有效的凭证。给官方提工单、对账单、查那些”客户端没显示结果却产生了费用”的疑难,能被服务端认的是 request_id,另外两个都不行。官方的常见问题页里列的排查顺序也是先看 HTTP 状态码与 request_id,再看响应中的 usage 字段,然后才是客户端是否自动重试。

反过来,在你自己的本地检索里,--id 最省事——list 出来直接抄那个两位数,比复制一长串 UUID 快得多。

—detect-repeat:拦住模型的复读

官方把”重复内容输出”定义为:模型重复不断地输出某一特定字词、句子以及空白字符,并且在达到 max_tokens 限制前不会停下来。这件事的代价是实打实的——文档给出的动机就是,在费用较高的模型上,这种重复输出会造成额外的 Tokens 消耗。

启用方式是在 start 时加 --detect-repeat,检测到重复行为时它会中断模型输出,并在日志里打出一行提示,说当前响应存在内容重复的问题。

两个关键限制,都在官方注里:

第一,只在流式输出(stream=True)的场合才会中断。非流式场合不适用。这个很好理解——非流式的时候整段内容已经在服务端生成完了,代理层拦无可拦,token 也已经花掉了。所以想让这个功能真正省钱,你的调用得是流式的。

第二,容忍度参数的方向是反直觉的。--repeat-threshold 用于设置对重复内容的容忍度,官方原话是越高的 threshold 表示容忍度越低,重复内容将更快被阻断,取值范围在 0 到 1 之间(以官方文档为准)。也就是说这个数调大不是”更宽松”而是”更严格”,很容易设反。

--repeat-min-length 则是检测的起始字符数量:输出的 utf-8 字符数超过这个数量才开启重复检测,低于它不检测。这个设计是为了避开短回复的误伤——一段很短的输出里出现重复字词太正常了。

—force-stream:一个被写成推测的选项

--force-stream 会强制让所有 /v1/chat/completions 请求走流式模式。MoonPalace 把请求参数里的 stream 字段置为 True,拿到响应后再按调用方的原始设置决定怎么返回:调用方本来就设了 stream=True,就按流式格式原样返回,不做特殊处理;调用方没设 stream 或者设了 stream=False,MoonPalace 会在收完所有流式数据块之后,把它们拼成完整的 completion 结构再返回。

官方对此的说法是,开启这个选项不会改变和破坏任何事物,调用方仍然可以用原先的代码逻辑调试和运行程序。

真正有意思的是它给出的理由。文档写的是”初步推测”:常见的 Connection Error / Timeout,可能是因为在非流式模式下,各中间层的网关或代理服务器设置了 read_header_timeout 或 read_timeout,Kimi API 服务端还在组装响应时,中间层就已经把连接断开了——此时连响应的 Header 都还没发出来。转成流式之后,MoonPalace 已经和服务端建立连接并开始接收数据块,中间层看到有数据在流动,就不至于误判成死连接。

这个推测和官方错误码文档里的处置建议是对得上的:那边写明收到 504(网关超时)时建议改用流式输出,常见于非流式长请求。所以 --force-stream 与其说是调试功能,不如说是一个可以在本地先验证一遍的猜想——如果开了它之后你的超时消失了,那问题多半就在链路中间而不在你的代码。这类超时与断流的通用处置思路,可以对照API 调用超时与中断怎么处理一起看。

查:list 与 inspect

代理启动之后,所有经它中转的请求都会记进一个 sqlite 数据库,位置是 $HOME/.moonpalace/moonpalace.sqlite。官方明确说你可以直接连这个数据库查,也可以用命令行查——直连数据库这条路对写脚本批量分析很友好。

moonpalace list 查最近产生的请求,默认展示的字段分两类:便于检索的 idchatcmplrequest_id,以及用于查看请求状态的 statusserver_timingrequested_at

moonpalace inspect 用来看某一条的细节,三种检索方式等价:

moonpalace inspect --id <本地自增ID>
moonpalace inspect --chatcmpl <chatcmpl-...>
moonpalace inspect --requestid <request-id>

默认情况下 inspect 不会打印请求和响应的 body,只给一份 metadata,字段包括 chatcmpl、content_type、group_id、moonpalace_id、request_id、requested_at、server_timing、status、user_id。想看 body 得显式加参数:

moonpalace inspect --chatcmpl <chatcmpl-...> --print request_body,response_body

这个默认值是合理的——body 通常很长,尤其是带了长文档或者一堆工具定义的请求,默认打出来会把终端刷爆。但不知道有这个开关的人,很容易以为”它没记 body”。

导:把一次请求整份交出去

如果某次结果不符合预期,或者你想把一个好例子反馈回去,用 export 把它导成文件:

moonpalace export \
  --chatcmpl <chatcmpl-...> \
  --good/--bad \
  --tag "code" --tag "python" \
  --directory $HOME/Downloads/

其中 id / chatcmpl / requestid 三选一,用法与 inspect 相同;--good/--bad 标记这是 Good Case 还是 Bad Case;--tag 可以重复给,用于打标签;--directory 指定导出文件存放的目录。

导出的 JSON 顶层有五块:metadata(与 inspect 看到的同一套字段)、request(含 url、header、body)、response(含 status、header、body)、category(goodcase / badcase)、tags。也就是说交出去的是一份完整现场,对方不需要再追着你要补充信息。

顺带一提,官方给的导出样例里,响应头那串文本中出现了 Msh-Cache 这个字段。这一页没有解释它的含义,所以这里也不作展开——想了解 Kimi 的上下文缓存机制,该看的是缓存那篇专门文档,别从一个 header 名字上推结论。

反馈渠道方面,官方推荐通过 GitHub Issues 提交 Good Case 或 Bad Case;如果不想公开请求信息,也可以通过企业微信、电子邮件等方式投递,文档给出的邮箱是 api-feedback@moonshot.cn。导出前记得自己看一眼 body——完整现场意味着你的 prompt、你的文档内容、你的工具定义都在里面。

几个容易栽的坑

改了 base_url 忘了改回去。 本地代理进程一关,所有请求就全挂了,而报错长得像网络问题——连接被拒绝、连接超时,异常信息里不会有任何一个字提示你”这个地址指的是本机”。建议把 base_url 做成环境变量或配置项而不是硬编码进代码,调试完把它切回官方端点,而不是靠记忆去逐个文件翻。

以为它能替代服务端排查。 MoonPalace 记的是经过它的那些请求。如果你的问题出在别的机器、别的环境,或者根本没走这个代理,本地库里什么都不会有。真要对账,还是得拿 request_id 去平台侧的用量看板对——官方常见问题页给的口径就是按时间、项目、模型和 request_id 与客户端日志、API 返回的 usage 逐条对照。这类日常成本核对的做法可以参考API 成本监控怎么做

只在出事的时候才打开它。 出事时现场已经没了。这个工具的价值恰恰在于常开——请求都躺在本地 sqlite 里,等你发现异常再回头 list、inspect,才有得查。

把调试链路和生产链路搞混。 --force-stream--detect-repeat 这类选项会改变请求的实际形态(前者改 stream 字段,后者会主动中断输出),它们是调试期的工具,不是生产架构。要在生产里解决超时,该改的是你自己的调用方式,而不是常驻一个本地代理。第一次接 Kimi、还没搞清端点和模型名怎么填的,先看Kimi API 接入把直连跑通,再挂调试代理,顺序反了只会多一层不确定性。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。