当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > LiteLLM 流式响应中断后怎样切到备用模型

LiteLLM 流式响应中断后怎样切到备用模型

来源:17golang原创 2026-11-05 13:14:58 0浏览 收藏

LiteLLM 的流式响应已经输出部分内容后,不能把“配置 fallback”理解成备用模型会从原来的字节位置接着写。对于本文讨论的 Router Chat Completions 路径,已有内容后出错会向调用方抛出异常;要切到备用模型,应由应用捕获允许恢复的错误,用原始消息开启新的生成尝试,并明确替换或重新展示回答。

官方地址:https://docs.litellm.ai/docs/proxy/reliability

下面按故障排查顺序说明。依据是 LiteLLM v1.106.0 的 Router 源码及官方修复说明,范围限定为纯文本 Chat Completions,不把该结论直接套到 Responses 或 Messages 的不同实现。示例没有执行,也不是真实生产事故记录。

先区分流已建立与内容已经输出

用户可能说“已经开始流式返回”,实际上存在两种情况:连接已经建立,但没有产生实质输出;或者用户已经看到半句话。前一种还有机会在用户无感的情况下切到备用路径,后一种必须处理已经交付的内容。

状态需要考虑的恢复方式
已建立流,尚无内容由对应版本和端点的重试、fallback路径处理
已有文本等内容保留中断状态,应用开启新的attempt
工具参数只到了一部分不执行残缺参数,独立处理新尝试

只判断HTTP连接是否建立,无法确定是哪一种。排查时应同时记录首个内容事件、异常发生点和正常结束信号。空文本也不一定表示没有输出:Router 的内容判断还覆盖工具调用、推理等非文本增量。

为什么配置了 fallback 仍会中断

普通 fallback 配置可以写在模型组之间:

# 两个名称必须对应现有且可访问的 model_list 模型组。
router_settings:
  fallbacks:
    - chat-primary: [chat-backup]

这个片段依赖已有模型列表,不能单独启动一个完整代理。它声明主组与备用组的关系,不是断点续传协议。当前源码在发现已经产生内容的流错误时直接抛出原始异常或包装异常,避免静默启动另一段回答污染已返回的内容。

因此,业务层收到中断不一定是备用名称写错。还应区分端点、实际部署版本,以及异常究竟发生在创建流时还是遍历流时。只把 router.completion 包在异常处理中,却把 for chunk in stream 留在外面,会漏掉后续读取错误。

流对象与已交付内容的责任边界
图1:流式恢复边界说明图。连接状态、已输出内容与应用尝试状态分别记录,不是运行截图。

已输出内容后采用新的生成尝试

下面提供应用层事件生成器。它要求调用方已经配置两个可用模型组,使用独立的 attempt 标识;前端收到新的 begin 时应创建新回答或替换旧回答,不把不同 attempt 的 delta 直接追加到同一文本。

import copy
from contextlib import suppress
from litellm import APIConnectionError, Timeout, InternalServerError, RateLimitError

RETRYABLE = (APIConnectionError, Timeout, InternalServerError, RateLimitError)

def text_attempts(router, messages):
    # 仅用于纯文本 Chat Completions;新尝试不携带旧的半截回答。
    original = copy.deepcopy(messages)
    for attempt, model in enumerate(("chat-primary", "chat-backup"), start=1):
        stream = None
        finished = False
        failure = None
        yield {"event": "begin", "attempt": attempt, "model": model}
        try:
            # 此处由应用掌管一次切换,避免与 Router 的隐藏切换叠加。
            stream = router.completion(
                model=model, messages=copy.deepcopy(original),
                stream=True, disable_fallbacks=True, num_retries=0,
            )
            for chunk in stream:
                if not chunk.choices:
                    continue
                choice = chunk.choices[0]
                if choice.delta.content:
                    yield {"event": "delta", "attempt": attempt,
                           "text": choice.delta.content}
                if choice.finish_reason is not None:
                    finished = choice.finish_reason == "stop"
            # 流结束不代表成功;没有正常终止信号时保持不完整状态。
        except RETRYABLE as exc:
            failure = exc
        finally:
            if stream is not None and hasattr(stream, "close"):
                with suppress(Exception):
                    stream.close()

        if finished and failure is None:
            yield {"event": "complete", "attempt": attempt}
            return
        yield {"event": "interrupted", "attempt": attempt}
        if failure is None:
            # length、拒绝或不明结束不自动切换,交给业务层判断。
            return
        if attempt == 2:
            raise failure
        # 主尝试发生指定的可恢复异常,循环才进入备用模型。

此处的“备用”是一次新的模型请求,输入仍是原始消息。模型可能重新组织答案,因此它不是精确接续。代码关闭单次请求的自动 fallback,并把同组重试次数设为0,让这个教学片段的一次主尝试和一次备用尝试更容易理解;生产项目应只保留一个明确的恢复责任方。

异常过滤只包含连接、超时、服务端错误和限流类。身份错误、参数错误、内容拒绝等不能通过捕获所有异常后无条件换模型来处理。备用模型也必须满足原请求的访问、业务与内容要求。

把不完整输出和工具调用隔离

收到异常时,将原 attempt 标成 interrupted,不能给它补一个 complete。旧内容可以作为“未完成草稿”保留,也可以在用户同意的产品设计下被新回答替换,但不能悄悄把两次回答混成一份完整输出。

示例只把 finish_reason="stop" 视为正常完成。长度截断、拒绝或没有结束信号时,交给业务层处理,不自动切换。实际产品还应区分用户主动取消与网络错误;用户已经停止生成时,不应继续发起备用请求。

工具调用不适用这个文本片段。参数增量需要完整组装,工具执行需要自己的确认和去重机制。已经发生外部副作用的工具操作,不能因为模型流中断就再次执行。新 attempt 也不应继承一半的工具调用结果。

原始消息、尝试标识与回答状态的组成关系
图2:应用尝试状态结构说明图。每次生成有独立标识,旧草稿与新回答分别管理。

记录能解释切换结果的信息

建议记录业务请求ID、attempt编号、请求模型组、实际服务模型、是否交付过内容、异常类别和结束状态。原始提示词和完整输出未必需要写进日志,排查标识应与内容分开管理。

复盘时可按“主流产生内容、读取出错、旧attempt中断、备用attempt建立、新attempt完成或失败”的事件核对业务逻辑。这里描述的是排查顺序,不代表本文产生过这样的运行数据。

把部分回答加进 messages 能不能续写?可以设计成一个新的续写任务,但要额外处理重复、矛盾和格式完整性,不能称为无损恢复。本文选择原消息重新生成,便于维护清楚的回答边界。

流自然结束就可以认定成功吗?不应只看迭代器退出。还要看端点定义的完成信号,并区分正常结束与内容截断。

所有 LiteLLM 端点都一样吗?不同。本文的源码依据和片段限定在 Router Chat Completions;Responses 与 Messages 应分别依据对应端点和部署版本处理。

参考:LiteLLM fallback 文档;v1.106.0 Router 实现;已输出内容后的错误传播修复说明。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
LiteLLM Router 怎样让同一模型组故障时切换其他部署LiteLLM Router 怎样让同一模型组故障时切换其他部署
上一篇
LiteLLM Router 怎样让同一模型组故障时切换其他部署
LiteLLM 怎样给不同 API Key 配置独立 fallback
下一篇
LiteLLM 怎样给不同 API Key 配置独立 fallback
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    893次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    894次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    978次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    967次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    793次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码