Multi-Agent系统可观测性实战:从链路追踪到思维过程还原
2026/9/16 4:42:55 网站建设 项目流程

1. 先搞清楚Multi-Agent系统的可观测性到底难在哪

做个假设,你现在负责一个由五六个Agent协作完成复杂业务任务的系统,它们有的负责拆解用户意图,有的负责检索知识库,有的负责调外部API,有的负责汇总结果。某天下午,系统对用户的普通提问突然返回了一个莫名其妙的答案,代码层面没有任何报错,日志里全是正常级别的信息,模型也没有超时或触发限流。你打开监控大盘,所有指标看起来都正常,但结果就是不对。这种状况我经历过不止一次,坦白说,它比服务宕机还让人抓狂——因为宕机至少有一个明确的排查起点。

先说结论:Multi-Agent系统的可观测性难题,本质上是"分布式的复杂性问题"和"大模型的不确定性问题"叠加在了一起。传统监控工具不是没用,而是远远不够用。

1.1 分布式系统的老问题,在Agent场景里被放大了

凡是做过微服务的人都知道,排查一个跨服务调用的问题,需要trace ID贯穿整条调用链。Multi-Agent系统本质上也是一个分布式系统,多个Agent各自运行,彼此通过消息通信,共享某些状态或工具。但相比微服务,它有几个特别麻烦的地方。

第一,调用关系是动态的。微服务之间的调用路径是代码写死的,A调B,B调C,链路固定。而Agent系统里,Agent A可能根据上下文决定是直接调用Agent B,还是先自己处理一部分再让Agent B接手,甚至可能同时协调Agent B和C并行工作。调用关系本身是模型"想"出来的,不是代码预先定义的。所以日志里常见的"服务名+接口名"维度,在Agent场景下根本没法提前打标。

第二,状态是共享且分散的。多个Agent经常会读写同一个共享状态,比如一个业务上下文,或者一个工具的执行结果缓存。微服务虽然也有共享存储,但状态变更逻辑是确定的。Agent系统里,每个Agent都可能根据自己对上下文的理解去修改状态,改完还不一定记录"为什么改"。这就像几个人同时在一张白纸上写字,谁写了自己的部分,白纸最后长什么样,很难追溯。

第三,失败是"软性"的。微服务超时、报错,至少有个明确的失败信号。Agent系统里最常见的失败是"模型没崩,但理解错了",它执行了正确的流程,却跑在错误的前提上。这种失败没有任何异常堆栈,只有结果偏离预期,而"偏离预期"这个判断本身,又需要结合业务语义,不是简单的状态码能表达的。

1.2 Agent系统的黑盒部分,恰恰是最需要被观测的

这也是我最想强调的一点。在传统系统里,链路追踪覆盖的是"代码执行路径",每一条分支都是开发者写出来的,可观测性要解决的,是搞清楚"走了哪条分支、为什么走这条分支"。但在Agent系统里,很大一部分"决策逻辑"不在代码里,而在模型的权重里。模型为什么决定先调用工具A再调用工具B?为什么突然换了策略?为什么某个Agent放弃了当前任务转去处理别的?这些决策过程,只有模型自己知道。

所以,Multi-Agent系统的可观测性,观测对象不再仅仅是"系统的行为",还包括"模型的思考过程"。这不是说要把模型输出的所有token都记录下来——Token级别的内容,绝大多数都没有长期存储价值。真正需要记录的是:模型接收到了怎样的上下文,做出了怎样的决策,调用了什么工具、传入了什么参数,工具返回了什么结果,模型基于这个结果又如何调整下一步计划。这一层层嵌套的信息,才构成了一个Agent的"思维轨迹"。

我还想纠正一个常见的误区,就是"把Agent的所有输入输出都打日志就是可观测性"。如果只是机械地记录,排障时你还是得面对几百条毫无关联的日志,靠肉眼去拼凑发生了什么。可观测性的核心价值是"还原因果",不是为了存数据而存数据。所以设计这套体系的时候,脑子里要时刻想着一个问题:如果明天线上出了诡异问题,我需要哪些信息才能推演出完整的因果链?

1.3 传统监控工具链的适配性和缺口

