接入 HappyOyster 1.0 后报错,先别猜“是不是生成太慢”。判断顺序建议是:请求有没有真正发出去、有没有拿到 HTTP 响应、响应码落在哪一段。拿到 4xx 或 200 但 body 里带错误字段,优先查请求格式、字段名和鉴权;连接阶段就失败,查地址、端口、TLS 和连接超时;请求发出成功却长时间没有结果,才归到生成等待超时。HappyOyster 1.0 的具体字段名、路径和参数以官方文档为准,下面的 URL、header、body 都只是占位。
报错定位建议按“连接层 → 请求格式层 → 生成等待层”三段走:先确认能发出请求并拿到响应码,再按 4xx/5xx 分层判断,最后才单独计时生成阶段。请求体字段、路径、鉴权方式以官方文档为准,本篇给出的 URL、header、body 均为占位,需要结合你自己的网关、SDK 和日志环境替换后验证。
先固定一个最小请求骨架
先把业务代码放到一边,用一个最小请求确认“能不能发出去”。这一步的目的不是验证生成质量,而是排除掉序列化、重试、中间件带来的干扰。下面的骨架字段是占位,替换成官方文档给出的真实路径和字段名。
# 只用于验证请求能否发出,字段以官方文档为准
curl -i -X POST 'https://<happy-oyster-host>/<api-path>' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-H 'X-Request-Id: <uuid>' \
`--connect-timeout` 5 `--max-time` 15 \
-d '{"model":"<model-name>","input":"hello","stream":false}'执行后重点看三件事:是否打印了 HTTP 状态行、响应体开头是什么、总耗时落在哪个量级。如果这一步就失败,问题在地址、鉴权或网络,跟生成超时无关。
用状态码和响应体分层错误
HTTP 状态码是最快的分层依据,但要注意有些服务会把应用层错误包在 200 里面,所以响应体也要看。
| 状态码/现象 | 常见原因 | 先查什么 |
|---|---|---|
| 连接失败、无响应 | DNS、端口、TLS、网络策略 | connect timeout 是否触发 |
| 400 / 422 | 请求体格式、字段名、必填项缺失 | body 是否被改写 |
| 401 / 403 | token 过期、权限不足 | Authorization 头 |
| 404 | 路径或路由不对 | URL 占位是否替换 |
| 409 / 429 | 幂等冲突或频率限制 | 重试策略 |
| 500 / 502 / 503 | 服务端或网关内部问题 | request-id 后的服务端日志 |
| 200 但 body 含 error | 应用层错误 | 响应体完整内容 |
| read timeout | 生成等待超过 read 限制 | read timeout 设置 |
日志记录点建议至少包含:请求发起时间、URL、状态码、耗时、request-id、响应体前若干字节。有 request-id 的时候,把它带到服务端日志里对齐,比反复猜字段更省时间。
单独测生成超时
把“请求发出成功”和“等待生成完成”分开计时,是区分格式问题和超时问题的关键。connect timeout 管建连,read timeout 管建连之后等响应的时间,两者设置不同,报错含义也不同。
import time, requests
t0 = time.time()
try:
r = requests.post(
"https://<host>/<api-path>",
headers={"Authorization": "Bearer <TOKEN>",
"Content-Type": "application/json"},
json={"model": "<model-name>", "input": "<prompt>", "stream": False},
timeout=(5, 5), # (connect, read) 占位,read 按官方建议与实际场景调大
)
print("status", r.status_code, "elapsed", round(time.time()-t0, 2))
print(r.text[:500])
except requests.exceptions.ConnectTimeout:
print("connect timeout", round(time.time()-t0, 2))
except requests.exceptions.ReadTimeout:
print("read timeout", round(time.time()-t0, 2))验证方式:先把 read timeout 设成一个明显偏小的值,观察是否稳定打印 read timeout;再把 connect timeout 设小并指向一个不可达地址,观察是否稳定打印 connect timeout。两次结果能稳定复现,说明超时配置本身在生效,再去判断超时值是否合理。
检查管线中的路由与序列化
现有管线通常会经过业务代码、SDK、网关、队列消费者等多跳,字段被改写或丢弃是常见原因。做法是每一跳都打印同一个请求体指纹,对比长度和哈希。
import hashlib, json
def fp(payload: dict) -> str:
body = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode()
return f"{len(body)}:{hashlib.sha256(body).hexdigest()[:12]}"
print("hop=sdk", fp(payload))
print("hop=gateway", fp(payload))
print("hop=consumer", fp(payload))如果某两跳之间长度或哈希变化,就在那一段查序列化:布尔值是否被转成字符串、null 字段是否被剔除、时间戳格式是否被改、数组是否被拍平。字段丢失检查可以拿官方文档里的必填字段列表逐项比对,比凭印象猜测更可靠。
做一次最小回放
把报错时的请求体和响应原样保存下来,用固定 body 回放,可以把随机因素和真实问题分开。
# saved_request.json 里放之前保存的请求体
curl -i -X POST 'https://<host>/<api-path>' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'X-Replay: 1' \
`--data-binary` @saved_request.json验证方式:同一个请求体连续发两次,比较状态码、响应体和耗时。两次都稳定失败,偏向请求格式、字段或路由问题;第一次成功、第二次失败,或只在某一时长后失败,更偏向生成等待超时、限流或服务端波动。回放时注意去掉本地加的重试和缓存,否则看到的不是原始错误。