LangChain 降级、重试与超时:让服务在模型抽风时还能用

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

LangChain 降级、重试与超时:让服务在模型抽风时还能用

模型 API 会限流、会超时、会 5xx。

你的服务不能跟着一起挂。

这篇讲三层防护:重试 → 降级 → 超时 ,

以及它们的组合顺序------顺序错了等于白做。

一、先分清三种故障

故障 特征 处理
瞬时故障 429 限流、503、网络抖动 重试(很快就好)
持续故障 某个模型整体不可用 降级(换模型/换服务)
慢 不报错但很慢 超时(别耗尽资源)

三种要分别处理,混在一起会互相干扰。

二、第一层:重试(with_retry)

python 复制代码
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    max_retries=2,        # 构造参数:自动重试 2 次
    timeout=30,
)

这是最省事的方式。LangChain 内置指数退避 ,

只重试可重试错误(限流/5xx/网络),参数错误不会重试(重试也没用)。

2.1 用 with_retry 更灵活

python 复制代码
chain = (prompt | llm | parser).with_retry(
    stop_after_attempt=3,
    wait_exponential_jitter=True,     # 指数退避 + 抖动
    retry_if_exception_type=(TimeoutError, ConnectionError),
)

with_retry 可以套在链的任意一层,而不只是 LLM:

python 复制代码
# 只给检索这一步加重试
retrieval = retriever.with_retry(stop_after_attempt=3)
chain = {"context": retrieval | format_docs, "question": RunnablePassthrough()} | ...

2.2 重试的三个坑

坑一:重试次数相乘

python 复制代码
llm = ChatOpenAI(max_retries=3)               # 3 次
chain = (prompt | llm).with_retry(stop_after_attempt=3)   # 再 3 次
# 最坏情况 3 × 3 = 9 次调用!

只在最外层加一层重试,别叠。

坑二:重试放大成本

一次请求重试 3 次 = 最多 4 倍费用。

如果你的失败率是 20%,平均成本就涨了 20%------这是隐形成本。

坑三:对不可重试的错误重试

参数错误(400)、认证失败(401)重试一万次也没用。

LangChain 已经帮你过滤了,但自己写重试逻辑时要注意:

python 复制代码
import asyncio
from openai import RateLimitError, APITimeoutError

async def smart_retry(coro_factory, max_attempts=3):
    for i in range(max_attempts):
        try:
            return await coro_factory()
        except (RateLimitError, APITimeoutError) as e:
            if i == max_attempts - 1:
                raise
            await asyncio.sleep(2 ** i)      # 1s, 2s, 4s
        except Exception:
            raise      # 其他错误直接抛,不重试

三、第二层:降级(with_fallbacks)

重试解决"偶发",降级解决"这个东西彻底不行了"。

3.1 换模型

python 复制代码
from langchain_openai import ChatOpenAI

primary = ChatOpenAI(model="gpt-4o", timeout=30, max_retries=1)
backup  = ChatOpenAI(model="gpt-4o-mini", timeout=30, max_retries=1)
last    = ChatOpenAI(model="gpt-3.5-turbo", timeout=30, max_retries=1)

llm = primary.with_fallbacks([backup, last])

chain = prompt | llm | parser
chain.invoke({"q": "..."})     # gpt-4o 挂了 → 自动换 gpt-4o-mini → 再换 3.5

按顺序尝试,第一个成功的就用。

3.2 换供应商(更有价值)

真正的生产级降级是跨供应商 ------

OpenAI 挂了换 Anthropic,都挂了换本地模型:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_community.chat_models import ChatOllama

llm = (
    ChatOpenAI(model="gpt-4o-mini", timeout=30)
    .with_fallbacks([
        ChatAnthropic(model="claude-3-haiku-20240307", timeout=30),
        ChatOllama(model="qwen2.5", timeout=60),     # 最后兜底本地模型
    ])
)

本地模型兜底很实用 :它可能效果差点,但至少不会"完全不可用"。

对客服这类场景,"回答质量下降"远比"直接报错"可接受。

3.3 降级到非模型方案

最极端的兜底------不用模型:

python 复制代码
from langchain_core.runnables import RunnableLambda

def keyword_fallback(inputs):
    """用关键词规则硬匹配一个答案"""
    return KEYWORD_ANSWERS.get(inputs["question"], "抱歉,服务暂时不可用,请稍后再试。")

llm_chain = prompt | llm | parser
final = llm_chain.with_fallbacks([RunnableLambda(keyword_fallback)])

3.4 什么时候会触发降级

关键点:with_fallbacks 只在"抛异常"时才切换。

如果模型返回了一个质量很差但没报错 的结果(比如空字符串、

答非所问),它是不会降级的。要处理这种情况,得自己加校验:

python 复制代码
def validate_and_fallback(chain, fallbacks, inputs):
    try:
        result = chain.invoke(inputs)
        if is_bad(result):         # 自己定义"什么是坏结果"
            raise ValueError("结果质量不合格")
        return result
    except Exception:
        for fb in fallbacks:
            try:
                return fb.invoke(inputs)
            except Exception:
                continue
        return "抱歉,服务暂时不可用。"

四、第三层:超时

4.1 设在哪里

两处都要设:

python 复制代码
# 1. 模型层
llm = ChatOpenAI(model="gpt-4o-mini", timeout=30)