这么说吧,Metrics(指标)、Logging(日志)、Tracing(链路追踪)这套经典的可观测性三支柱,在Multi-Agent系统里都是必要的,但单独拿出来又都不够。

Metrics能告诉你系统整体健康度:QPS、延迟、Token消耗、错误率。但它回答不了"为什么",它只能告诉你"出问题了"。而且Agent系统的很多问题不会体现在这些常规指标上——比如某个Agent在逻辑上绕了远路,多调了十几个工具,但整体延迟依然在阈值内,表面上看一切正常,实际效率已经严重退化。

Logging是排障的基础,但Agent系统的日志有两个天然短板。一是日志量大,每个Agent的每次思考、每个工具调用、每轮消息,如果全量记录,存储和查询成本都很高。二是日志之间的关联性差,如果没有统一的链路ID贯穿,几条日志谁先谁后都说不清楚。

Tracing是最接近需求的手段,分布式追踪的模型——trace、span、event——天然适合描述Agent的多层嵌套调用。但问题在于,开源的Tracing方案大多是为RPC调用设计的,span的语义是"一次远程调用",而Agent的"一次推理"和"一次工具调用"之间,不是简单的父子调用关系,更像是一个循环里的多次决策步骤。直接套用现成模型,你会发现在页面上看到的是乱七八糟的span树,很难还原出Agent真正的思考过程。

所以我的看法是:构建Multi-Agent系统的可观测性,不能指望某一个现成工具全搞定,而是需要一套从数据模型到采集方案都经过重新设计的体系,把传统手段作为基础设施,在它上面叠加Agent特有的观测维度。下一节就聊聊我是怎么拆解这个问题的。

2. 分层可观测:从消息链路到思维过程,一个都不能少

在传统分布式系统的可观测性设计里,我们习惯按基础设施、应用、业务三层去做。但Agent系统的观测对象多了一个"模型决策层",而且这层往往还是最关键的。我实践下来比较有效的做法,是把它拆成四个层面,每个层面解决一类问题,四个层面拼在一起,才能还原完整的因果链。

2.1 消息层:Agent之间通信的确定性追踪

最底层的是消息层。无论Agent之间是通过消息队列、事件总线,还是直接函数调用,每一次通信都需要有一个全局唯一的message ID,并且在日志里天然带上发送方、接收方、消息类型、时间戳。这一层解决的问题是"谁在什么时候给谁发了什么内容"。

这里我踩过一个比较深的坑。早期我们把消息内容直接序列化成JSON打进日志,结果线上一个Agent每轮对话要和另一个Agent交换十几次信息,一次完整任务下来,光消息日志就有上百条,每条还都很大。排障的时候想找"某个业务会话中,Agent C第一次接到的是什么消息",光靠日志检索就得折腾半天。后来改成:消息落库时拆成meta和payload,meta单独建立索引,payload按需存储。链路追踪只需要meta,payload在需要回溯具体内容时才去查询。这样既保证了可观测性,又控制了存储成本。

另外,消息层的观测必须带上"语义摘要"。一条消息原文可能几千个token,但在链路追踪里,我们只需要看它的摘要,比如"Agent A向Agent B传递了用户关于订单状态的查询意图,附带订单号ORD-20241001"。这个摘要可以由发送方Agent在发送时通过一次LLM调用生成,也可以由规则提取关键字生成。实际操作中,规则提取效果往往不稳定,但胜在便宜;LLM摘要质量高,但会增加成本和延迟。我的建议是:核心链路用LLM摘要,边缘消息用规则截取。不要试图对所有消息都做高质量摘要,成本会失控。

2.2 状态层:每个Agent的内部快照和上下文变化

Agent系统的bug,很大比例出在状态管理上。某个Agent维护了一段内部上下文,在处理过程中逐步更新,但某个分支下它忘记更新了,或者更新错了,后续它所有的决策都会基于一个过期的上下文展开,这种问题只有在状态层才能被发现。

状态层要记录的是:每个Agent在某一个时刻持有哪些关键状态。这里说的状态不是模型参数,而是Agent运行时的业务状态,比如当前任务列表、已完成步骤、引用文档列表、临时变量、对外部工具的缓存结果等。记录方式应该是一个个"状态快照",在Agent每次状态发生变更时打一个快照,并关联到当前的trace ID和message ID。

