当前位置: 首页 > news >正文

【Agent 的多模型路由熔断:免费模型池怎么做到「永远有能用的」】

Agent 的多模型路由熔断:免费模型池怎么做到「永远有能用的」

系列专栏【打造你自己的 Agent】第 2 篇 · 代码全部来自真实项目 Rescene

先讲个真实事故。

我的项目 Rescene 主打「永久免费」,靠的是聚合各厂商的免费档模型。上线初期,路由层是「配了一个模型就用一个模型」——结果就是灾难:OpenRouter 的免费档全部 slug 限流 429(连 llama-3.3-70b 都 429),NVIDIA NIM 免费试用档限流严重到跑 Agent 频繁 429,我一度怀疑是不是自己代码写错了。

后来想明白了:免费模型的本质是「不稳定」。今天能用的,明天可能就 429;上午还快的,下午可能超时。所以多模型路由不是「选个好模型」的问题,而是**熔断(Circuit Breaking)**的问题——把「不稳定」当作默认前提来设计。

今天这篇讲清楚三件事:

  1. 路由链:怎么把多个模型源排成一条「备用链」
  2. failover 时机:什么时候该切、什么时候不该切(这个细节决定成败)
  3. 探测与淘汰:怎么让免费池「永远活着」

一、路由链:把模型源排成一条链

多模型路由的第一步,是把所有可用源排成一条有序链:先试谁、失败切谁、最后兜底谁。

Rescene 的路由链构建(真实代码,已精简):

