news 2026/5/28 16:47:22

Agent 挂了我怎么知道?自主Agent 的可观察性工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent 挂了我怎么知道?自主Agent 的可观察性工程实践

三个月前,我的 AI Agent 在凌晨 2 点挂了。

它负责每天抓取数据、生成报告、推送给下游系统。挂了之后什么都没发生——没有报错,没有告警,下游系统只是静静地不再收到数据。直到第二天早上用户问"昨天的报告怎么没出来",我才发现。

当时的状态监控就是:每小时ps aux | grep agent

这是我犯的第一个根本性错误:把"进程存活"当成"Agent 正常运行"。


为什么 Agent 的可观察性比普通服务更难

普通服务挂了,你看 HTTP 5xx 就知道了。Agent 不一样:

  • 它可以是「活着但卡住」:进程在跑,但 LLM 调用卡在 rate limit retry 里,三小时没有实质进展
  • 它可以是「活着但走错路」:任务执行了,但每一步都在做错误决策,直到资源耗尽才崩
  • 它可以是「静默失败」:工具调用返回空数组,Agent 认为"没有数据",正常退出,但实际上是查询条件写错了

传统的"进程是否存活"检测对这三种情况全部失效。你需要的是语义级别的健康检测


第一层:心跳 ≠ 进程探活

我用 OpenClaw 跑 Agent,它有内置的 heartbeat 机制。但我最初配错了方向:

// 错误配置:只检测进程{"heartbeat":{"interval":"30m","check":"process"}}

进程活着 ≠ Agent 在干活。正确的做法是让 Agent主动写入心跳时间戳

// agent/main.js — 每完成一个工作单元就更新asyncfunctionprocessTask(task){awaitupdateHeartbeat({task_id:task.id,step:'started',timestamp:Date.now()});constresult=awaitllm.call(task.prompt);awaitupdateHeartbeat({task_id:task.id,step:'llm_done',tokens_used:result.usage.total_tokens,timestamp:Date.now()});// ... 后续步骤}

然后有个独立的 watchdog 进程检查心跳是否超时:

// watchdog.jsasyncfunctioncheckHeartbeat(){constlastBeat=awaitdb.get('agent:heartbeat:last');constage=Date.now()-lastBeat.timestamp;if(age>10*60*1000){// 10 分钟没心跳awaitalert.send(`Agent 疑似卡死,上次心跳${Math.round(age/60000)}分钟前,步骤:${lastBeat.step}`);}}

关键点:watchdog 必须是独立进程,不能跟 Agent 在同一个进程里——否则 Agent 崩了 watchdog 也跟着没了。


第二层:状态快照与检查点

Agent 执行到一半挂了最难处理:重启后不知道跑到哪里了,从头跑可能重复操作,不跑又丢数据。

我现在的做法是每个"不可逆操作"前都写检查点