比较反直觉的是,快照不一定要全量存储。很多状态对象很大,全量存储不现实。我用的方案是:正常运行时每N步打一个全量快照,其他变更只记diff。这样排障时,先用diff把变更路径找出来,定位到某一步之后再去看那一步的全量快照。这有点像Git的commit机制,storage开销可控,而且能够支持"回到某个时间点查看现场"。

状态层还有一个容易遗漏的观测维度:Agent之间的共享状态。如果多个Agent都在读写同一个上下文(比如一个共享的工作记忆区),那这个共享状态本身也需要被观测,记录每次读写的Agent、时间和变更内容。这个以后在调试案例里会提到,多Agent互相覆盖状态,几乎是最难排查的问题之一。

2.3 过程层:模型每一步推理的可视化呈现

如果说状态层解决的是"Agent看到了什么",过程层解决的就是"Agent基于这些信息做了什么决策"。这层在技术上没有统一标准,我的做法是:在Agent的推理循环(reasoning loop)里注入观测回调,把每个循环步骤的数据都导出。

具体记录的数据包括:模型输入(即当前Prompt的内容)、模型输出的决策(选了哪个工具、传了什么参数、还是决定直接返回)、工具的执行结果、以及模型的下一步计划。每一条记录都关联到当前trace ID,并且在trace的UI里按时间轴展开。这样你就能看到类似这样的过程:Agent A读了用户提问,判断需要查订单系统,调用get_order工具,工具返回了一个订单状态,Agent A根据这个结果决定要不要让Agent B介入……整个过程一目了然。

在这个过程中,有一件只可意会的事:如何记录"模型的想法"。目前大部分Agent框架在调用LLM时,会返回reasoning_content之类的字段(不同模型叫法不同),这就是模型的推理过程。这个内容非常有价值,但体量也大。我的建议是:开发环境全量保存,生产环境保存一份摘要和关键阶段的完整推理,其他阶段只保存结论。如果生产环境出了问题,先用摘要判断哪个阶段可疑,再回到开发环境或采样环境去复现完整推理,这个思路比全量记录所有推理要实际得多。

2.4 可观测数据模型:围绕Agent场景重新设计trace和span的语义

最后聊数据模型。现在很多团队直接用OpenTelemetry的trace模型去套Agent系统,结果发现不对劲。问题在于,OpenTelemetry的span假设是一个树状调用结构,而Agent的行为更像是一个"带循环和条件跳转的流程图",甚至是一个多Agent协作的有向图。我最终采用的模型,是对OpenTelemetry做了一层扩展:

概念传统语义(微服务)Agent场景拓展
Trace一次用户请求的完整调用链一次Agent任务(从发起目标到全部完成)的完整执行轨迹
Span一次RPC调用或方法执行一次Agent决策周期、一次工具调用、一次子任务委派
Event日志事件(异常、信息)Agent的关键状态变化、人工干预、重试原因
Attribute请求元数据(URL、状态码)Agent角色、模型名称、Token消耗、上下文长度、温度参数等
Link跨Trace关联Agent之间的消息引用、共享状态变更关联

不要小看这个语义映射,它决定了后面所有观测数据怎么组织和查询。比如你接入了一个现成的Tracing UI,按span的类型去过滤时,你能轻松筛选出"所有调用过工具get_order的Agent决策",或者"所有把任务委派给Agent C的决策",这些筛选能力是排障的基础。如果只是把Agent的每一步都埋成一个普通的span,不做类型区分,那UI上的数据就是一锅粥。

数据模型定了,监控和告警才有的放矢。下一节说说指标体系和告警策略,这决定了你"能不能在中段就发现问题,而不是等用户反馈"。

3. 监控指标体系:哪些指标真正值得盯,告警怎么设才不吵

很多人一开始搭Agent系统监控,第一反应是看模型的调用量、延迟和费用。这些当然要看,但只看这些,你会错过大问题。我在生产环境跑了一段时间之后,总结了一套更贴合Agent特性的指标维度,按重要程度排了个序。

