你在本地跑通了第一段 Python 请求,但一放进项目就出问题:偶尔卡住不返回、批量跑一半被限流、报错信息看不出哪一步挂了、换个模型整个脚本要重写。这篇不讲协议原理,只讲把请求包成能上线的函数该怎么做——参数怎么设、异常怎么兜、并发怎么控、日志记什么、上线前拿什么自测。
你在本地跑通了第一段 Python 请求,但一放进项目就出问题:偶尔卡住不返回、批量跑一半被限流、报错信息看不出哪一步挂了、换个模型整个脚本要重写。这篇不讲协议原理,只讲把请求包成能上线的函数该怎么做——参数怎么设、异常怎么兜、并发怎么控、日志记什么、上线前拿什么自测。
先把心态摆正:一次请求成功,只能说明这一次没踩到边界,不代表你的函数是稳定的。下面每一节都按「先写什么、怎么验证、什么情况下不适用」来讲,你照着做能少走很多弯路。
先写一个能跑的请求函数,再往上加东西
不管是 requests 还是官方 SDK,第一步都是把一次同步请求封成函数。关键是把四个变量单独拎出来做成配置:接口地址、鉴权头、模型名、消息体。不要把它们散落在业务代码里,否则换一次源你要翻遍全项目。字段含义可以到 基础字段词典(/ai-dict/category/basics) 先混个脸熟,字段名拼错是最常见的低级报错。
- 先跑通非流式,确认能拿到完整返回,再改流式;流式挂掉时先退回非流式,判断是网络问题还是解析问题
- 请求函数只做两件事:发请求、返回结构化结果或抛明确异常,不要在函数里顺手写业务逻辑
- 把「能跑」和「稳定跑」分开看:一次成功只能说明这次没触发边界,不能当成参数设对了
- 函数签名里把超时、重试次数留成可选参数,后面几节才有地方加东西
超时、重试和退避:三个必须写死的值
新手最容易犯的错是全套默认值:连接超时和读取超时分开设,读取超时按你最慢那档模型的响应时间留余量,别用一个默认值走天下。具体怎么分档,可以对照 超时调优(/api-timeout-tuning) 里的思路,按你的实际请求形态取值,不要照抄别人的数字。
| 参数 | 设成什么 | 不设会怎样 |
|---|---|---|
| 连接超时 | 短一些,覆盖握手阶段 | 网络差时线程一直挂住 |
| 读取超时 | 按最慢模型留余量 | 慢响应被当成失败反复重试 |
| 总时长预算 | 重试次数 × 单次超时不能超过它 | 一个卡住的请求拖住整批任务 |
| 退避上限 | 要有上限和抖动 | 批量时请求挤在同一秒,更容易被限流 |
重试只对可恢复的错误做:连接失败、网关错误、限流。参数错误、鉴权失败重试多少次都一样,纯属浪费额度。退避要带随机抖动,固定间隔在批量场景下反而更容易撞上限流。
- 设两个超时连接超时和读取超时分开放,读取超时先按你观察到的慢请求上限乘一个安全系数
- 区分可重试错误把异常按网络层、鉴权层、参数层、限流层分类,只对前两者里的网络类和限流类重试
- 加退避和抖动每次重试等待时间递增,并对等待时间加随机扰动,避免多任务同时重试
- 设总预算算出最坏情况总耗时,超过预算就放弃并记录,不要无限重试
并发别凭感觉调,先量再压
很多人一上来就把并发调到很高,结果失败率飙升还以为是接口问题。正确顺序是先单线程跑一批,看单次耗时和失败率,这就是你的基线。基线都不稳就别急着加并发。限流的具体机制和恢复方式可以对照 限流恢复(/api-rate-limit-recovery),照着排查比你瞎猜快。
- 并发数从低往高试,盯失败率和延迟,出现明显拐点就回退一档
- 队列要有背压:积压超过阈值就暂停投递,而不是无限堆内存
- 批量任务记录每条的成功、失败、重试次数,失败后只重投失败项,不要整批重跑
- 给每个任务打上来源标签,方便区分是接口慢还是你自己的代码慢
# 思路示意,不是完整代码
results = []
for item in batch:
try:
r = call_api(item, timeout=(3, 30))
results.append((item, 'ok', r))
except RetryableError as e:
results.append((item, 'retry', str(e)))
except FatalError as e:
results.append((item, 'fail', str(e)))
# 失败项单独收集,下一轮只跑 fail/retry 的降级方案要在写代码时就留好口子
降级不是出事了才想,而是写代码时就留好口子。模型名、超时、并发数全部走配置,不要硬编码在业务逻辑里,换源时才不用改代码。准备「换模型」和「降级到非流式或更短输出」两条退路,触发条件写成明确规则:连续失败几次、延迟超过多少。不要写成「感觉不行就换」。
- 换模型退路
- 同一任务准备一个能力相近的备选源,触发条件写成连续失败次数或延迟阈值
- 降级输出
- 流式失败退回非流式,或缩短最大输出长度,保证任务能出结果
- 格式容忍
- 多源切换时字段可能缺失,解析层用默认值兜底,别假设两家格式完全一致
- 降级日志
- 降级发生时至少打一条日志,否则你会在几天后才发现一直在用备用方案
需要注意,降级本身会带来体验差异,如果你的业务对输出质量敏感,降级后要么提示用户,要么走异步补偿,不要静默换掉还在用结果的下游。这条在什么情况下不适用?如果任务本身可重试且不面向用户,静默降级问题不大。
日志记什么、上线前自测什么
日志不是越多越好,而是要能在事后复盘一次慢请求和一次贵请求。每次请求至少记:时间、模型、耗时、是否流式、输入输出量、状态码、错误类型。字段清单和回放思路可以参考 日志字段与回放(/api-log-fields-replay),照着对齐一遍能发现你缺了哪些关键字段。
| 自测用例 | 怎么造 | 看什么 |
|---|---|---|
| 正常请求 | 用一条普通输入 | 能否稳定返回、耗时是否在预期内 |
| 错误密钥 | 故意写错鉴权信息 | 报错是否可读、是否被归为鉴权类而不是网络类 |
| 超长输入 | 塞一条明显超限的内容 | 是否被正确拒绝、错误信息是否提示具体原因 |
| 网络抖动 | 手动断开或代理延迟 | 超时和重试是否按预期触发,日志能不能还原过程 |
对成本敏感的场景,先用 成本估算工具(/tools/model-cost-calculator) 按你的请求形态估一遍,再决定并发上限和重试策略。具体单价和分档数字不要凭记忆,去 价格追踪(/price-watch) 核对当时的数字,因为不同时间、不同源都可能变。
下一步如果你已经能稳定发请求,接下来该确认你用的源本身靠不靠谱——延迟、可用性和价格都值得交叉看一眼,再决定要不要固定用某一家。→常见问题
用 requests 还是用官方 SDK?
先用 requests 手写一遍,你会被迫看清 URL、鉴权头、请求体这三样东西,排错时知道去哪找。跑通之后如果 SDK 提供重试和流式解析再换过去,但别一上来就用 SDK,不然报错在封装层里你根本看不出是哪一步。注意有些 SDK 只认自家协议,换源时要确认兼容范围。
报错信息看不懂,从哪里开始查?
按状态码分层查:401/403 是鉴权和权限,先核对 key 和 base_url;400 多半是请求体字段问题,把 body 原样打出来对一遍字段名;429 是限流,看并发和频率;502/504 是网关侧,先确认不是自己超时太短触发重试雪崩。按这个顺序查,比反复改代码快得多。
本地能跑,放到服务器就失败,怎么办?
先排除环境差异:服务器能不能访问目标域名、有没有走代理、环境变量里的 key 有没有带进去、时间是否同步。逐项验证,别一次改多个变量。另外服务器上的读取超时往往比本地更早触发,把超时值调大一点再测一次,能区分是网络问题还是配置问题。