深夜自动任务最怕的不是任务本身出错,而是依赖的模型服务突然不可用。人已经休息,监控没人查看,等到第二天才发现整晚任务都没有产出,后续工作也被一起推迟。
这类问题不一定要靠复杂的高可用平台解决。以 Firefly Router 为例,模型故障转移可以通过配置一个首选模型和多个备用模型来完成:首选模型请求失败后,路由器自动尝试下一个模型。对调用方来说,仍然只需要请求同一个路由入口。
故障转移解决的是什么
一次模型调用失败,可能来自多种原因:
- 模型服务临时不可用或发生大面积故障
- 请求超时,尤其是长上下文或批量任务
- 供应商触发限流
- 账户余额、配额或区域策略导致请求被拒绝
- 某个模型正在维护或暂时下线
如果业务代码只写死一个模型 ID,失败就会直接向上抛出。夜间批处理通常没有人工重试机制,于是一个短暂故障会变成一整晚的任务空窗。
故障转移的核心是把“使用哪个模型”的决策交给路由层:
任务程序 -> Firefly Router -> 首选模型
-> 备用模型 1
-> 备用模型 2
当首选模型返回可重试的错误时,路由器继续尝试后续模型;当请求成功时,结果按正常流程返回给任务程序。这样可以减少业务代码中的供应商判断和重复重试逻辑。
在 Firefly Router 中配置模型链
来源信息给出的做法是:填写模型 ID 时,在首选模型后配置备用模型,从而让 Router 负责故障转移。由于不同版本或部署方式的配置分隔符可能不同,下面使用一个示意配置说明模型链的结构。实际接入时,请以当前 Firefly Router 的模型 ID 格式为准。
# firefly-router.yaml
# 示例:将首选模型和备用模型放入同一条路由链
routes:
nightly-report:
model: "deepseek-chat, qwen-plus, gpt-4o-mini"
timeout_seconds: 60
max_attempts: 3
如果你的 Router 版本要求使用数组,也可以按下面的结构改造:
routes:
nightly-report:
models:
- deepseek-chat
- qwen-plus
- gpt-4o-mini
retry:
max_attempts_per_model: 1
retryable_status_codes: [408, 429, 500, 502, 503, 504]
这里的顺序很重要:deepseek-chat 是首选模型,后面的模型是备用模型。备用模型不应只是在名称上不同,还要确认它们确实来自可用的独立供应商或服务通道,否则多个模型可能同时受到同一个故障影响。
如果 Firefly Router 的实际语法支持在模型 ID 中直接填写故障转移链,那么业务侧通常只需要保留一个模型配置:
export LLM_MODEL="deepseek-chat,<备用模型ID>,<第二备用模型ID>"
请把尖括号中的内容替换为真实模型 ID,并根据 Router 文档确认分隔符。重点不是把多个模型写进业务代码,而是让路由层维护这条顺序明确的候选链。
让夜间任务真正可靠
模型故障转移只能解决“换一个模型继续请求”,不能自动解决所有业务问题。夜间任务还需要配合幂等、超时、日志和告警。
下面是一个可以直接改造的 Python 调用示例。假设 Firefly Router 提供兼容 OpenAI 的 HTTP API,具体地址、认证变量和模型链格式请按你的部署替换:
import os
import time
from openai import OpenAI
client = OpenAI(
api_key=os.environ["FIREfly_ROUTER_API_KEY"],
base_url=os.environ.get(
"FIREFLY_ROUTER_BASE_URL",
"http://localhost:8080/v1",
),
)
MODEL_ROUTE = os.environ.get(
"LLM_MODEL",
"deepseek-chat,qwen-plus,gpt-4o-mini",
)
def generate_report(input_text: str) -> str:
response = client.chat.completions.create(
model=MODEL_ROUTE,
messages=[
{
"role": "system",
"content": "你是一个严谨的报告助手,只输出可核验的结论。",
},
{"role": "user", "content": input_text},
],
timeout=60,
)
return response.choices[0].message.content or ""
for attempt in range(3):
try:
report = generate_report("请根据输入数据生成夜间运营报告。")
with open("nightly-report.md", "w", encoding="utf-8") as file:
file.write(report)
print("report generated")
break
except Exception as exc:
print(f"attempt {attempt + 1} failed: {exc}")
if attempt == 2:
raise
time.sleep(10 * (attempt + 1))
这个示例中有三个需要留意的边界:
- Router 的内部故障转移应优先于业务侧重试。否则一次业务重试可能再次从首选模型开始,浪费时间,也可能放大限流。
- 文件写入、数据库更新和消息发送要设计成幂等。模型请求成功但进程在保存结果前退出时,重跑任务不能制造重复数据。
- 不要捕获异常后静默结束。达到重试上限后应该记录失败任务、发送告警,或者写入一个可恢复的任务队列。
哪些错误应该触发切换
并非所有错误都适合立即切换模型。认证失败、提示词格式错误、输入超出上下文限制等问题,换模型通常仍然失败,还会掩盖真正原因。
建议把错误分成两类:
| 错误类型 | 是否适合故障转移 | 处理建议 |
|---|---|---|
| 408、429、500、502、503、504 | 通常适合 | 按退避策略尝试备用模型 |
| 网络连接失败、读取超时 | 通常适合 | 限制重试次数并切换通道 |
| 401、403 | 通常不适合 | 检查密钥、权限和账户状态 |
| 400、上下文超限 | 通常不适合 | 修正请求或压缩输入 |
| 内容审核或业务策略拒绝 | 视情况而定 | 不要无条件绕过策略 |
如果 Router 能够按状态码和错误类型配置重试规则,优先使用 Router 的规则。业务代码只负责处理最终失败,例如记录任务状态为 failed 并触发告警。
上线前检查清单
- 首选模型不可用时,备用模型确实能返回结果
- 备用模型支持相同的 API 协议、消息格式和关键参数
- 不同模型的输出长度、工具调用和结构化输出能力经过验证
- 已设置连接超时、读取超时和最大重试次数
- 已区分可重试错误与请求本身的业务错误
- 夜间任务具备幂等机制,可以安全重跑
- 失败、切换、最终成功和最终失败都有日志
- 备用模型的价格、速率限制、数据处理策略符合要求
- 定期执行一次故障演练,而不是等真正故障发生时才验证
模型故障转移的价值不在于保证每次调用都成功,而在于把一次供应商故障降级成一次可观测、可恢复的路由事件。对于无人值守的夜间任务,先配置一条可靠的模型链,再补上重试、告警和幂等,通常就能显著降低单一模型故障带来的影响。