3.1 基础健康指标:不可缺失的地基

所有AI系统都该看的指标,这里快速过一遍:

  • 请求量(QPS):按Agent类型和工具类型拆分,看清每个Agent的负载。
  • 延迟分布:不要只看平均值,重点看P50、P95、P99。Agent系统的延迟分布经常是长尾的,P99可能比P50高出好几倍,这种长尾往往是Agent在个别场景下进入低效循环的信号。
  • Token消耗:按模型、按Agent、按会话拆分。Token用量异常上升,通常意味着某个Agent的Prompt在不断膨胀,或者陷入了自我循环。
  • 错误率和重试率:包括模型调用失败、工具调用失败、Agent内部异常。注意,Agent系统里"重试"不一定由框架拉起,模型经常自己决定"刚才工具没成功,我再试一次",这种重试同样要监控。

这一层是整个监控体系的地基,任何一层的观测数据都可以关联到这些指标。光有这些不够,下面这些才是Agent系统特有的。

3.2 Agent系统特有的指标:没有这些,你等于没监控

第一组指标,我管它们叫"决策质量指标"。包括:单任务平均决策步数(一个完整任务里Agent总共做了多少次决策)、单决策平均工具调用数、上下文重写频率(Agent被中断或任务切换时,上下文被重新构建的次数)。这些指标直接反映Agent的效率。案例:某次我们发现订单查询类任务的平均决策步数从5涨到了11,排查后发现是因为某个Agent在Prompt模板里多加了一段历史对话的拼接逻辑,导致模型每次都要先花几步"理解"那段历史,才进入真正的查询流程。

第二组指标,是"循环与停滞指标"。这是Agent系统最容易出问题、也最不容易被传统监控捕捉的地方。包括:单个Agent连续执行步骤数超过阈值(比如超过10步没产出外部可见结果)、同一个工具被连续调用N次且返回结果都一样、Agent把任务在多方之间来回传递超过K次。这些指标本质上是在做"行为模式识别",传统Metrics很难直接表达,需要写一些自定义 exporter,从消息层的日志数据里实时统计。实现上不复杂,Kafka Streams或Flink都能做,关键是阈值需要根据业务调参,没有一劳永逸的默认值。

第三组指标,是"上下文与状态健康度指标"。包括:上下文窗口占用率(当前会话的Prompt Token数占模型最大上下文的比例)、共享状态冲突次数(两个Agent在同一窗口期改写了同一份状态)、状态过期读取次数(Agent读取的某些缓存数据已经超过时效)。这几项要结合业务逻辑来定义,但它们往往是Agent系统"行为怪异"的根本原因。

3.3 告警策略:阈值告警会把你淹没,行为告警才救命

刚开始做告警的时候,我们按常规套路,给每个指标都设了固定阈值,然后就被告警淹没了。原因是Agent系统天然有高方差,同一个任务,因为用户表达方式不同,决策步数可以从3跳到15;同一个Agent,下午高峰期的延迟可能是凌晨的5倍。固定阈值要么太松,问题曝光时已经晚了;要么太紧,每天成百上千条告警根本看不过来。

后来我们调整了策略,核心就一句话:别只盯绝对值,要看变化趋势和分布偏移。具体做法是:

  • 对决策步数、工具调用数这类指标,用最近7天同时段的数据做基线,告警条件设为"当前值超过基线P95的1.5倍",而不是"决策步数>10"。
  • 对Agent之间消息传递次数,设一个硬上限告警,超过就触发,这个属于"无论业务怎么变都不该出现"的强异常。
  • 延迟、Token消耗这类指标,同时监控P95而不是平均值。平均值被少数慢请求拉高时,你可能已经在损失用户体验了,P95更敏感。

告警消息必须带上trace ID和会话ID,写着"决策步数异常,trace=xxxxx,当前15步,基线P95是6步"。没有上下文的告警就是噪音,运维同学收到告警还得自己去找日志,来回一折腾,处置效率就下来了。

3.4 监控大盘怎么设计:一张图能讲到什么程度

