背景
with_retry 负责临时错误(429/529)的指数退避,并在连续 529 达到 MAX_CONSECUTIVE_529 后切换到 FALLBACK_MODEL。with_retry 这一侧的切换逻辑(state.current_model = FALLBACK_MODEL)本身是对的。
但 LLM 调用是通过 lambda 传入 with_retry 的,其中 model 用默认参数 mdl=state.current_model 捕获。该写法在单模型、未配置 fallback 的常规路径下工作正常,但一旦触发 fallback,切换不会生效:虽然state.current_model 已被改为 FALLBACK_MODEL,实际请求却仍发往主模型,因为被lambda默认参数在定义时按值冻结。
改动一览
lambda 默认参数 mdl=state.current_model → 调用时读取 state.current_model
问题:默认参数 mdl=state.current_model 只在 lambda 定义时求值一次,被冻结为 PRIMARY_MODEL。with_retry 在 retry 循环里反复调用的是同一个 lambda 对象,默认参数不会随调用重算。因此即便 fallback 分支修改了 state.current_model,callback 内 model=mdl 读到的仍是被冻结住的旧值。
ps:该 lambda 在 while True 每轮迭代会重建,理论上下一轮可读到新模型;但 with_retry 在切换后仍于当轮 retry 循环内用旧模型继续重试,直至 MAX_RETRIES 耗尽抛 RuntimeError,被外层 except 判为 unrecoverable 并 return——loop 往往在回到循环顶部、有机会用上新模型之前就已退出。
修复:让 callback 在调用时读取 state.current_model,不再用默认参数冻结。
影响范围
对单模型 / 未配置 FALLBACK_MODEL 的运行:行为完全一致,无 regression。
对持续 529 触发 fallback 的场景:从"切换无效 → 耗尽重试抛 RuntimeError → 被判 unrecoverable 退出"改为"真正切到 FALLBACK_MODEL 继续请求"。
代码
call site 建议如下改动(with_retry 内部无需改动,已经是正确的):
# before:mdl 在 lambda 定义时被冻结,fallback 切换读不到
response = with_retry(
lambda mt=max_tokens, mdl=state.current_model:
client.messages.create(
model=mdl, system=system, messages=messages,
tools=TOOLS, max_tokens=mt),
state)
# after:改为具名 def,model 在调用时读取,fallback 生效
def call_model():
return client.messages.create(
model=state.current_model, # 调用时读取,fallback 才能生效
system=system, messages=messages,
tools=TOOLS, max_tokens=max_tokens)
response = with_retry(call_model, state)
或者如果保留 lambda,可以仅删除 mdl 默认参数
response = with_retry(
lambda mt=max_tokens: # 只冻结 max_tokens,不冻结 model
client.messages.create(
model=state.current_model, # 关键:改为调用时读取
system=system, messages=messages,
tools=TOOLS, max_tokens=mt),
state)
注:目的是max_tokens 在重试期间不被 with_retry 修改,保留 mt=max_tokens 冻结无影响;但 model 必须改为调用时读取。
背景
with_retry负责临时错误(429/529)的指数退避,并在连续 529 达到MAX_CONSECUTIVE_529后切换到FALLBACK_MODEL。with_retry这一侧的切换逻辑(state.current_model = FALLBACK_MODEL)本身是对的。但 LLM 调用是通过 lambda 传入
with_retry的,其中model用默认参数mdl=state.current_model捕获。该写法在单模型、未配置 fallback 的常规路径下工作正常,但一旦触发 fallback,切换不会生效:虽然state.current_model已被改为FALLBACK_MODEL,实际请求却仍发往主模型,因为被lambda默认参数在定义时按值冻结。改动一览
lambda 默认参数
mdl=state.current_model→ 调用时读取state.current_model问题:默认参数
mdl=state.current_model只在 lambda 定义时求值一次,被冻结为PRIMARY_MODEL。with_retry在 retry 循环里反复调用的是同一个 lambda 对象,默认参数不会随调用重算。因此即便 fallback 分支修改了state.current_model,callback 内model=mdl读到的仍是被冻结住的旧值。ps:该 lambda 在
while True每轮迭代会重建,理论上下一轮可读到新模型;但with_retry在切换后仍于当轮 retry 循环内用旧模型继续重试,直至MAX_RETRIES耗尽抛RuntimeError,被外层except判为 unrecoverable 并return——loop 往往在回到循环顶部、有机会用上新模型之前就已退出。修复:让 callback 在调用时读取
state.current_model,不再用默认参数冻结。影响范围
对单模型 / 未配置
FALLBACK_MODEL的运行:行为完全一致,无 regression。对持续 529 触发 fallback 的场景:从"切换无效 → 耗尽重试抛
RuntimeError→ 被判 unrecoverable 退出"改为"真正切到FALLBACK_MODEL继续请求"。代码
call site 建议如下改动(
with_retry内部无需改动,已经是正确的):或者如果保留 lambda,可以仅删除 mdl 默认参数