# 2. 应用层(Web 服务)
import asyncio
result = await asyncio.wait_for(chain.ainvoke(inputs), timeout=45)

应用层的超时要略大于模型层 ------

否则可能出现"应用层已经返回超时,模型层还在跑"的悬挂请求。

4.2 流式场景的超时

流式调用不能用简单的 wait_for(因为它是持续产出):

python 复制代码
async def stream_with_deadline(chain, inputs, first_token_timeout=15, total_timeout=120):
    start = asyncio.get_event_loop().time()
    first = True
    async for chunk in chain.astream(inputs):
        now = asyncio.get_event_loop().time()
        if first:
            if now - start > first_token_timeout:
                raise TimeoutError("首字超时")
            first = False
        if now - start > total_timeout:
            raise TimeoutError("总时长超时")
        yield chunk

首字超时(TTFT)和总时长要分开设 ------

用户能容忍"慢慢输出",但不能容忍"点了没反应"。

五、三层的正确组合顺序

复制代码
用户请求
   ↓
【超时】整体包一层 deadline
   ↓
【降级】with_fallbacks([备用1, 备用2, 规则兜底])
   ↓
【重试】每个分支内部 max_retries / with_retry

顺序反了会出问题:

  • 如果重试包在降级外面 → 模型 A 失败,重试 A 三次,才降级到 B。
    浪费时间,用户等太久;
  • 正确是:先降级(快切),每个降级分支内部自己重试。
python 复制代码
# ✅ 正确:降级在外,重试在内
primary = ChatOpenAI(model="gpt-4o", max_retries=1)       # 内部重试 1 次
backup  = ChatOpenAI(model="gpt-4o-mini", max_retries=1)
llm = primary.with_fallbacks([backup])

chain = (prompt | llm | parser).with_config(recursion_limit=5)

六、监控:别等用户告诉你挂了

python 复制代码
from langchain_core.callbacks import BaseCallbackHandler
import logging

class FallbackMonitor(BaseCallbackHandler):
    def on_chain_error(self, error, *, run_id, **kwargs):
        logging.error(f"chain_error run={run_id} type={type(error).__name__}")

    def on_llm_error(self, error, *, run_id, **kwargs):
        metrics.incr(f"llm.error.{type(error).__name__}")

三个要看的指标:

指标 告警线 含义
错误率 > 2% 供应商出问题了
降级触发率 > 5% 主模型不可靠,考虑换
P95 延迟 超 SLA 有慢请求

降级触发率特别重要 ------它告诉你"主用方案还行不行"。

如果每天都大量走降级,说明主模型该换了,而不是继续凑合。

七、完整示例

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda

prompt = ChatPromptTemplate.from_template("{question}")

def make_llm(model, **kw):
    return ChatOpenAI(model=model, timeout=kw.get("timeout", 30),
                      max_retries=kw.get("max_retries", 1), temperature=0)

primary = make_llm("gpt-4o-mini")
backup  = make_llm("gpt-3.5-turbo")

def rule_fallback(inputs):
    return "抱歉,AI 服务暂时不可用,已为你转人工客服。"

chain = (
    prompt
    | primary.with_fallbacks([backup])
    | StrOutputParser()
).with_fallbacks([RunnableLambda(rule_fallback)])

async def safe_answer(question: str) -> str:
    try:
        return await asyncio.wait_for(chain.ainvoke({"question": question}), timeout=45)
    except asyncio.TimeoutError:
        return "处理超时,请稍后再试。"

八、小结

  • 三种故障分开处理:瞬时→重试、持续→降级、慢→超时;
  • 重试只在最外层加一层,多层叠加会让调用次数相乘、成本翻倍;
  • with_fallbacks 只在抛异常 时切换,
    质量差但不报错的结果需要自己加校验;
  • 跨供应商降级最有价值,最后可以用本地模型或规则兜底;
  • 正确顺序:超时包最外层 → 降级 → 每个分支内部重试;
  • 监控降级触发率,持续偏高说明主方案该换而不是继续兜。

下一篇讲批量与并发------batch 的正确用法和速率控制。

相关推荐
MoRanzhi12031 小时前
第 8 篇 · 机器学习建模与实验评估:建模流程与对照实验
python·机器学习·scikit-learn·模型评估·交叉验证·shap·消融实验
棉猴1 小时前
玩游戏学Python9-算术运算符应用1
python·游戏·pygame·玩游戏·游戏编程
凤年徐2 小时前
【Python】Python 语法速通指南
开发语言·python
quantdash_cc2 小时前
量化数据清洗应该放在数据层还是策略层?从系统架构看清职责边界
开发语言·python·数据分析·系统架构·量化交易·股票数据·quantdash
梅雅达编程笔记2 小时前
Day 06 · 第一个 API 接口:从 Model 到 JSON
python·django·drf·后端开发·restful api·序列化器
yi0112 小时前
DAY28: LeetCode 111. 二叉树的最小深度
人工智能·笔记·python·算法·leetcode·二叉树·递归
方方洛2 小时前
ai-agent教程-05-上下文工程
人工智能·llm·agent
用户721746588262 小时前
三条热词通道的机制拆解:bl speech 的参数面、生效模型范围与错配读数
llm·agent·ai编程
森码2 小时前
一条 Prompt 在 Harness 中经过了谁?
agent