最后是关于面板设计的一些经验。给团队看的核心大盘,不要搞太多图,我建议控制在6-8个核心面板,每个面板都能回答一个核心问题:

  1. 当前在线Agent数量和任务吞吐量——一眼看清系统整体是否健康。
  2. 各类Agent的平均决策步数和工具调用数趋势——效率退化能第一时间体现。
  3. Token消耗按模型和Agent拆分——成本与异常信号。
  4. 工具调用成功率热力图——哪个工具开始不稳定。
  5. Agent之间消息传递网络图——看清楚协作拓扑是否正常,有没有消息风暴。
  6. 共享状态冲突的Top列表——谁在频繁改同一块状态。

另外一定要有一个"全部trace检索"的入口,这是排障专用的,不等于监控大盘,但要在面板上放一个直达链接。排障时最讨厌的就是从一个指标页跳到另一个指标页,跳来跳去就忘了最初那条线索。一张可检索的trace总览页,比10张花里胡哨的大盘更有用。

4. 调试实战:从"表现异常"到"揪出真凶"的完整链路

监控体系搭好之后,真正考验它的是排障场景。这一节我用三个实际踩过的案例,把完整的排查链路走一遍。这几个案例基本涵盖了Multi-Agent系统里最高频的几类疑难杂症。

4.1 案例一:Agent陷入了"自我对话式"死循环

现象:某个Agent在处理用户咨询时,系统整体延迟飙升,但CPU和内存都不高,模型调用量却异常大。

排查过程:

第一步,打开监控大盘看趋势。决策步数指标当场暴露了问题,某个会话的决策步数在10分钟内飙升到200多步,而正常情况应该不超过8步。点进对应的trace,看到一条超长的span链条。

第二步,在trace里按时间轴回放该Agent的过程层日志。发现在第6步之后,Agent A开始重复"调用Agent B,得到一份回复,然后把这份回复原样抛给Agent B,Agent B又原样回复",两者都不认为任务已经完成,但也没有推进任何实质进展。消息内容摘要显示,两个Agent仿佛在互相说"根据你的回复,我认为还需要进一步处理",然后对方又回复同样的内容。

第三步,查状态层快照。发现两个Agent的共享工作记忆里,有一份"用户问题澄清"的状态在反复被写入,但写入的内容完全一样。也就是说,Agent A认为"没有澄清清楚",反复触发澄清流程,Agent B每次响应后又把状态恢复原样,形成循环。

根因:Agent的停止条件设计不严谨。在框架代码里,Agent的循环终止条件是"模型主动决定结束",但模型在某个上下文下倾向于不结束——因为它判断"还有未澄清的信息"。修复方案分两步:一是给循环加硬性上限,超过N步强制终止并返回当前结果,这在生产环境必须存在,属于最后一道保险;二是修正Prompt中对"澄清"的判断标准,明确只有在某个具体字段缺失时才需要澄清,而现状是共享状态里那个字段永远不可能被写满,等于给了模型一个永远不会满足的终止条件。

4.2 案例二:工具调用的参数错乱,模型"自己说服了自己"

现象:用户查询订单状态,系统返回了一个状态完全不符的结果。模型没报错,工具也执行成功,工具返回值在日志里也正常,但最终给用户的答案是错的。

排查过程:

第一步,先从tracing里找到这次会话的trace,定位到调用order查询工具的那条span。展开工具调用的输入参数,发现模型传给工具的订单号是ORD-20241003,但用户实际问的是ORD-20241005。这个错位不是网络问题,是模型自己传错了参数。

第二步,往前追溯,看模型是基于什么信息做出了这个传参决策。过程层日志显示,模型在处理用户问题时,先从会话历史里提取了订单号,但历史里包含了两个订单号,模型提取时抓取了第一个,而没有验证它与用户当前提到的订单是否一致。

第三步,查看共享状态。发现另一个Agent在处理同一会话时,把共享上下文里的"当前订单号"覆盖成了ORD-20241003。也就是说,模型读到的压根不是用户最新提到的那个订单号,而是另一个Agent留下的残留状态。