classAgentCheckpoint{constructor(runId,storage){this.runId=runId;this.storage=storage;// Redis / 本地 SQLite 均可}asyncsave(step,state){awaitthis.storage.set(`checkpoint:${this.runId}:${step}`,{step,state,saved_at:Date.now()});console.log(`[checkpoint] saved step=${step}`);}asyncload(step){returnthis.storage.get(`checkpoint:${this.runId}:${step}`);}asynchasCompleted(step){constcp=awaitthis.load(step);returncp!==null;}}// 使用asyncfunctionrunPipeline(runId){constcp=newAgentCheckpoint(runId,redis);// 步骤 1:拉数据(幂等,可重跑)letrawData;if(awaitcp.hasCompleted('fetch')){rawData=(awaitcp.load('fetch')).state.data;console.log('[resume] skipping fetch, loaded from checkpoint');}else{rawData=awaitfetchData();awaitcp.save('fetch',{data:rawData});}// 步骤 2:LLM 处理(有成本,不可随意重跑)letanalysis;if(awaitcp.hasCompleted('analyze')){analysis=(awaitcp.load('analyze')).state.result;}else{analysis=awaitllm.analyze(rawData);awaitcp.save('analyze',{result:analysis});}// 步骤 3:写入下游(只跑一次)if(!awaitcp.hasCompleted('push')){awaitpushToDownstream(analysis);awaitcp.save('push',{pushed_at:Date.now()});}}

这段代码做到了:重启后从上次成功的步骤继续,不重复 LLM 调用,不重复写入下游。


第三层:语义健康检查

心跳告诉你 Agent 在跑,但不告诉你跑得对不对。我加了一个每 5 分钟跑一次的"语义探针":

asyncfunctionsemanticHealthCheck(agent){// 发一个有已知答案的探针问题constPROBE={input:"2+2等于多少?",expected_pattern:/4/};conststart=Date.now();constresult=awaitagent.run(PROBE.input,{timeout:30_000});constlatency=Date.now()-start;constmetrics={latency_ms:latency,responded:result!==null,correct:PROBE.expected_pattern.test(result?.output||''),timestamp:Date.now()};awaitmetrics.record('agent.health',metrics);if(!metrics.correct){awaitalert.critical(`语义健康检查失败:探针问题回答异常,latency=${latency}ms`);}if(latency>20_000){awaitalert.warn(`Agent 响应过慢:${latency}ms`);}returnmetrics;}

真实生产中,探针问题可以更复杂——比如"处理一条测试数据,验证输出格式正确"。核心是:有输入、有期望输出、机器可判断对错


第四层:故障恢复自动化

上面三层都是"发现问题"。发现之后呢?

我之前的流程是:收到告警 → 手动 SSH → 查日志 → 重启。这在凌晨 3 点不现实。

现在的做法是把恢复动作编成代码:

classAgentSupervisor{constructor(agentFactory,options={}){this.agentFactory=agentFactory;this.maxRestarts=options.maxRestarts??3;this.restartWindow=options.restartWindow??3600_000;// 1h 内最多 N 次this.restartHistory=[];this.agent=null;}asyncstart(task){this.agent=awaitthis.agentFactory();try{returnawaitthis.agent.run(task);}catch(err){returnthis.handleFailure(err,task);}}asynchandleFailure(err,task){constnow=Date.now();this.restartHistory=this.restartHistory.filter(t=>now-t<this.restartWindow);if(this.restartHistory.length>=this.maxRestarts){// 超过重启次数上限,人工介入awaitalert.critical(`Agent 在${this.restartWindow/60000}分钟内重启了${this.maxRestarts}次,停止自动恢复,等待人工处理`,{error:err.message,last_checkpoint:awaitthis.getLastCheckpoint()});throwerr;}this.restartHistory.push(now);constdelay=Math.min(1000*2**this.restartHistory.length,60_000);awaitalert.warn(`Agent 崩溃,${delay/1000}s 后自动重启(第${this.restartHistory.length}次)`,{error:err.message});awaitsleep(delay);// 重启并从检查点恢复this.agent=awaitthis.agentFactory();returnthis.agent.resumeFrom(task,awaitthis.getLastCheckpoint());}}

重点:设硬上限。自动恢复很好,但无限重启会掩盖真正的 bug,还会烧钱(LLM 调用是有成本的)。


现在的监控架构

三个月踩坑下来,我的 Agent 监控长这样:

┌─────────────────────────────────────────┐ │ Agent 主进程 │ │ ┌─────────┐ ┌──────────┐ ┌────────┐ │ │ │ 心跳写入 │ │ 检查点存储│ │ 指标上报│ │ │ └────┬────┘ └────┬─────┘ └───┬────┘ │ └───────┼─────────────┼────────────┼───────┘ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Redis │ │ SQLite │ │ InfluxDB│ └────┬────┘ └─────────┘ └────┬────┘ │ │ ▼ ▼ ┌─────────┐ ┌─────────┐ │Watchdog │ │ Grafana │ │(独立进程)│ │(告警规则)│ └────┬────┘ └────┬────┘ │ │ └──────────┬──────────────┘ ▼ ┌──────────┐ │ 告警通知 │ │(TG/邮件) │ └──────────┘

四层加一起,从"发现凌晨挂机要到早上"变成了"5 分钟内自动告警、30 分钟内自动恢复或人工接管"。


踩坑总结

  1. 不要用进程活着当健康指标——用语义心跳
  2. watchdog 必须独立于 Agent 进程——否则 Agent 崩了什么都不知道
  3. 每个不可逆操作前存检查点——幂等重跑比重来成本低很多
  4. 自动恢复要设上限——无限重启=无限烧钱,而且掩盖真实问题
  5. 语义探针比日志更早发现问题——日志记录的是发生了什么,探针检测的是能不能正常工作

如果你的 Agent 现在也只有"进程监控",这篇文章里的代码可以直接拿去用。有问题欢迎评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 16:46:05

Smithbox终极指南:如何成为游戏修改大师的5个步骤

Smithbox终极指南&#xff1a;如何成为游戏修改大师的5个步骤 【免费下载链接】Smithbox Smithbox is a modding tool for Elden Ring, Armored Core VI, Sekiro, Dark Souls 3, Dark Souls 2, Dark Souls, Bloodborne and Demons Souls. 项目地址: https://gitcode.com/gh_m…

作者头像 李华
网站建设 2026/5/28 16:45:25

数据预处理全流程:图像、文本与视频的统一处理管线

系列导读 你现在看到的是《多模态大模型应用开发实战:从原理到工程落地的完整指南》的第 3/10 篇,当前这篇会重点解决:构建健壮的数据管线,为多模态模型训练和推理提供高质量输入,避免垃圾进垃圾出。 上一篇回顾:第 2 篇《环境搭建与推理优化:多模态模型本地部署避坑指…

作者头像 李华
网站建设 2026/5/28 16:45:07

虚幻引擎5时代,Cascade粒子系统用户如何用官方插件一键迁移到Niagara?

虚幻引擎5迁移指南&#xff1a;Cascade粒子系统到Niagara的高效转换策略当项目资源库中积累了大量Cascade粒子特效时&#xff0c;引擎版本升级带来的技术栈更新往往令人头疼。作为技术美术或初级程序员&#xff0c;你可能已经注意到虚幻引擎5官方文档中那些不太显眼却至关重要的…

作者头像 李华
网站建设 2026/5/28 16:44:22

STM32 HAL库点灯实战:从CubeIDE配置到LED闪烁完整指南

1. 项目概述与准备工作拿到一块STM32黑金板&#xff08;Blackpill&#xff09;&#xff0c;看着上面那颗小小的LED&#xff0c;很多朋友的第一反应可能就是“点灯”。别小看这个操作&#xff0c;它就像嵌入式世界的“Hello World”&#xff0c;是你与这块芯片建立沟通的第一步。…

作者头像 李华