调用 Xiaomi MiMo-V2.6 返回报错或空结果时,先不要改请求参数,也不要急着降并发。第一步是把返回体原文留下来,看清 HTTP 状态码、业务错误码和 message 这三层信息,再按错误码判断问题属于入参、路由认证还是资源限制。方向定对了,改动范围通常只有一两处;方向错了,很容易在参数和资源之间来回试。下面按“先取证、再归类、后逐项核对、最后最小复现”的顺序展开,每一步都给出可观察、可验证的动作。
报错先归类再动手:400/422 一类通常指向请求体构造,401/403/404 一类通常指向密钥、模型名或 endpoint 路由,429/503/504 与超时、显存不足、队列积压一类通常指向资源与限流。适用场景是接口能返回结构化错误;操作动作是先原样保存状态码、错误码、message、request_id;验证方式是缩小到最小请求体后重跑,若仍失败则往路由或资源方向查;风险边界是错误码语义会随网关和 SDK 包装层变化,必须以自己环境里未二次转述的返回体为准。
保存返回体里的状态码、错误码与消息原文
很多“查不出原因”的报错,其实是信息在传递过程中被吃掉了。需要记录的字段建议固定成几项:HTTP 状态码、业务错误码(如果有)、message 原文、request_id 或 trace_id、请求时间与耗时、实际使用的模型名、以及请求体的摘要(不是全文,避免日志里塞进敏感字段)。
日志里容易丢信息的环节通常有这些:SDK 只把 message 包成一个异常,丢掉状态码和响应头;统一拦截器把不同错误重写成同一句“请求失败”;日志组件对超长 message 做截断;异步任务只记成功不记失败;网关侧只记状态码不记 body。排查时可以先在这些位置确认一遍,看看原始响应是否还能取到。
手动复现一次请求,是最省事的取证方式。用命令行直接打,保留响应头和响应体:
# 通用接入骨架,按自己环境的 endpoint 与鉴权方式替换
curl -i -X POST "$BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"<模型名>","messages":[{"role":"user","content":"ping"}]}' \
2>&1 | tee err_case_raw.txt
把输出原样落盘后,先看第一行状态码,再看 body 里的错误码字段。后续所有判断都以这个文件为准,而不是以聊天窗口里转述的一句话为准。
按错误码把问题分成入参、路由认证、资源三类
划分依据主要是错误码所在的区间和它的语义。下面这张表是常见的对应关系,具体数值以自己环境的返回为准:
| 状态码 / 错误码常见形态 | 大概率类别 | 典型特征 | 先做什么 |
|---|---|---|---|
| 400、422、字段校验类错误码 | 入参 | 提示某个字段名、类型或取值不合法 | 核对请求体字段名、类型、嵌套层级 |
| 401、403 | 路由认证 | 鉴权失败、无权限访问该模型 | 检查密钥、鉴权头格式、账号权限 |
| 404、路由不存在 | 路由认证 | 模型名写错或 endpoint 路径不符 | 核对模型名与接口路径 |
| 429 | 资源 / 限流 | 提示频率或并发超限,可能带重试建议 | 降并发、加退避、看队列 |
| 500、503、504、超时 | 资源 | 服务端异常、排队超时、显存不足 | 看服务侧日志、显存占用与队列长度 |
同一类错误在不同网关下可能被包装成不同的码,所以判断时要把状态码、错误码和 message 三者一起看。如果三条信息互相矛盾,或者拿不到结构化的错误码,处理方式是先把请求缩到最小:只留模型名和一条最简消息。最小请求仍然失败,基本可以排除字段构造问题,转向路由认证或资源;最小请求成功,则问题在缺失或多余的字段上。
入参类:逐项核对字段名、类型、必填与嵌套层级
请求体构造引起的失败占比不低,逐项过一遍比反复重启服务有效。核对清单可以按这个顺序走:
- 字段名拼写与大小写:接口通常区分大小写,
messages写成message就是两种结果。 - 类型是否匹配:字符串、数组、整数、布尔不要混用,例如把温度写成字符串。
- 必填项是否齐全:模型名、消息列表这两项通常不能缺。
- 嵌套层级是否正确:消息对象里的
role和content是否在同一层,数组是否少包了一层。 - 取值是否越界:温度、最大输出长度这类参数一般有取值范围。
- 流式开关与解析方式是否匹配:开了流式却按整包解析,容易表现为空结果而不是报错。
字段名写错时,报错表现分两种:一种是直接拒绝,message 里出现 unknown field、unexpected field 之类提示;另一种是被静默忽略,接口返回 200 但结果异常或为空。第二种更隐蔽,需要靠对比返回内容发现。做法是先发一个只含必填字段的最小请求体,确认能通,然后一次只加一个字段,每加一次跑一次,哪一步开始出错就定位到哪个字段。改动前后都把响应体保存下来,便于对照。
资源类:对照显存、并发数与队列长度判断限流或内存不足
资源问题要先分清是被限流还是本地或服务侧资源不够。观察方式通常有三种:看服务侧日志里的排队与耗时记录;看显存占用(自建部署时可用 nvidia-smi 之类的命令,按固定间隔采样,观察是否贴近上限);看客户端侧的并发数与重试次数,确认是不是自己把并发打高了。
两者在日志里的表现不太一样。限流一般表现为 429 或带频率字样的提示,请求往往在很短时间内就被拒绝,服务侧没有对应的计算记录;内存不足或显存不足则常表现为 500、进程重启、请求在中途超时,服务侧日志里能看到分配失败或中断的记录。把这两类日志放在一起看,基本能区分开。
验证步骤建议一次只改一个变量:先把并发降到 1,重跑同一请求,如果恢复正常,说明与并发相关;再逐步把批量减小,观察是否在大输入时复现。降并发或减批量只是定位手段,确认问题后需要结合环境决定容量配置、超时设置或重试退避策略,而不是把它当成长期方案。
构造最小复现用例并记录修复前后的差异
最小用例的构成尽量固定:一个不变的模型名、一条最简输入消息、固定的参数集合、固定的超时时间,以及把原始响应写入文件的动作。它要能在命令行或一个短脚本里重复执行,且不依赖上一步的中间状态。示例骨架如下,替换占位符后即可运行:
# 最小复现用例:只保留必填字段,输出落盘便于对比
python run_case.py \
`--model` "<模型名>" \
`--input` "ping" \
`--out` before.json
# 修复后重跑同一脚本,输出到另一个文件
python run_case.py \
`--model` "<模型名>" \
`--input` "ping" \
`--out` after.json
记录修复前后差异时,重点对比四项:状态码是否变化、错误码是否消失、message 内容是否改变、返回内容是否从空变为正常。把这些字段并排写在一张对照记录里,比只写“已修复”有用,因为下次出现相似报错可以直接比对。
回归时要重跑的场景清单建议至少覆盖:单条最简请求、之前失败的那条原始请求、带较长输入的请求、批量请求、流式请求,以及并发略高时的表现。每项都保留一次原始响应。如果某项仍然失败,说明修复只覆盖了一部分触发条件,按前面的分类重新判断是入参还是资源,再回去核对对应的清单。