根因:这个问题的本质不是模型传参能力不行,而是多个Agent操作同一个上下文时,缺少"字段级所有权"的约束。Agent A有权限写"当前订单号"这个字段,但它不知道这个字段已经被Agent B定义成了另一个语义。修复措施:一是在共享状态层引入字段归属机制,每个字段只允许特定Agent写入,其他Agent只能读;二是在Agent的Prompt里强化"使用上下文中的数据前,先确认数据来源",模型在收到指令后会倾向于重新确认。这不能根除,但能大幅降低发生概率。

4.3 案例三:Agent之间互相覆盖状态,最终结果张冠李戴

现象:有两个Agent,一个负责收集用户信息(Collector),一个负责填写业务订单(Filler)。某个会话中,用户先是查了一笔旧订单,然后在同一会话里又发起一个新的下单请求,结果Filler把查旧订单的信息填进了新订单里。

排查过程:

第一步,从trace的共享状态事件里搜索"冲突记录"。很快看到Collector和Filler在同一个5秒窗口内先后写入了"订单备注"字段,Collector写的是用户对旧订单的询问,Filler写的是新订单的备注,由于没有锁或版本控制,后写的数据覆盖了先写的。

第二步,查看两个Agent各自的状态快照。Collector认为自己存储的"用户近期订单"数据是最新的,它也会把这个数据作为会话上下文传给Filler。Filler读取共享状态时,拿到了刚被覆盖的数据,把新单和旧单信息搅在了一起。

第三步,看消息层的通信日志,发现这两个Agent从来没有就直接沟通,它们是并行执行的,完全通过共享状态交互。问题就出在"先读后写"没有原子性保障。

根因:在Agent系统的架构设计里,很多时候我们为了让Agent解耦,让它们都通过共享状态交互。但共享状态没有做并发控制时,并行Agent就会互相踩脚。修复措施比较复杂,我当时的做法是:给共享状态的每个写操作增加一个基于版本号的乐观锁,Agent在写之前必须先读版本号,写入时如果版本号不匹配就重试;同时在业务层面做了一次调整——新下单和查旧单这两个动作,从并行改为串行,用一条简单的流程编排规则控制。共享状态并发控制之后,这类互相覆盖的问题基本绝迹。

4.4 断点调试与回放机制:让Agent的"意识流"变成可回放的录像

最后说一个效率提升最明显的工具:时间旅行调试,也就是回放机制。

Agent系统的调试困境在于,你很难让它在生产环境按你的节奏"走一步停一步"。但如果你把过程层的每一步记录完整——模型输入、输出、工具入参、工具出参、状态变更——你就可以在事后把任意一次执行完整回放出来。回放时你可以做到:在当前这一步暂停,查看这之前的全部上下文和Agent的决策依据,甚至可以修改某一步的工具返回结果,看看Agent会不会走另一条路。这就是所谓的"时间旅行调试"。

实现上,这套能力比想象中简单。只要过程层日志里记录了每次工具调用前后Agent内部的上下文、模型输入和输出,就能在本地开发环境用相同的Prompt序列重新跑一遍模型。注意要记录的是Prompt的实际内容,而不是"Prompt模板ID",因为模板渲染之后的内容才是模型真正看到的东西。很多团队用Playground式的界面来做回放,线上trace一键导入,本地断点跑,效率提升非常明显。

5. 工具链选型与落地经验:开源方案和自研方案怎么选

聊完监控和排障,再说说工具链。这两年Agent可观测性的开源工具冒出来很多,每个都号称自己支持Agent场景,但实际用起来差距不小。我按使用场景把它们粗分成了三类,给出我的选型建议。

5.1 开源方案横向对比:LangFuse、LangSmith、Phoenix等

先看市面上主流的几个方案,都是可以直接拿来用的:

工具定位优点局限
LangFuseLLM可观测性与trace支持跟踪LLM调用、Prompt版本、反馈打分,成本低Agent协作的复杂链路可视化相对弱,对自定义事件支持一般
LangSmithLLM应用调试与评估和LangChain深度整合,回放对比能力强,支持数据集评估不开源,SaaS服务,数据要出域,很多团队过不了安全审计
Arize PhoenixLLM trace与评估开源可自托管,支持OpenTelemetry协议,社区活跃生态较新,部分功能不够成熟
OpenTelemetry + 自研扩展一切可观测性的地基标准统一、不被厂商锁定,前后端通用只提供数据采集标准,Agent语义映射要自己设计

