EveryInfra 调用偶发失败时,先不要改代码逻辑。建议按“密钥 → 环境变量 → 调用地址”三层逐层排除:确认密钥是否从环境变量读取、同一份请求在测试与线上配置里差在哪几个字段、绕开 SDK 手工发一次请求看原始返回。三层里任一层没对齐,表现都可能像“偶发失败”,但处理动作完全不同。判断边界是:只处理能通过配置差异、日志字段、状态码原文验证的问题;如果三层都对齐仍失败,再考虑服务端限流或区域网络,不在本页范围。
EveryInfra 接入失败先分层,不要混着改。第一层看密钥是否硬编码、变量缺失时进程是否直接报错退出;第二层用同一份请求分别打测试与线上,逐字段比对调用地址、超时、重试次数;第三层用 curl 绕开 SDK,排除自动重试和自动补头。每层都要留下状态码原文和响应头,鉴权失败与地址失败分开记录。适用场景是配置驱动的接入排查,风险边界是手工请求通过而 SDK 仍失败时,问题在客户端库配置而非密钥或地址。
把调用示例里的密钥改成从环境变量读取
接入示例里最常见的隐患是密钥写在代码或配置文件里。换环境时要改代码,代码进了版本库还会把密钥带出去。建议把所有密钥统一从环境变量读取,代码里只保留变量名。下面是一段最小调用骨架,密钥取自环境变量,语言和 SDK 名称按你实际使用的替换,结构保留即可。
import os
import sys
API_KEY = os.environ.get("EVERYINFRA_API_KEY")
BASE_URL = os.environ.get("EVERYINFRA_BASE_URL", "https://api.example.com")
if not API_KEY:
# 变量缺失时立刻失败,不要带着空密钥继续发请求
print("missing env: EVERYINFRA_API_KEY", file=sys.stderr)
sys.exit(1)
headers = {"Authorization": "Bearer " + API_KEY}
payload = {"model": "default", "input": "ping"}
# 这里换成实际 SDK 或 HTTP 客户端调用
resp = client.post(BASE_URL + "/v1/invoke", json=payload, headers=headers)
print(resp.status_code)
print(resp.text[:500])变量缺失时的报错形态要固定成一种:要么在启动阶段直接退出并打印变量名,要么在首次调用前抛出明确异常。不建议让空密钥进入请求链路,否则上游可能返回 401,看起来像密钥错了,实际是变量没加载。验证方式:清空变量后启动进程,观察它是否在发请求之前就退出;再用 env | grep EVERYINFRA 确认当前 shell 里确实有值。风险边界是容器、定时任务、IDE 运行配置各自的环境变量来源不同,本地能读到的变量在部署环境里不一定存在,需要结合部署方式确认。
用同一份请求分别打向测试与线上配置,逐字段记录差异
“偶发失败”很多时候不是偶发,而是两个环境配置不同,请求恰好落在不同分支。建议准备同一份请求体,分别用测试配置和线上配置各发一次,把返回的状态码原文抄下来对照。下面的字段表可以直接照着填,占位值替换成你的实际配置。
| 字段 | 测试配置 | 线上配置 | 差异说明 |
|---|---|---|---|
| 调用地址 | https://api-test.example.com | https://api.example.com | 域名或路径前缀不同,需确认路径是否带版本号 |
| 超时时间 | 30s | 10s | 线上超时更短,慢请求会被本地截断 |
| 重试次数 | 1 | 3 | 线上重试更多,失败耗时被放大 |
| 返回状态码 | 填实际返回原文 | 填实际返回原文 | 只有两边都抄原文才能比对 |
对照时重点看三类差异:调用地址是否指向不同端点、超时是否短于正常响应时间、重试次数是否让超时被重复触发。状态码要抄原文,不要写成“失败了”或“报错了”,因为 401、403、404、429、5xx 的排查方向不同。超时和重试次数在客户端库默认值里也常常不同,需要结合所用库的版本确认。
用 curl 或等价命令手工发一次请求,绕开 SDK 封装
SDK 会自动重试、自动补请求头、自动改写路径,这些行为会掩盖真实返回。建议用 curl 手工发一次同参数请求,看原始状态码和响应头。命令里的密钥仍从环境变量取,避免出现在命令历史里。
curl -i -X POST "$EVERYINFRA_BASE_URL/v1/invoke" \
-H "Authorization: Bearer $EVERYINFRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"default","input":"ping"}'执行后记录两件事:手工请求的完整状态行和响应头;同一参数下 SDK 请求的返回。常见差异是 SDK 返回了重试后的最终结果,而手工请求只看到第一次的 4xx;或者 SDK 补了某个头,手工请求缺这个头导致失败。如果手工请求成功而 SDK 失败,问题多半在客户端库配置,不在密钥或地址;如果手工请求也失败,说明配置层确有错误,继续按状态码定位。命令里的路径以你实际文档为准,不要照搬这里的占位路径。
在返回体与响应头里区分鉴权失败与地址失败
鉴权失败和地址失败的表现容易混:两者都可能返回 4xx,但改动方向完全相反。建议每次失败都记录状态码、响应体里的错误码字段、以及 WWW-Authenticate 或类似响应头是否存在。下面的对应表只写判断依据,结论来源一栏要如实标注是实际返回还是文档描述。
| 状态码 | 典型错误信息 | 指向 | 结论来源 |
|---|---|---|---|
| 401 | unauthorized / invalid api key | 密钥缺失、过期或格式不对 | 实际返回时填入 |
| 403 | forbidden / insufficient permission | 密钥有效但无权访问该模型 | 实际返回时填入 |
| 404 | not found / no route | 调用地址或路径前缀错误 | 实际返回时填入 |
| 429 | rate limited | 频率限制,不是配置错误 | 文档描述 |
响应头里的鉴权挑战头通常只在鉴权失败时出现,地址错误一般不会有。遇到 404 先查路径和版本前缀,不要先换密钥;遇到 401 先查变量是否加载和密钥是否过期,不要先改域名。表里的错误文本是常见形态,具体文案以 EveryInfra 实际返回为准,不要把括号里的英文当成固定字符串去匹配。
给调用加一次带日志的重试,观察失败是否收敛
加一次有限重试的目的不是提升成功率,而是判断失败是瞬时抖动还是稳定存在的配置错误。建议只对超时和 5xx 重试,鉴权类 4xx 不重试;每次尝试都写日志,字段至少包含时间、状态码、耗时。
for attempt in range(2):
start = time.time()
try:
resp = client.post(url, json=payload, headers=headers, timeout=10)
cost = round((time.time() - start) * 1000)
log.info("attempt=%s status=%s cost_ms=%s", attempt, resp.status_code, cost)
if resp.status_code < 500:
break
except TimeoutError:
cost = round((time.time() - start) * 1000)
log.warning("attempt=%s status=timeout cost_ms=%s", attempt, cost)两次结果对比看三点:两次都是同一个 4xx,说明配置错误稳定存在,重试无用;第一次超时第二次成功,更像网络抖动或服务端慢启动;两次都超时且耗时接近设定的超时值,要回头核对第二节里的超时配置是否短于正常响应。日志里保留时间、状态码、耗时三个字段,事后能对上两次尝试的间隔。这套做法只用于定位,不建议把重试当作长期补偿手段。