做自养Agent以来,最折腾我的不是Agent本身的业务逻辑,而是喂给它的模型资源池。我维护了一个免费模型池,初始凑了10个模型,结果6天时间挂了4个。这帖子就把整个过程复盘一遍——包括模型池怎么搭、日志怎么记、挂了之后怎么从日志里定位问题,以及后续怎么做了自动切换和熔断。
先说背景。我所谓的“自养Agent”,不是用现成的对话平台,而是自己部署的一套定时触发的智能体框架:每天按计划任务去抓取信息、调用模型做摘要和结构化输出,再把结果回填到自己的知识库里。这种Agent对模型的需求是量大、便宜、容忍偶尔失败,所以免费模型池是绕不开的选项。也正是因为它量大,日志和分析就变得特别重要——不然模型挂了,你只会看到结果不对,却没法定位到底哪一环出了问题。
如果你也在自己喂Agent,手头有一批模型资源,或者准备搭一个免费池子,这篇文章可以直接帮你少走弯路。我会把场景、日志结构、排查路径和自动容灾方案都拆开讲清楚。
1. 先说说自养Agent的部署结构和日志规划
1.1 我为什么选择“多模型冗余池”而不是单模型
自养Agent的核心矛盾和普通应用不一样:普通应用追求单次请求质量,Agent追求的是批量任务的稳定吞吐。我跑的任务大多是“每天定时采集内容,然后让模型做摘要、分类、抽取关键词”,单条任务失败可以重试,但整体Pipeline不能因为某一个模型限流就卡死。
所以从一开始,我就在架构上做了模型池。所谓池,就是我把多个可用模型放在同一个路由层后面,Agent只跟路由层对话,路由层负责选模型。模型选择策略是简单的优先级加权:质量好、速度快、免费的模型排前面,备用模型排后面。这样即便前面的模型挂了,请求也能自动落到下一个可用模型上。
很多人会问,为什么不直接用一个付费API,省心又稳定?答案很直接:我这种每日高频调用、长期跑批的任务,用付费API一个月下来费用不低。而免费模型池虽然不稳定,但配合冗余和切换机制,完全可以把不稳定转换成“偶尔慢一点、极少失败”,代价几乎为零。
1.2 日志是我后来排查“消失”的唯一线索
模型池的运维和普通服务不一样:你无法控制模型方的服务状态,唯一的观测手段就是请求日志。所以我在搭池子第一天就做了三件事:
第一,所有Agent调用模型的请求,统一走一层封装,把时间、模型名、任务ID、Prompt长度、响应码、耗时全部打点记录到结构化日志里。
第二,日志不进Docker stdout,而是写入宿主机指定目录,按天切分文件。这样出了问题,我可以直接在服务器上看当天的调用记录,不用翻容器日志。
第三,给关键任务挂了crontab定时任务,任务本身和执行结果都会留痕迹,方便我对照“任务执行时间”和“模型报错时间”之间的关联。
现在回头看,这三点恰好是后来我能在6天内快速定位4个模型失效的核心基础。如果你的Agent还没有统一的日志出口,我建议先把这一步补上,否则一切排查都是靠猜。
2. 免费模型池是怎么凑出来的:10个模型从哪来
2.1 我用的四类免费模型来源
这里说的免费模型,不是破解或者盗用的“免费”,而是各家平台公开的免费档、试用额度、开源模型本地部署这几类。我筛选下来,大致分四拨:
第一拨是本地开源模型。我用Ollama跑了一些小尺寸的模型,比如qwen2.5:7b、llama3.1:8b这类。这类模型不占外部API额度,免费且完全自主可控,缺点是速度和效果不如云端大模型,但做摘要、分类这种中等难度任务完全够用。本地模型一共占了池子里的3个名额。
第二拨是国内云厂商的免费额度。现在几家大模型平台对新用户和开发者都有免费试用资源,有的是赠送token,有的是限时免费调用部分规格。我把这类额度接进池子,作为云端主力。这部分的坑后面细说,因为很多额度不是永久性的,到期或者用超就直接404或者限流——我这次挂掉4个模型,有一半是倒在这上面的。
第三拨是公开评测/开放接口模型。有一些社区维护的开放接口提供了基于开源模型的免费推理服务,经常有模型可以白名单调用。这类适合做补充,但稳定性更差。
第四拨是缓存和服务端自建的模型别名。比如有些网关自带“免费版”映射,把某个模型映射到较小参数量版本上。这种我也挂了1个。
2.2 接入时做了统一抽象
10个模型来源各不相同,接口地址、API格式、鉴权方式都不一样。我没有为每个模型单独写一套调用代码,而是统一把它们转成OpenAI兼容的接口格式,再起了一个极简路由层。
每个模型在配置里就是一个条目,核心字段包括:name(模型别名)、base_url(接入地址)、api_key(密钥)、model(实际模型名)、weight(权重)、timeout(超时时间)、max_retries(最大重试次数)。Agent调用时只需传一个逻辑模型名,路由层根据权重挑一个真实模型发出请求。
这一步非常关键:因为各家模型API风格不一致,如果没有统一抽象,后面做自动切换和日志统计都会很痛苦。统一成OpenAI格式之后,我只需要关心每个模型的服务质量,而不需要关心它们底层是什么协议。
2.3 初始10个模型的构成明细
初始池子配置大概是这样的:
| 序号 | 模型别名 | 来源类型 | 用途倾向 |
|---|---|---|---|
| 1 | local-qwen7b | 本地Ollama | 高效摘要 |
| 2 | local-llama8b | 本地Ollama | 备用路由 |
| 3 | local-qwen32b | 本地Ollama | 复杂推理 |
| 4 | cloud-a-free | 云厂商A免费档 | 日常主力 |
| 5 | cloud-b-free | 云厂商B免费档 | 日常主力 |
| 6 | cloud-c-trial | 云厂商C试用额度 | 批量处理 |
| 7 | open-v1 | 开放接口1 | 辅助分类 |
| 8 | open-v2 | 开放接口2 | 辅助生成 |
| 9 | gate-free1 | 网关别名1 | 低优先级 |
| 10 | gate-free2 | 网关别名2 | 低优先级 |
这个结构看着像模像样,但之后的6天就是它给我上了一课:免费池里的模型不是“可用资源”,而是“动态资源”,你需要接受它们的生命周期非常短这个事实。
3. 6天少了4个模型:完整时间线和日志证据
3.1 前3天:限流和响应劣化是第一批信号
第1天到第3天,池子表面上是稳定的,但日志里已经出现了一些值得警惕的信号。
最明显的是HTTP 429(限流)频率逐步上升。我做了一套失败计数逻辑,单模型连续失败3次会被暂时标记为“inactive”并切换流量。第1天只有零星429,基本是某个时间点并发上来触发的,重试两次就恢复了,所以我没太在意。到第2天,429开始集中在某一个云厂商免费档上,而且重试后仍间歇失败。我当时判断是“平台的分钟级限流”,在代码里把该模型的并发和退避时间调了一下。
第3天,从日志里看到该模型的平均响应时长从原来的2秒左右涨到了6秒以上,甚至出现了读超时。这时我意识到:免费额度的服务优先级通常会被平台降低,慢是常态。但我还指望它撑几天,没做下线处理。
3.2 第4到第6天:报错集中爆发,4个模型先后退出
第4天开始,情况就不一样了。
先是cloud-b-free这个模型开始成批返回401。查了日志,错误信息是invalid api key。我一度以为是密钥配置出了问题,后来去对应平台看了才知道,新用户赠送的试用额度到期后,旧密钥直接被吊销,不是限流,是账户层面的资源停止。这个模型从日志角度来看,就是“突然不可用”,没有任何梯度过程。
同一天下午,open-v2也开始出问题,报的是404 model not found。这个更麻烦,因为接口本身是好的,但平台把免费模型下线并调整了模型名称。也就是说,我配置里的model字段已经失效,要么改名要么换新模型。
第5天,cloud-a-free也开始出现限流,但这次不是429,而是返回一个业务错误码,大意是“当前模型不在免费范围内”。这说明平台把免费档调整了,原本免费的模型被我那天的高频调用触发到付费检查。总而言之,免费额度缩水了。
第6天,我例行看统计日志时发现,池子里能成功返回的模型只剩6个了——10个里挂了4个,其中2个是完全401/404,1个是额度到期,1个是被移出免费档。本地Ollama那3个倒是稳如泰山,一台破机器扛住了所有压力。
3.3 从日志数据看这4个模型的“消失方式”
我把这4个模型的失效类型整理成了表格,方便对照你手头日志里的错误码:
| 模型 | 失效前主要错误 | 失效判定 | 根因归类 |
|---|---|---|---|
| cloud-b-free | 401 invalid key | 密钥吊销 | 额度到期型 |
| open-v2 | 404 model not found | 模型不存在 | 模型下线型 |
| cloud-a-free | 业务错误码 | 免费范围调整 | 范围收紧型 |
| gate-free1 | 429 + 超时 | 连续不可用 | 限流劣化型 |
这四种类型基本包含了免费模型池中“模型消失”的所有常见形态。它们有一个共同点:外界不会给你发任何下线通知,唯一能感知到的方式就是日志里的错误码和响应时间变化。所以我后来经常跟朋友说,免费模型池运维的本质是“日志驱动型运维”。
4. 日志排查的实战:从crontab日志到模型调用日志
4.1 先确保所有日志能对上时间线
排查这次事故,我用了三部分日志:Agent任务日志、模型调用日志、系统crontab执行日志。三者缺一不可。
Agent任务日志记录的是每个任务什么时候启动、什么时候结束、输出是什么。模型调用日志记录的是每一次模型请求的细节。crontab日志则用来确认定时任务本身有没有漏跑、跑了几次。对照这三者的时间戳,我才敢下结论说“某个模型在某个时间点开始不可用”,而不是单纯猜测。
具体到文件层面,我的做法是:
- 模型调用日志:
/var/log/agent/model_access.log,按天切割,每条一行,包含请求时间、模型别名、HTTP状态码、耗时 - Agent业务日志:
/var/log/agent/agent.log,记录任务级信息 - Crontab执行痕迹:
/var/log/agent/cron_records.log,通过脚本在任务开头和结尾各打一条标记
这样即使某天系统重启过,我也能凭时间戳还原故障现场。
4.2 查看crontab执行日志的三个技巧
很多新手在排查“定时任务明明配了,为什么没跑”时都会卡住。这里分享三个我实测有用的技巧。
第一个,crontab本身不写统一日志,不同发行版位置不同。Debian/Ubuntu看/var/log/syslog,CentOS看/var/log/cron。直接grep CRON /var/log/syslog | tail -50就能看到最近的执行记录。
第二个,把任务的输出重定向到一个独立文件。比如0 8 * * * /opt/agent/run_daily.sh >> /var/log/agent/cron_records.log 2>&1,这样不用翻系统日志,也能看到每次任务的标准输出和报错。
第三个,如果是容器内跑crontab,要注意容器重启后cron服务不一定自动拉起。我在服务器上加了健康检查,看pgrep cron是否存在,没有就自动重启它。这个细节帮我避免过一次“日志缺失”的假象。
4.3 从日志片段识别模型的死亡前兆
这次事故里,我在日志里看到了几个规律性的信号,这里挑两个典型的片段说。
第一个片段:同一模型在几分钟内,从200变成429再变成200,然后又429。这是典型的“限流抖动”。我用颜色标注了这类记录,看趋势能发现某平台免费额度在下行。遇到这种情况,不要只调重试参数,要开始准备替补模型。
12:01:02 [INFO] model=cloud-a-free status=200 latency=2130ms 12:01:03 [INFO] model=cloud-a-free status=429 latency=18ms 12:01:04 [WARN] model=cloud-a-free retry=1 12:01:06 [INFO] model=cloud-a-free status=200 latency=4112ms 12:01:07 [INFO] model=cloud-a-free status=429 latency=15ms第二个片段:同一个模型短时间连续报不同错误码,从429变成400再变成404。这种“错误码漂移”通常意味着模型条目本身有问题,不是单纯的限流。
12:20:01 [ERROR] model=open-v2 status=429 code=rate_limit 12:20:05 [ERROR] model=open-v2 status=400 code=invalid_parameter 12:20:09 [ERROR] model=open-v2 status=404 code=model_not_found遇到第二类片段,基本可以直接把这个模型从池子里摘除,因为它已经不是“临时不可用”,而是“永久消失”了。如果继续留在池子里,不仅本身浪费重试机会,还可能导致任务整体超时。
5. 模型池的容灾设计:自动切换、熔断和健康巡检
5.1 流量自动切换策略
模型池的容灾核心不是把模型修好——你修不好别人的服务——而是把流量导到还活着的模型上。我的路由层实际执行三个动作:
动作一是失败计数。每次请求失败,该模型失败数加1,成功则减1,按滑动窗口维护。当一个模型的失败率超过50%且样本数大于10时,它被标记为“half-open”状态,自动间歇降权。
动作二是熔断降级。连续失败达到5次,直接熔断5分钟。熔断期间所有本该发给它的流量,按权重发给其他模型。
动作三是优先级漂移。每个模型有一个动态得分,等于“基础权重除以近期失败率”。这样即便某个模型设置的是高优先级,一旦失败率飙升,它也会自动排到后面去。
这套逻辑不复杂,但非常管用。4个模型失效的时候,我基本没有手动干预,所有任务都是靠剩余模型撑过来的。唯一的代价是部分任务耗时变长、输出质量略降。
5.2 健康检查驱动的自动剔除
光靠路由层的失败计数还不够,因为某些模型在低频调用下不会暴露问题。我加了一个专门的健康检查脚本,定时跑起来,对池子里所有模型发一条极小的探针请求,比如“返回OK”,然后记录响应码和延迟。
这个脚本挂在crontab里,每10分钟跑一次。每次执行都会生成一条JSON结构的记录,包含每个模型的可用状态。如果连续3次健康检查都失败,脚本会把模型从主动路由名单中移除,并发一条告警通知。
这里有个细节:探针请求也走生产请求的同一个路由层和密钥体系,这样测出来的结果才是真实可用的。用单独的测试key测出来的“可用”,不能代表线上真实状态。
5.3 密钥、额度和到期时间的管理
这次挂掉的4个模型里,有2个和密钥、额度直接相关。做免费模型池,密钥管理不能随手往配置文件里塞。我的做法是:
把每个模型的base_url、api_key、到期日期放在一个独立的环境变量文件里,代码运行时加载,不要把密钥提交到代码仓库。同时维护一个到期清单,每个模型的额度到期日期都记上,到期前3天自动提醒。
有人会问,有些平台根本没给到期日期,怎么知道什么时候到期?我的经验是,优先看平台控制台或开发者文档里的“额度有效期”字段,找不到的话,就用历史调用日志自测——连续两天出现401,基本可以判定密钥失效,直接排查到对应平台。
5.4 免费模型池的容量策略
模型池的容量管理,我后来定了个规矩:池子里至少保留8个模型,云端免费模型不少于4个,本地模型至少2个。少于这个数,就触发“补充模型”告警。
算一笔账可以说明为什么定这个数:假设单个免费模型的月可用率是70%(这是真实水平),单模型故障概率确实挺高。但如果池子里有8个模型,路由层每次调用只随机挑一个,只要有一个模型活着就能完成任务。8个里全部挂掉的概率是0.3的8次方,几乎不可能。关键是保证数量冗余,而不是让每个模型都100%稳定。
6. 免费模型池运维的常见问题和避坑经验
6.1 典型问题速查
| 问题现象 | 大概率原因 | 优先排查路径 |
|---|---|---|
| 突然大量401 | key被吊销/额度到期 | 看平台控制台,查密钥状态 |
| 突然大量404 | 模型被下线/改名 | 看接口文档,确认model字段 |
| 持续429 | 免费额度触限 | 降并发,换备用模型 |
| 响应时间暴涨 | 平台对免费档降优先级 | 记录延迟,准备切换 |
| 偶发500 | 平台内部故障 | 重试1-2次,失败熔断 |
| 任务没跑 | cron服务没拉起 | 检查crond进程和crontab日志 |
6.2 几条实际操作中总结的经验
第一条,不要信仰任何一个免费模型。我今天觉得某某模型稳定,可能在明天就没了。免费池的运维心态应该是“把每个模型当作临时工”,而不是“把它当固定资产”。所以在设计上,不要对任何模型做硬编码或写死逻辑,一切通过配置化和路由层调度。
第二条,日志和告警的重要性高于模型本身。模型可以随便换,但你要知道它什么时候挂的、为什么挂的,靠的就是日志。我这次能把4个模型的失效原因逐一还原,完全仰仗日志设计做得好。如果你的Agent还没日志,请一定从今天开始补上,不然等于闭着眼开车。
第三条,免费模型的“有效生命周期”比你想的短。从我这次的数据看,10个免费模型6天内失效4个,半衰期不到10天。这意味着免费池的巡检频率至少要按天来,而不是按月。可以设计一个每日自动巡检报告,早上看一眼报告,就知道哪些模型昨晚开始不稳了。
第四条,本地模型是最后的避风港。6天里唯一让我踏实的是本地那3个Ollama模型。不管外部免费额度怎么变,本地模型永远在线。虽然能力弱一些,但是在关键任务上兜底,我觉得比云端的免费额度更可靠。建议自养Agent到后期,资源充裕的话务必准备一个本地模型作为“地基”模型。
6.3 从这次事故里学到的调度心得
过去我只把“模型池”当成一个简单的资源集合,现在我会把它当成一个需要持续治理的系统。所谓治理,就是三个循环:
第一,准入循环。新模型入池前,先跑一天探针,确认错误率和延迟达标,再放量到生产流量里。
第二,巡检循环。每天自动跑健康检查,更新每个模型的可用状态,生成可用性报表。
第三,淘汰循环。连续挂了或劣化超过阈值的模型,自动摘除并进入“冻结”状态,等平台修复后再重新评估是否入池。
这三个循环看起来很机械,但正是这种机械,让我在模型池只剩6个可用模型时,还能保证Agent业务基本不受影响。自养Agent本来就够折腾了,能把模型资源的稳定性管理做到“让它在后台自动转”,你就可以把精力放在Agent的业务逻辑本身。
最后再说一点:免费池这东西,本质上是用自己的运维时间换成本。如果你没有精力做日志和巡检,那我建议还是老老实实买付费API。你图免费省下的钱,最后可能会在排查事故的加班里加倍还回去。
我自己现在跑着的池子,仍然保留着免费和本地混合的模式,但每次新加模型,第一件事永远是写日志配置、挂上探针。毕竟,这次的6天4个只是开始,模型池的维护是日常活,不是一次性的。