我对这几款的真实感受是:如果团队刚起步、需要快速看到效果,LangFuse是最低门槛的选择,装个Docker就能跑,基本的LLM调用追踪都可以覆盖。如果对数据安全要求高,必须走自托管路线,那Phoenix值得花精力研究,因为它支持OpenTelemetry标准,后续迁移的沉没成本低。LangSmith我一般推荐在开发调试阶段用,生产环境因为数据合规问题很少长期依赖。

但不管选哪个工具,都要有心理准备:Agent系统的可观测性,不可能100%靠现成工具解决。原因前面也说过,Agent特有的多层嵌套、动态拓扑、共享状态冲突,现成工具很难全部覆盖。工具解决的是"采集和展示"的问题,"数据模型的Agent语义化设计"必须自己动手。所以我们最终的架构是:采集层用OpenTelemetry全家桶,中间加一层自己的Agent事件模型,展示层接了LangFuse和Grafana,排障时两个界面配合用。

5.2 自研轻量方案:什么时候该自己动手

自研不一定是坏事,也不一定很重。如果你的场景只是内部工具,Agent数量不多,业务逻辑相对固定,那完全可以用结构化日志 + 事件总线的轻量方案,省去一堆重依赖。

做法是这样的:Agent框架里定义好统一的日志结构,每次决策、每次工具调用、每次消息收发,都输出一行JSON格式的结构化日志,字段包括trace_id、agent_id、step_index、type、summary、payload_ref。所有日志发到Kafka或直接写对象存储,查询按trace_id聚合。展示层可以做得极简——一个网页,输入trace_id,按时间轴展开每一行日志即可。

这种方案的好处是:一是完全没有厂商锁定,所有数据都在自己手里;二是模型简单,团队成员理解成本低;三是配合日志检索系统(比如ELK或Loki),可以直接做关键字搜索,排障效率很高。缺点是缺少开箱即用的可视化能力,需要自己写一点前端,而且高级的回放、评估功能要自己造轮子。

我个人的经验标准是:当你的Agent系统每天任务量超过10万次,或者有多个Agent长期并行协作且需要精细排障时,自研的精力和成本已经超过引入成熟工具了。反之,如果你还在POC阶段或日任务量很小,先自研轻量方案,跑通业务再逐步引入开源工具,不要一上来就上一套大而全的监控平台。

5.3 数据量与采样策略:全量采集是个陷阱

这是个非常容易踩的坑。一开始我们都想"全量采集最保险",结果半个月后存储成本直接爆炸。以一个中等规模系统为例,每天10万次任务,每次任务平均10个决策步,每个决策步需要记录模型输入输出摘要、工具参数、状态快照,一天的数据量在几十GB到上百GB之间,索引之后成本还要翻几倍。

我的建议是分层采样策略:

  1. 全量采集"轻量信息":每个trace的meta信息,trace_id、时间、Agent类型、最终状态、决策步数、Token用量,这些量小,全量保存。
  2. 按比例采集"详细过程":状态快照、工具入参出参、模型输出的详细日志,按5%~10%比例采样。为了保证覆盖度,可以按会话ID哈希做一致性采样——同一个会话的所有步骤要么全采要么全不采,这样采样不会破坏单个会话的完整性。
  3. 按需全采"异常样本":任何指标触发告警的trace,或最终状态为失败的trace,必须全量保留详细过程。这些是最有价值的排障素材,不能省。
  4. 在线数据 + 离线归档:热数据保留7天用于日常排障,7天以后压缩归档到廉价存储,保留30~90天用于趋势分析。

这套策略落地后,我们每天的存储量降到全量方案的20%左右,而且排障时几乎没有遇到过"找不到数据"的情况。核心诀窍就一条:节省成本要省在"不太可能出问题的正常样本"上,绝不能省在异常样本上

5.4 团队落地时容易被忽视的环节

最后说两个团队协作层面的经验。

一是Agent开发同学的本地开发环境,也接上这套可观测性体系。很多团队只做了生产环境的埋点,本地跑Agent时完全没有trace,导致"本地复现不了、线上拍瞎猜"的恶性循环。正确做法是:本地开发时把trace导出到一个共享的Dev环境,开发同学可以互相查看对方的Agent执行轨迹。Agent的bug经常依赖具体数据和Prompt上下文,本地自己跑一遍往往复现不了,能在线查看同事的trace,协作效率会好很多。

