LiteLLM 流式响应中断后怎样切到备用模型
来源:17golang原创
时间:2026-11-05 13:14:58 325浏览 收藏
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 留在外面,会漏掉后续读取错误。

已输出内容后采用新的生成尝试
下面提供应用层事件生成器。它要求调用方已经配置两个可用模型组,使用独立的 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 也不应继承一半的工具调用结果。

记录能解释切换结果的信息
建议记录业务请求ID、attempt编号、请求模型组、实际服务模型、是否交付过内容、异常类别和结束状态。原始提示词和完整输出未必需要写进日志,排查标识应与内容分开管理。
复盘时可按“主流产生内容、读取出错、旧attempt中断、备用attempt建立、新attempt完成或失败”的事件核对业务逻辑。这里描述的是排查顺序,不代表本文产生过这样的运行数据。
把部分回答加进 messages 能不能续写?可以设计成一个新的续写任务,但要额外处理重复、矛盾和格式完整性,不能称为无损恢复。本文选择原消息重新生成,便于维护清楚的回答边界。
流自然结束就可以认定成功吗?不应只看迭代器退出。还要看端点定义的完成信号,并区分正常结束与内容截断。
所有 LiteLLM 端点都一样吗?不同。本文的源码依据和片段限定在 Router Chat Completions;Responses 与 Messages 应分别依据对应端点和部署版本处理。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
285 收藏
-
484 收藏
-
科技周边 · 人工智能 | 23小时前 | 性能优化 · 人工智能 · 深度学习 · PyTorch CUDA SDPA scaled_dot_product_attention Flash Attention214 收藏
-
科技周边 · 人工智能 | 23小时前 | 性能优化 · python · ONNX Runtime InferenceSession SessionOptions 图优化 Execution Provider281 收藏
-
414 收藏
-
157 收藏
-
234 收藏
-
135 收藏
-
230 收藏
-
344 收藏
-
312 收藏
-
458 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习