ModelRetryMiddleware / ToolRetryMiddleware: `retry_on=SomeError` (a bare class) retries every exception

当你在 ModelRetryMiddleware 或 ToolRetryMiddleware 中把 retry_on 写成单个异常类(如 retry_on=TimeoutError )而不是元组时,所有异常都会被重试,即使是不该重试的 KeyError 、 ValueError 、鉴权错误等也会跑满

快速结论:当你在 ModelRetryMiddleware 或 ToolRetryMiddleware 中把 retry_on 写成单个异常类(如 retry_on=TimeoutError)而不是元组时,所有异常都会被重试,即使是不该重试的 KeyError、ValueError、鉴权错误等也会跑满重试预算并进入 on_failure。优先检查 retry_on 的传参形式,并把 langchain-core / langchain 更新到包含该修复的版本。

适用环境:Issue 确认涉及 langchain 与 langchain-core;触发位置为 libs/langchain_v1/langchain/agents/middleware/_retry.py。Issue 未提供操作系统、Python、CUDA、显卡或具体依赖版本信息。

最快修复方案:Issue 中合并的修复是把 should_retry_exception 的判断顺序改为先判断 isinstance(retry_on, (type, tuple)),再回退到 callable 分支,从而让单个异常类被当作单元素元组处理。对应地升级到包含该修复的 langchain / langchain-core 版本即可,无需修改你自己的调用代码。

注意事项:该修复会改变现有行为:此前“单个类被误当谓词”的调用会静默重试全部异常,修复后只重试匹配 retry_on 的异常。若你依赖了旧行为,升级后需要重新评估重试语义。提交者提到的 PR #41111 曾“auto-closed pending assignment”,最终由社区 PR 合并落地,若你的版本早于合并点,升级前请先在隔离环境验证。

问题场景

在 LangChain 的 agent middleware 中使用 ModelRetryMiddleware 或 ToolRetryMiddleware 做重试控制时,用户按最直觉的写法把单个异常类传给 retry_on,例如 ToolRetryMiddleware(retry_on=TimeoutError) 或 ModelRetryMiddleware(retry_on=TimeoutError)。期望只重试 TimeoutError,但实际观察到 handler 被调用 1 + max_retries 次,随后 on_failure 生成失败消息,说明不匹配的异常也被重试了。

报错原文

ModelRetryMiddleware / ToolRetryMiddleware: `retry_on=SomeError` (a bare class) retries every exception

print(should_retry_exception(KeyError("k"), ValueError))     # 'k'   (truthy, not even a bool)
print(should_retry_exception(KeyError("k"), (ValueError,)))  # False

3 Tool 't' failed after 3 attempts with KeyError: 'not a timeout'. Please try again.

No exception is raised, which is the problem.

Expected: `KeyError` does not match `retry_on=TimeoutError`, so it is re-raised immediately after one attempt.
Actual: the handler is called 3 times (1 + max_retries), then `on_failure` produces the ToolMessage
"Tool 't' failed after 3 attempts with KeyError: 'not a timeout'. Please try again."

Same with `ModelRetryMiddleware(retry_on=TimeoutError)`: a model raising `ValueError` is retried the full budget.

原因分析

根因在 langchain/agents/middleware/_retry.py 的 should_retry_exception,其原始逻辑为:

if callable(retry_on):
    return retry_on(exc)
return isinstance(exc, retry_on)

在 Python 中异常类本身是 callable 的,因此 retry_on=TimeoutError 这种单个类的写法会先进入 callable 分支,执行 TimeoutError(exc)。这返回的是一个异常实例,永远是 truthy,结果就是所有异常都被判定为可重试。ModelRetryMiddleware 和 ToolRetryMiddleware 会把每个异常都重试到预算上限,并把本该快速失败的异常也交给 on_failure 处理。同时该分支还会泄漏非布尔值,例如 should_retry_exception(KeyError('k'), ValueError) 返回的是 ValueError(KeyError('k')) 而不是 False。

另一个诱因是 RetryOn 类型别名声明的是元组,但用户最自然写出的就是单个类,且没有任何校验错误提示,行为上也不易察觉——middleware 只是“重试得比预期多”。同一包内的其他 API(如 ToolStrategy(handle_errors=SomeError))接受单个类型,用户会合理期待行为一致。

环境排查

  • 确认 langchain 与 langchain-core 的版本,排查所使用的版本是否已包含 should_retry_exception 的修复。
  • 确认出问题的代码位于 libs/langchain_v1/langchain/agents/middleware/_retry.py,核对当前版本中该函数的判断顺序。
  • 确认 retry_on 的传参形式:是单个异常类、异常元组,还是 callable 谓词。
  • Issue 未提供 Python、CUDA、PyTorch、显卡或操作系统版本,这些项无需作为排查依据。

解决步骤

  1. 升级到包含该修复的 langchain / langchain-core 版本;修复的核心是把判断顺序改为先类型、后 callable。
  2. 如果你需要临时确认或自行打补丁,可参考 Issue 中合并的逻辑:
    if isinstance(retry_on, (type, tuple)):
        return isinstance(exc, retry_on)
    return retry_on(exc)
  3. 配套把 RetryOn 类型别名扩展为 type[Exception] | tuple[type[Exception], ...] | Callable[[Exception], bool],使单个异常类成为受支持的输入(该别名改动在最终合并版本中由社区 PR 落地;若你的版本未包含,可优先尝试用元组形式规避)。
  4. 升级或打补丁后,用最小复现脚本验证:retry_on=TimeoutError 时 handler 抛出 KeyError,应只调用一次并直接抛出,而不是跑满重试。
  5. 如果暂时无法升级,可优先尝试把 retry_on=TimeoutError 写成元组 retry_on=(TimeoutError,),从而绕开 callable 分支,避免所有异常被重试。

验证方法

运行最小复现脚本,观察 handler 的调用次数与返回结果。修复前 calls["n"] 为 3,并返回 “Tool ‘t’ failed after 3 attempts with KeyError: ‘not a timeout’. Please try again.”;修复后不匹配的异常应在一次尝试后直接向上抛出,不再进入重试与 on_failure。同时可运行 should_retry_exception(KeyError("k"), ValueError),修复后应返回 False 而不是 truthy 的异常实例;并确认元组形式与 callable 谓词形式的行为保持不变。

参考来源

langchain-ai/langchain #41025

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 28553

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注