游乐游手机版
首页/AI教程/文章详情

自主Agent可观察性工程实践:如何知道Agent挂了?

时间:2026-06-05 16:44
三个月前,有支AI Agent在凌晨2点挂了。 它的任务听起来挺简单:每天抓数据、生成报告、再推给下游系统。但挂了就是真挂了——没有报错、没有告警,下游系统只是安安静静地停止接收新数据。直到第二天早上有人问“昨天的报告呢”,才发现出事了。 当时所谓的“监控”是什么?就是每过一小时手动跑一遍 ps a

三个月前,有支AI Agent在凌晨2点挂了。

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

它的任务听起来挺简单:每天抓数据、生成报告、再推给下游系统。但挂了就是真挂了——没有报错、没有告警,下游系统只是安安静静地停止接收新数据。直到第二天早上有人问“昨天的报告呢”,才发现出事了。

当时所谓的“监控”是什么?就是每过一小时手动跑一遍 ps aux | grep agent

回过头来看,这其实是犯了一个根本性的错误:错把“进程活着”当成了“Agent正常运行”。

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

普通服务要是挂了,看HTTP返回的5xx状态码就一目了然。但Agent的行为模式要复杂得多:

  • 它可以「活着但卡住」——进程还在,但LLM调用被限速重试卡了三个小时,半点进度都没推进
  • 它可以「活着但走错路」——任务确实在执行,但每一步都在做错误决策,直到资源耗尽了才崩溃
  • 它可以「静默失败」——工具调用返回了空数组,Agent判断“没有数据”,然后正常退出,可实际上是查询条件写错了

传统的“进程是否存活”检测,对这三种情况几乎全部失效。要真正掌握Agent的健康状态,需要的是语义级别的健康检测。

第一层:心跳 ≠ 进程探活

拿OpenClaw跑Agent的实践来说,它自带heartbeat机制,但最初方向就配错了:

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

进程活着真不等于Agent在干活。正确做法是让Agent主动写入心跳时间戳:

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

然后需要一个独立的watchdog进程来检查心跳是否超时:

// watchdog.js async function checkHeartbeat() { const lastBeat = await db.get('agent:heartbeat:last'); const age = Date.now() - lastBeat.timestamp; if (age > 10 * 60 * 1000) { // 10 分钟没心跳 await alert.send(`Agent 疑似卡死,上次心跳 ${Math.round(age/60000)} 分钟前,步骤:${lastBeat.step}`); } }

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

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

Agent执行到一半挂了,这种情况最难处理:重启后不知道跑到哪一步了,从头跑可能会重复操作,不跑又会导致数据丢失。

现在的做法是在每个“不可逆操作”前都写入检查点:

class AgentCheckpoint { constructor(runId, storage) { this.runId = runId; this.storage = storage; // Redis / 本地 SQLite 均可 } async sa ve(step, state) { await this.storage.set(`checkpoint:${this.runId}:${step}`, {step, state, sa ved_at: Date.now()}); console.log(`[checkpoint] sa ved step=${step}`); } async load(step) { return this.storage.get(`checkpoint:${this.runId}:${step}`); } async hasCompleted(step) { const cp = await this.load(step); return cp !== null; } } // 使用 async function runPipeline(runId) { const cp = new AgentCheckpoint(runId, redis); // 步骤 1:拉数据(幂等,可重跑) let rawData; if (await cp.hasCompleted('fetch')) { rawData = (await cp.load('fetch')).state.data; console.log('[resume] skipping fetch, loaded from checkpoint'); } else { rawData = await fetchData(); await cp.sa ve('fetch', { data: rawData }); } // 步骤 2:LLM 处理(有成本,不可随意重跑) let analysis; if (await cp.hasCompleted('analyze')) { analysis = (await cp.load('analyze')).state.result; } else { analysis = await llm.analyze(rawData); await cp.sa ve('analyze', { result: analysis }); } // 步骤 3:写入下游(只跑一次) if (!await cp.hasCompleted('push')) { await pushToDownstream(analysis); await cp.sa ve('push', { pushed_at: Date.now() }); } }

这段代码的核心价值在于:重启后能从上次成功的步骤继续执行,既不会重复调用LLM,也不会重复向下游写入数据。

第三层:语义健康检查

心跳功能只告诉你Agent还在跑,但回答不了“跑得对不对”这个问题。为此,每5分钟跑一次“语义探针”会很有帮助:

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

在生产环境中,探针问题可以设计得更复杂——比如“处理一条测试数据,然后验证输出格式是否正确”。核心思路清晰:有输入、有期望输出、机器能自动判断对错。

第四层:故障恢复自动化

前三层解决的是“发现问题”,那发现之后呢?

过去的流程是:收到告警 → 手动SSH → 查日志 → 重启。凌晨3点碰到这种情况,基本不现实。

现在把恢复动作直接写成代码:

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

一定要设硬上限。自动恢复功能很实用,但无限重启只会掩盖真正的bug,还会白白烧钱——毕竟每次LLM调用都有成本。

现在的监控架构

踩了这三个月的坑,最终打磨出来的Agent监控框架是这样的:

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

四层加在一起,从“凌晨挂了到第二天早上才发现”变成了“5分钟内自动告警、30分钟内自动恢复或人工接管”。

踩坑总结

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

如果你的Agent目前也只有“进程监控”这一层,上面的代码可以直接参考使用。

来源:https://juejin.cn/post/7644466893395689506
上一篇FastMCP实战:30行Python打造AI数据库查询工具 下一篇手把手教你编写自己的Agent技能教程
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Synthesia零基础教程:客户端安装与工作区权限设置
AI教程 · 2026-06-07

Synthesia零基础教程:客户端安装与工作区权限设置

本文介绍了AI视频生成工具Synthesia的入门流程。内容涵盖从官网下载客户端、完成账户注册与登录,到软件安装与启动的完整步骤。详细说明了如何初始化工作区,包括创建首个AI视频项目、选择模板与AI主播。最后,指导用户理解并设置团队协作中的不同权限角色,以便安全高效地共同管理项目。

FramePack新手入门指南:安装启动报错修复导出全流程
AI教程 · 2026-06-07

FramePack新手入门指南:安装启动报错修复导出全流程

本文详细介绍了FramePack工具从下载安装到项目导出的完整流程。内容涵盖软件安装步骤、首次启动设置、常见报错解决方案以及项目打包导出方法。指南旨在帮助用户快速掌握工具核心操作,解决使用过程中可能遇到的技术问题,确保顺利完成AI视频帧处理任务。

FLUX.1保姆级教程:环境安装、显存优化与首次出图测试
AI教程 · 2026-06-07

FLUX.1保姆级教程:环境安装、显存优化与首次出图测试

本文详细介绍了FLUX 1的安装与初步使用流程。内容涵盖从Python环境配置、代码仓库克隆、依赖包安装,到关键的显存优化设置,最后指导用户完成首次文生图测试。教程旨在帮助用户顺利搭建运行环境,解决常见安装问题,并实现基础图像生成功能。

AnythingLLM新手实战:本地大模型部署后知识库接入设置
AI教程 · 2026-06-07

AnythingLLM新手实战:本地大模型部署后知识库接入设置

本文介绍了在本地部署大模型后,如何为AnythingLLM设置知识库。内容涵盖知识库的基本概念、创建与配置步骤、文档上传与处理技巧,以及如何通过问答测试其效果。旨在帮助用户有效整合本地文档资源,构建个性化的AI知识助手,提升信息检索与利用效率。

Aider安装失败排查:扩展冲突与登录异常全解析
AI教程 · 2026-06-07

Aider安装失败排查:扩展冲突与登录异常全解析

本文针对Aider安装过程中常见的扩展冲突与登录异常问题,提供了系统的排查思路与解决方案。内容涵盖如何识别并处理与其他AI工具的兼容性问题,解决因网络或账户设置导致的登录失败,以及通过环境检查、依赖更新等步骤彻底排除安装障碍,帮助用户顺利完成安装与配置。