调用 Claude Sonnet 5.5 出现超时、返回被截断、字段对不上,通常不是同一个原因,而是链路不同位置的三类故障:超时属于时间与连接层,截断属于模型生成终止,字段错位属于请求组装或响应解析。把它们混在一起查,容易出现「改了超时配置,截断照旧」的情况。建议拆开处理,各自去找对应的那份记录:客户端日志、网关(或代理、中间层)日志、以及返回体本身。
三类故障要分别取证:超时先看客户端与网关两侧的时间戳和连接关闭方式,判断请求是没送到还是送出去没等到;截断看返回体里的停止原因与用量字段;字段错位做请求体与响应体字段名的逐项对照,对不上的先标为待确认。三份记录各自留证后汇总成一条结论,再改到具体配置项,最后用同一条提示重放验证。
在客户端与网关日志里区分超时和连接被断
先回答一个问题:请求到底有没有送出去。客户端日志(你发起调用的那一侧)一般能看到发起时间、连接建立时间、首字节时间、读超时时间,以及抛出的异常类型;网关或中间层日志一般能看到收到请求的时间、转发到上游的时间、上游首字节时间、状态码,以及连接是被谁关掉的。两边的 request_id / trace_id 要能对上,对不上就先解决链路标识不统一的问题。
判断依据可以按这个思路走:网关日志里完全没有对应 request_id 的记录,说明请求没送到,问题在客户端侧的地址、鉴权头、网络出站或中间层的路由规则;网关有收到请求、但没有完整的响应完成记录,或者记录了上游超时、连接重置、连接被对端关闭,说明送出去了没等到结果,这时要区分是上游处理慢,还是中间层自己的读超时设得比上游慢。
- 客户端侧必看:request_id、发起时间、连接建立时间、首字节时间、超时/连接类异常名、是否发生过重试。
- 网关侧必看:request_id、收到请求时间、转发时间、上游首字节时间、HTTP 状态码、上游错误字段、连接是否正常结束。
- 两侧都不带 request_id 时,先补上这一个字段,否则后面所有对照都做不了。
排查顺序可以按下面这张表走,一次只推进一格,避免同时改多处。
| 顺序 | 先看什么 | 关键字段 | 判定走向 |
|---|---|---|---|
| 1 | 客户端日志 | request_id、异常类型 | 有无本地连接/鉴权错误 |
| 2 | 网关日志 | request_id、收到时间、上游首字节 | 请求是否送达 |
| 3 | 返回体 | 停止原因、用量字段 | 是否被截停 |
| 4 | 请求体 / 响应体 | 字段名、层级、类型 | 错位在发送侧还是解析侧 |
| 5 | 重放 | 固定参数、每次 request_id | 偶发还是稳定复现 |
在返回体里读停止原因和用量字段
收到响应后,不要只判断 HTTP 状态码是不是 200。正常结束和提前截停,通常都返回成功状态,差别在响应体的停止原因和用量字段上。具体字段名以你实际接入版本的响应结构为准,先原样打印完整响应体,再按下面的位置找。
{
"stop_reason": "<停止原因,例如到达生成长度上限或被停止符命中>",
"stop_sequence": "<命中的停止符,未命中时为空>",
"usage": {
"input_tokens": "<输入用量>",
"output_tokens": "<输出用量>"
}
}
读取方式:停止原因指向长度上限或停止符时,属于被截停,需要结合你设置的生成长度上限、停止符和提示词一起看;停止原因指向正常结束,才按「这次调用是完整的」处理。流式返回时,还要看最后一条事件里有没有明确的结束标志,只看中途收到的文本块会误判。用量字段用来交叉验证:输出用量贴着上限,基本可以认定是长度卡住。
字段缺失时不要默认正常结束,先在日志里标注为待确认,例如记成 stop_reason=missing(待确认)、usage.output_tokens=missing(待确认)。标注之后再去看是不是接入层自己把响应体裁剪过、或者解析代码只取了部分键。
把请求体与响应体字段名逐个对照
字段对不上,多半不是模型返回错,而是发送侧的名字或层级和解析侧预期的不一致。做法很土但有效:把实际发出的请求体和实际收到的响应体各自原样打一份,然后逐行填对照表。
| 用途 | 请求体字段名 | 响应体/解析侧字段名 | 类型 | 是否必需 | 是否一致 |
|---|---|---|---|---|---|
| 模型标识 | model | model | 字符串 | 是 | |
| 输入内容 | messages / content | content | 数组或字符串 | 是 | |
| 生成长度上限 | max_tokens | usage.output_tokens | 整数 | 视接入方式 | |
| 停止符 | stop_sequences | stop_sequence | 数组 / 字符串 | 否 |
填表时注意三点:名字大小写与下划线、驼峰是否一致;是数组还是对象还是字符串;嵌套在哪一层。对不上的行标出来,不要凭印象改代码。常见的两种不一致来源,一是发送侧按文档写错了名字或层级,比如把生成长度限制写在错误的层级里,或者把该传数组的传成了字符串;二是解析侧沿用了旧版本结构,按旧键名取值,或者用了严格模式的反序列化,多一个字段就直接失败。
用同一条提示重放失败请求
确认稳定复现还是偶发,唯一靠得住的办法是重放。重放时只允许改变量,其余参数固定:模型标识、完整输入内容、生成长度上限、停止符、是否流式、调用超时值、鉴权头的来源方式(不要把密钥本体写进记录),以及最重要的——把自动重试关掉,否则你看到的是重试后的结果。
- 建议连续重放 3 到 5 次,间隔不要靠得太近,每次记录 request_id、发起时间、耗时、停止原因和用量。
- 每次都失败且失败点相同,按稳定复现处理,直接进入字段对照和配置核对。
- 只有部分失败,按偶发处理,重点看失败那几次的网关日志和网络侧,而不是改提示词。
- 重放仍无法复现时,把「无法复现」写进结论,并保留当次原始请求体用于后续追踪。
把三类记录汇总成一次排查结论并回写配置
观察停在日志里没有意义,结论要落到「改哪里、怎么验证」。可以按下面这个格式写在工单、变更记录或配置文件的注释里,四段齐全才算出结论。
现象:调用在等待一段时间后中断,客户端报超时;另有部分请求返回成功但文本明显不完整。
证据位置:客户端日志(含 request_id、异常类型);网关日志(收到请求时间、上游首字节时间、上游错误字段);返回体(停止原因、用量字段)。
改动点:<明确的配置项>,例如客户端读超时值、网关上游超时值、生成长度上限、解析层的字段映射表。
验证结果:用同一条提示重放,记录每次 request_id 与停止原因;确认改动后现象是否消失,未消失则回到对应的那一处记录继续查。
回写时注意边界:调大超时值只影响等待时长,不会让上游变快;调大生成长度上限会让输出变长,也可能带来新的截断判断问题。改动一次只动一个配置项,并在结论里写清这次改的是哪一处记录对应的哪一个字段,下一次出现同类现象时,才能顺着这份结论直接定位。