解释为何HTTP 200不等于模型成功返回,介绍检查空响应、截断响应、缓存冒充的5步探测脚本,防止空内容进入下游导致静默失败。
没人记录的故障模式
HTTP 状态码 200 只能说明 HTTP 请求到达了服务器并获得了响应。它不能证明以下任何一点:
响应体中有可用的字节。
模型返回了非空字符串。
JSON 是完整的且可解析的。
回答是新鲜的,不是来自之前请求的缓存。
finish_reason 符合预期。
最危险的情况是静默失败:choices[0].message.content 为空,但请求在日志中看起来是成功的,所以下游解析器在之后失败,却没有明确的原因。
从确定性探测开始
发送一个最小请求,它应该总是产生文本。使用单个单词的响应提示、较低的 token 预算和硬超时。此探测假设 OpenAI 兼容的响应形状;如果你的提供商使用不同的契约,请适配字段路径。
#!/usr/bin/env bash
set -euo pipefail
ENDPOINT="${1:?usage: probe.sh <endpoint>}"
PAYLOAD='{"model":"test-model","messages":[{"role":"user","content":"Reply with the single word OK."}],"max_tokens":64,"stream":false}'
tmp=$(mktemp)
code=$(curl -sS -o "$tmp" -w '%{http_code}' \
-H 'content-type: application/json' \
--max-time 30 \
-d "$PAYLOAD" \
"$ENDPOINT")
bytes=$(wc -c < "$tmp")
content=$(jq -r '.choices[0].message.content? // empty' "$tmp" 2>/dev/null || true)
finish=$(jq -r '.choices[0].finish_reason? // empty' "$tmp" 2>/dev/null || true)
if [ "$code" != "200" ]; then
echo "TRANSPORT_FAIL code=$code"
elif [ "$bytes" -eq 0 ] || [ -z "$content" ]; then
echo "EMPTY code=$code bytes=$bytes finish=$finish"
elif [ "$finish" = "length" ]; then
echo "TRUNCATED_OR_STOP code=$code bytes=$bytes finish=$finish"
else
echo "OK code=$code bytes=$bytes finish=$finish content=${content:0:40}"
fi
rm -f "$tmp"
脚本返回四种状态:TRANSPORT_FAIL、EMPTY、TRUNCATED_OR_STOP 或 OK。重点不是修复端点;而是在响应进入你的流水线之前,让静默失败变得可见。
免费端点是测试表面,不是契约
MonkeyCode 的免费模型访问和免费服务器选项使得此探测成本很低。免费服务器对冒烟测试和临时 CI 任务很有用,但应该将其视为非托管测试表面:预料冷启动、共享容量和偶尔的静默响应。
披露:本文是 MonkeyCode 产品推广的一部分。
当你将此探测指向免费模型或免费服务器时,请使用相同的规则:
保持 payload 确定性且不含密钥。
设置硬超时,然后停止;不要在速率限制周围循环。
在解析之前将原始响应保存为 CI artifact。
使用 nonce 重试一次,而不是使用完全相同的提示词。
比较热运行和冷运行的内容。
探测不能告诉你的事
此检查涉及传输和响应结构。它不会评分输出质量、检测幻觉、衡量长期稳定性或保证隐私。将通过探测视为前置条件,而不是结果。
可重复的冒烟序列
首次接触新的免费端点时,按以下顺序操作:
使用唯一的 nonce 发送单词请求。
记录 code、bytes、finish_reason 和总时间。
等待 60 秒,使用新的 nonce 发送相同的提示词。
分别用 stream=false 和 stream=true 各运行一次。
对比每个 nonce 返回的内容差异。
如果任何步骤返回 EMPTY 或 TRUNCATED_OR_STOP,不要将响应输入到更大的工作流中。先修复请求形状。
当你需要严格可用性、稳定延迟、批处理吞吐量、私有数据处理或保证响应体时,不要使用免费模型或免费服务器。免费层级适用于评估、冒烟测试和调试,不适用于生产保证。
200 是检查的开始,不是结束。五个小信号——状态码、字节数、内容、finish reason 和新鲜度——将不可见的失败转化为可重复的分类步骤。