直接调用模型接口时,偶发超时、限流和上游 5xx 是最常见的异常。本文用一个小型 Python 客户端,把超时边界、有限重试和错误分类放到同一层,方便后续接入业务。
设置连接与读取超时
请求必须有明确的时间边界,连接超时和读取超时可以分别控制。
python
import requests
session = requests.Session()
response = session.post(
"https://your-api-endpoint.example/v1/messages",
headers={
"x-api-key": "sk-your-key",
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
json={
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "连通性测试"}],
},
timeout=(5, 60),
)
response.raise_for_status()
如果通过 jiekou.vip等接入平台调用,只需按照对应文档替换地址和认证配置,不要把配置写死在业务函数中。
按错误类型决定动作
认证失败和参数错误不适合重试;429、网络异常以及部分 5xx 则可以有限重试。
python
import random
import time
RETRYABLE_STATUS = {429, 500, 502, 503, 504}
def request_with_retry(send, attempts=4):
for attempt in range(attempts):
try:
response = send()
if response.status_code not in RETRYABLE_STATUS:
response.raise_for_status()
return response
except (requests.Timeout, requests.ConnectionError):
if attempt == attempts - 1:
raise
else:
if attempt == attempts - 1:
response.raise_for_status()
time.sleep(min(8, 2 ** attempt) + random.uniform(0, 0.3))
指数退避配合随机抖动,可以避免多个实例同时重试。
统一错误分类
业务层只需要关心错误类别,不必到处判断底层异常文本。
python
from dataclasses import dataclass
@dataclass
class ApiError:
category: str
status_code: int | None
retryable: bool
message: str
def classify_error(exc=None, response=None):
if isinstance(exc, requests.Timeout):
return ApiError("timeout", None, True, str(exc))
if isinstance(exc, requests.ConnectionError):
return ApiError("connection", None, True, str(exc))
status = response.status_code
if status == 401:
return ApiError("authentication", status, False, response.text)
if status == 429:
return ApiError("rate_limit", status, True, response.text)
if status >= 500:
return ApiError("upstream", status, True, response.text)
return ApiError("request", status, False, response.text)
日志和告警可以按 category 聚合,比依赖变化的错误文案更稳定。
记录调用边界
至少记录请求 ID、模型、耗时、状态码和最终错误类别。密钥不能写入日志,用户提示词也不应整段落盘。
python
import logging
import time
import uuid
logger = logging.getLogger("model_api")
def call_model(send, model):
request_id = uuid.uuid4().hex
started = time.perf_counter()
try:
response = request_with_retry(send)
logger.info(
"model_call_ok",
extra={
"request_id": request_id,
"model": model,
"status_code": response.status_code,
"duration_ms": round((time.perf_counter() - started) * 1000),
},
)
return response.json()
except Exception:
logger.exception(
"model_call_failed",
extra={
"request_id": request_id,
"model": model,
"duration_ms": round((time.perf_counter() - started) * 1000),
},
)
raise
验证故障路径
上线前至少模拟三种情况:把读取超时调小,确认超时会重试;使用错误密钥,确认 401 只失败一次;模拟 429 和 503,确认退避会增加并最终停止。
把这些边界补齐后,模型调用就有了明确的失败处理方式,后续接入业务时也更容易定位问题。