funcresolveBackends(userKeystring,modelstring)[]RouterBackend{// 精确路由:前端明确选了某个模型 → 只走那一个ifmodel!=""&&model!="auto"{ifb:=resolveExact(userKey,model);b!=nil{return[]RouterBackend{*b}}}varuserChain,freeChain[]RouterBackend// 1. 用户自己配置的提供方(有 Key 的)—— 排最前for_,e:=rangeuserEntries{b:=RouterBackend{Name:e.Name,BaseURL:e.Endpoint,Model:e.DefaultModel,...}ife.IsDefault{userChain=append([]RouterBackend{b},userChain...)// 默认的提到链头}else{userChain=append(userChain,b)}}// 2. 免费池:Key 来源 = 用户保存的 > 环境变量;没 Key 的源直接不进链for_,f:=rangefreeModelCatalog{iff.Disabled{continue}// 探测失败的已被标记退役key:=lookupKey(f)// 用户保存 > env,都没有就跳过b:=RouterBackend{Name:f.Name,Model:f.Model,Timeout:45*time.Second,...}freeChain=append(freeChain,b)}// 免费池按参数规模降序:优先用「最能打」的免费模型sort.SliceStable(freeChain,func(i,jint)bool{returnfreeChain[i].ParamsB>freeChain[j].ParamsB})returnappend(userChain,freeChain...)// 用户链在前,免费链兜底}

设计要点:

  • 用户自己的 Key 永远排最前——稳定、快、可控,免费池只是兜底
  • 免费池按参数规模降序——能干活的大模型优先,实在不行才退到小模型
  • 拿不到 Key 的源根本不进链——与其连了再失败,不如一开始就排除

二、failover:切,但只在「对的时候」切

路由链有了,接下来是核心问题:什么时候切下一个

非流式:失败就秒切

普通(非流式)请求最简单——失败就切下一个,全部失败才报错:

funcrouteChatOnce(ctx context.Context,backends[]RouterBackend,msgs,tools)(string,[]core.ToolCall,error){vartried[]stringfor_,b:=rangebackends{ifctx.Err()!=nil{return"",nil,ctx.Err()}// 用户取消了就别试了content,calls,err:=openAIChatOnce(ctx,b,msgs,tools)iferr!=nil{tried=append(tried,fmt.Sprintf("%s: %v",b.Name,err))continue// 失败,秒切下一个}returncontent,calls,nil}return"",nil,fmt.Errorf("所有模型源不可用:%s",strings.Join(tried,";"))}

注意两个细节:

  1. ctx.Err()检查——用户已经关掉页面/取消请求时,别还在那试下一家
  2. 失败原因全部收集——最后报错时把每家失败原因拼出来,用户一看就知道是谁挂了

流式:只在「首包之前」切

Agent 场景下 90% 的请求是流式的,这里有个反直觉的规则(Rescene 的真实注释):

// 沿路由链做流式调用。failover 只发生在拿到 200 响应之前 // (连接失败/非200 秒切下一个);流一旦开始就不再切换源。

为什么流开始后不能切?两个原因:

  1. 已吐的 token 全白费——用户已经看到了 200 字,切到下一个源重新生成,体验是「话说到一半突然重来」
  2. 会重复执行工具——如果流式响应里已经包含 tool_call 指令(比如"调用 write_file"),切源重发会导致同一个工具被执行两次

所以规则就是:连接失败 / 非 200 → 秒切;已经开始吐字 → 死磕到底

流式超时:一个坑死无数人的细节

Go 的http.Client.Timeout会把「读取整个响应体」也计入超时窗口——免费档模型生成一次常常 >45 秒,流式响应会被Client.Timeout在读到一半时砍断。Rescene 的解法:

funcstreamHTTPClient()*http.Client{return&http.Client{Timeout:0,// 关键:整体超时归零,绝不能在这里设Transport:&http.Transport{DialContext:(&net.Dialer{Timeout:15*time.Second,KeepAlive:30*time.Second,}).DialContext,ResponseHeaderTimeout:30*time.Second,// 只卡「连接 + 首字节」...},}}

原则:「连接 + 首字节」要限时(30s),「流式生成过程」不限时。首字节 30 秒没到说明这源大概率挂了,直接 failover;一旦开始吐字,哪怕生成 2 分钟也随它去——取消权交给请求上下文(浏览器断开即取消)。


三、探测与淘汰:免费池怎么「永远活着」

路由链再聪明,也架不住链上的源全是死的。Rescene 的做法是每日探测 + 自动退役

  • 每天自动探测各厂商免费档模型可用性(真实调用一次,看是否 429 / 超时 / 报错)
  • 跑不了的自动标记退役f.Disabled = true,不再进链)
  • 能用的自动恢复(探测通过重新进链)

实测教训(都写在代码注释里了):

// —— OpenRouter 已整体移除:免费档全部 slug 限流 429(连 llama-3.3-70b/405b 都 429) // —— NVIDIA NIM 免费试用档已整体移除:2026-07-23 实测限流严重,跑 Agent 频繁 429

免费模型厂商今天送、明天限流是常态。把「探测 + 退役 + 恢复」做成自动化闭环,比任何手动维护都可靠——这也是「免费池永远是真能跑的」这句话的底气来源。


四、踩坑清单

  1. Client.Timeout会砍流式——它把读响应体也算进窗口,免费档 45s+ 的生成会被拦腰截断。流式 client 必须 Timeout=0,用ResponseHeaderTimeout只卡首字节。
  2. 流开始后绝不能切源——前面吐的字全白费,更危险的是 tool_call 指令会被重复执行(同一工具跑两遍)。
  3. max_tokens 太小会死循环——4k 上限会把稍长的 write_file 参数截断在 JSON 中间,工具报unexpected end of JSON input,模型收到错误后整份重写,又超 4k,又截断……死循环。Rescene 单轮给到 16384,靠工作流总预算兜底。
  4. reasoning_effort不能无脑塞给所有源——不支持该字段的上游大概率报错而不是安静忽略,只在b.Reasoning == true时才带。
  5. 空链要有明确的错误信息——一个 Key 都没配时链就是空的,报「所有模型源不可用:」加一片空白会让用户以为产品坏了。明确提示「请在设置面板填入至少一个 API Key」。
  6. 免费源 Key 的来源要分层——用户自己保存的 > 环境变量,都没有就跳过。免得环境变量里的旧 Key 让用户以为「我配了呀为什么不用」。

五、关于 Rescene

本文代码来自我手写的开源项目Rescene——专攻前端设计、浏览器自动化、Computer Use的二次元 Agent:

  • 🔋免费模型每日更新:每天自动探测各厂商免费档模型,免费池永远是真能跑的(本文的探测/退役/恢复闭环就是它在跑)
  • 🎨专攻前端设计:内置 54 个真实设计系统参考,Agent 写完直接真实渲染给你看
  • 🌐真实浏览器自动化:基于 Chromium + CDP,是真浏览器在跑你的页面
  • 🧠成长中的记忆:每次工作流完成后自动萃取经验,下次自动融入上下文
  • 🔧4+4+2 Agent 工作流:40% 计划 → 40% 验证 → 20% 编码

🔗 官网:https://rescene.shanca.me/ (全速下载最新发行版)
🐙 GitHub:https://github.com/Rescenix/ResceneAgent


下篇预告

第 3 篇:《省 Token 工程:动态工具发现和前缀匹配之外,还有 11 种核心技术》——注入优化、轨迹压缩、架构隔离、调用策略四大类,全部有真实代码和实测数据,最后引出 Agent 的「并行任务-后台任务-系统级审批自检框架」(Agent Harness)。明天见~

http://www.jsqmd.com/news/1311310/

相关文章:

  • libcurl网络编程实战:从编译集成到高并发架构设计
  • 2026 年新消息:上海到三门峡猪肉冷链运输服务团队选哪家,别再乱运猪肉了!三门峡这家冷链运输路子,让肉品损耗直接降六成 - 行业甄选官
  • 第十章信息系统的基础知识
  • 杭州表导演高职提前招工作坊选购指南:实力机构推荐与多维解析 - 优质品牌商家
  • 农牧企业出海,如何用“数字化+AI”穿越周期?——ALCF2026(越南站)观察
  • PyTorch GPU部署全攻略:从安装到排错,彻底解决torch.cuda.is_available()返回False
  • react Native UI Component
  • 基于Arduino与GSM模块的复古电话DIY:硬件复现与通信实践
  • 其利天下技术“第九届(苏州)电动工具控制与充电技术研讨会”“2026‘(苏州)清洁电器技术创新论坛”即将于苏州重磅登场
  • 用 Ace Data Cloud 快速接入 Suno 声音克隆 API:让 AI 音乐生成进入个性化声音时代
  • ChatGPT助力LinkedIn个人资料优化全攻略
  • 2026 年至今,越秀专业的自然馆场景复原工作室找哪家,你看的远古巨兽不是特效,竟是靠它还原的真实现场? - 行业推荐【认证官】
  • PVE Tools终极指南:5分钟完成Proxmox VE复杂配置的完整教程
  • 2026年8月苏州塑料粒子/POM塑料粒子公司推荐精选_苏州朗泰塑胶化工有限公司 - 行业平台推荐
  • 如何用QtScrcpy实现安卓投屏控制:5步搞定无延迟屏幕镜像
  • 西安80坐标系坐标点地图可视化:91卫图助手实操指南
  • 《从信息学发展史到 Python 循环:我的一天学习记录》
  • ShareX自动化工作流配置:从截图、OCR识别到自动上传的完整实践
  • Elasticsearch安全配置实战:解决elasticsearch-setup-passwords报错全攻略
  • 我常年切换 Linux 发行版,如果你中了以下7招,你也该换了
  • C++内存五大区详解:栈、堆、静态区、常量区与代码区
  • 做视频号的人,终于不用下载、截图、复制文案来回折腾了
  • B2118 验证子串
  • 离线发票信息提取工具v1.2.0:基于OCR与规则引擎的本地化解决方案
  • 宜宾护墙板定制公司怎么选?2026年诚信企业推荐与行业观察 - 优质品牌商家
  • Windows下Nginx与IIS共存:解决80端口冲突的反向代理方案
  • 《AI 渐进编程》之二十三:自适应调度——什么时候启用多Agent流水线
  • VC++6.0与面向对象编程:历史价值、MFC框架与工程实践深度解析
  • 3种方法彻底移除Windows Defender:从基础隐藏到完全卸载的完整指南
  • Grove AND逻辑门模块:硬件与门在嵌入式项目中的实战应用