二是排障流程的文档化。我强烈建议把排过的疑难问题整理成一个内部Wiki,按"现象、trace链接、根因、修复方式、如何防范"的结构记录。Agent系统里同一类问题会反复出现,因为模型版本一升级、Prompt一改,之前修过的问题可能又冒出来。有一个历史问题库,每次排障都可以先检索一遍,很多问题其实是有成熟方案的,不用每次都从零开始。

6. 刻意为之的边界设计:避免可观测性本身成为系统的性能陷阱

最后这部分,聊一个很多人搭完监控后才发现的问题:可观测性本身也是有成本的,不加约束的打点会让系统变慢、变贵,甚至影响原本要观测的行为。

6.1 打点对Agent决策性能的实际影响

Agent系统的执行链路比传统后端应用长得多。一次任务可能涉及多次LLM调用、多次工具调用,每次都要检查"是否需要记录、记录什么内容、写到哪里"。如果这些操作是同步的,Agent每走一步都要等日志写完才能继续,P95延迟会肉眼可见地变差。

我最早遇到过一次,加完详细日志后,Agent单次任务耗时增加了将近40%。一开始还以为是模型变慢了,查了半天最后发现是同步日志写入阻塞了推理循环。从那之后,我们定了一条铁律:Agent推理关键路径上,可观测性数据采集绝不能同步阻塞。日志一律异步上报,宁可丢几条日志,也不能让打点拖慢主流程。

具体做法是:Agent内部只做内存缓冲区写入,缓冲区满了或者到时间窗口就批量上报;上报走单独的线程池或直接发到消息队列,不影响主线程。因为可观测性数据的实时性要求通常不高,秒级或毫秒级延迟完全可以接受。

6.2 打点会改变Agent行为?还真会

这个现象不是我们团队先碰到的,但确实很值得警惕。大模型对Prompt非常敏感,如果你把一大段"本次决策内容摘要""当前状态快照"等观测数据拼进下一轮的Prompt里,模型可能会因为上下文内容的变化而做出完全不同的决策。特别是有的Agent框架在注入观测数据时,会把Debug信息挂在系统提示词里,不经意间改变了模型的注意力分布。

所以我的建议是:观测数据的采集、清洗、存储,都必须与模型输入严格分离。采集是在模型调用之外的旁路操作,绝不允许为了"方便调试"就把调试信息拼进模型输入。如果确实需要在Prompt里加一些辅助信息,也要设计成显式的、稳定的格式,并且变量名、描述都要谨慎设置,不要简单地印上JSON dump的结果。

6.3 可观测性数据的生命周期管理

最后说下数据的清理和归档。Agent系统的trace数据非常"专一"——只有新出问题的trace才有高频访问价值,时间一长,绝大多数trace都不会再被访问。所以我建议在数据生命周期管理上采取主动策略:

  • 热数据(7天内):全量保留,支持秒级查询。
  • 温数据(7~45天):按事件摘要保留,支持trace_id检索但明细需要延迟加载。
  • 冷数据(45~90天):只保留统计聚合值和异常样本详情,正常样本的明细删除或归档到冷存储。

不要舍不得删。保留一堆永远不会被查的明细数据,消耗的是存储成本和查询性能。我见过有团队把一年的全量Agent trace全存着,查询一个trace要十秒以上,监控面板一卡就是半分钟,这种体验下没人愿意用,体系自然就荒废了。可观测性是一个活系统,不是一次性搭完就结束的,它需要定期维护、调整采样率、清理数据,才能长期发挥价值。

从架构设计到工具选型,再到排障实战和成本控制,整个可观测性体系的建设,我最大的体会是:它不是一个单纯的技术问题,而是需要围绕"如何更快定位Agent系统的异常"这个核心目标,持续调整和迭代的工程实践。不要把自己困在某一套工具或某个框架里,真正值钱的是你对自己系统的理解有多深,以及你把这种理解转化成监控和排查手段的能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询