news 2026/10/26 7:14:49

Agent 可观测性:从 trace 结构化数据中直接挖掘信号,而非依赖 LLM Judge

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent 可观测性:从 trace 结构化数据中直接挖掘信号,而非依赖 LLM Judge

1. 为什么我开始怀疑 LLM Judge 这件事

做 Agent 可观测性这两年,我踩过最大的一个坑,就是过度依赖 LLM Judge 来评估 agent 的表现。一开始觉得这东西真香:把 trace 丢给一个强模型,让它打分、判断对错、给出改进建议,一套流程下来感觉特别"智能"。但用得越久,越发现几个要命的问题。

第一是成本和延迟。每次评估都要调用一次大模型,如果你的 agent 每天跑几千上万条 trace,光是评估费用就够呛。第二是不确定性。同一个 trace,换个 prompt、换个温度、甚至换个时间点跑,LLM Judge 给出的结论可能完全不一样。第三,也是最让我警惕的一点:LLM Judge 会掩盖 trace 本身就能直接告诉你的信息。

举个我实际遇到的例子。有个 agent 在调用工具时反复重试同一个 API,连续失败了 7 次才放弃。这个事实——"重试 7 次"——是 trace 里明明白白记录着的,根本不需要任何模型来判断。但当时我们的评估流程是先跑 LLM Judge,Judge 说"这个任务完成得不太好",我们再去翻 trace 找原因。绕了一大圈,其实 trace 自己早就把答案写脸上了。

所以这篇文章我想聊的核心就是标题那句话:agent traces 本身能告诉你什么,而不需要 LLM judge 介入。围绕这个思路,我会结合 tracelint、OpenInference、Phoenix 这几个工具,讲讲怎么从 trace 的结构化数据里直接挖出有价值的信号。这套方法适合所有在做 agent 开发、调试、监控的工程师,不管你是刚上手还是已经有一套评估体系,都能从中拿到能直接用的东西。

2. 先搞清楚:一条 agent trace 里到底有什么

2.1 trace 的本质是结构化事件流

很多人对 trace 的理解停留在"日志"层面,觉得它就是一堆文本。但现代 agent 可观测性工具(比如基于 OpenInference 标准的那些)产出的 trace,本质上是一棵有层级结构的 span 树。每个 span 代表一个操作单元:一次 LLM 调用、一次工具调用、一次检索、一次路由决策。span 之间有父子关系,有开始和结束时间,有输入输出,有状态码,有各种属性(attributes)。

这就意味着,trace 不是"给人读的文本",而是"给程序分析的数据结构"。这个认知转变非常关键。一旦你把 trace 当成结构化数据,你就会发现里面藏着大量可以直接计算、直接判断的信号,压根不需要一个模型来"理解"它。

我用 Phoenix 做可视化的时候,最直观的感受就是:一条 trace 展开后,你能清楚看到哪个 span 耗时最长、哪个 span 报错了、哪个 span 的输出是空的。这些都是确定性的事实,不是"模型觉得"。

2.2 一条典型 trace 的关键字段

为了后面讲清楚怎么分析,我先把一条典型 agent trace 里最值得关注的字段列一下。不同工具命名略有差异,但核心大同小异:

字段类别具体字段能说明什么
结构信息span_id, parent_span_id, trace_id调用层级、谁调用了谁
时间信息start_time, end_time, duration耗时分布、瓶颈定位
类型信息span_kind (LLM/TOOL/RETRIEVER/CHAIN)这一步在干什么
状态信息status_code, status_message成功/失败/异常
输入输出input.value, output.value实际传了什么、返回了什么
模型信息llm.model_name, token_count用了哪个模型、花了多少 token
工具信息tool.name, tool.parameters调了哪个工具、参数是什么

这张表里的每一行,都是不需要 LLM 就能提取和判断的。你写几行代码就能统计出:这次运行调了几次工具、失败了几次、总 token 消耗多少、最慢的 span 是哪个。这些统计本身就是极强的诊断信号。

2.3 为什么结构化信号比"模型判断"更可靠

我打个比方。你去医院体检,血常规报告上写着"白细胞 12.5,参考范围 4-10"。这个数字是仪器直接测出来的,客观、可复现。而"医生看了报告说你觉得怎么样"——这是判断,依赖医生的经验和状态。LLM Judge 就类似于后者。

trace 里的结构化信号就是"血常规数字"。重试次数、错误率、延迟分布、token 消耗、工具调用序列——这些都是仪器测出来的客观值。它们不会因为换个 prompt 就变,不会因为模型版本更新就漂移。你可以把它们写进 CI,设成告警阈值,做成 dashboard,稳定得很。

我的经验是:能用确定性规则从 trace 里算出来的结论,就不要交给 LLM Judge。把 LLM Judge 留给真正需要"语义理解"的场景,比如判断回答是否切题、语气是否合适。两者分工明确,成本和可靠性都会好很多。

3. tracelint:把 trace 分析变成可复用的规则

3.1 tracelint 解决的是什么问题

tracelint 这个工具的思路我特别喜欢,它把 trace 分析做成了类似 linter 的规则引擎。你定义一组规则,它扫描你的 trace,命中规则就报出来。这跟 ESLint 检查代码、SQL lint 检查查询是一个套路——把领域经验固化成可执行的检查项。

为什么这个思路对 agent trace 特别合适?因为 agent 的很多问题是有固定模式的:工具调用失败没重试、重试次数过多、某个 span 超时、输出为空、token 超预算、循环调用同一个工具……这些都是可以用规则描述的。你不需要一个模型来"感觉"这次运行有问题,规则直接告诉你。

3.2 我常用的几类 tracelint 规则

下面这几类规则是我在实际项目里反复用到的,基本覆盖了 agent 最常见的翻车场景。

第一类:错误与重试相关。比如"任何 TOOL span 的 status_code 为 error 且没有后续成功重试"、"同一个 tool.name 在单条 trace 内被调用超过 N 次"。这类规则能直接抓出工具调用层面的问题。

第二类:性能相关。比如"单个 span duration 超过阈值"、"整条 trace 总时长超过阈值"、"LLM span 的 token 数超过预算"。性能问题在 trace 里是最容易量化的。

第三类:结构相关。比如"trace 深度超过 N 层"(可能陷入递归)、"存在孤立的 span"(编排逻辑有问题)、"CHAIN span 下没有任何子 span"(空执行)。

第四类:内容相关。比如"LLM 输出为空字符串"、"工具返回结果为空"、"输入参数包含明显的占位符如 TODO"。

这些规则写起来都不复杂,但组合起来威力很大。我一般会先跑一遍全量 trace,统计每条规则的命中率,然后针对高频命中的规则去优化 agent 逻辑。

3.3 写一条规则的实际过程

假设我要检测"工具调用失败后没有重试"这个模式。逻辑是这样的:遍历 trace 里所有 TOOL span,如果某个 span 状态是 error,检查它之后有没有对同一个 tool.name 的成功调用。如果没有,就报出来。

用伪代码表达大概是这样:

def check_tool_retry(trace): issues = [] tool_spans = [s for s in trace.spans if s.span_kind == "TOOL"] for i, span in enumerate(tool_spans): if span.status_code == "ERROR": # 看后续有没有同名工具的成功调用 later = tool_spans[i+1:] retried = any( s.tool_name == span.tool_name and s.status_code == "OK" for s in later ) if not retried: issues.append({ "rule": "tool_fail_no_retry", "span_id": span.span_id, "tool": span.tool_name, "message": f"工具 {span.tool_name} 失败后未重试" }) return issues

这段逻辑没有任何模型参与,纯确定性。跑一万条 trace 也就几秒钟,成本几乎为零。而如果交给 LLM Judge,一万条 trace 就是一万次模型调用,费用和延迟完全不是一个量级。

注意:规则不要写得太死。比如"重试超过 3 次就报警"这种阈值,最好先跑一段时间数据看看分布,再定阈值。我一开始拍脑袋定了个 3,结果发现正常业务里有些工具就是会重试 4-5 次,误报一堆。后来改成看 P95 分位数,才合理。

4. OpenInference:让 trace 数据可移植、可分析

4.1 为什么需要统一标准

在 OpenInference 这类标准出现之前,每个可观测性工具都有自己的 trace 格式。你用 A 工具采集的数据,想换到 B 工具分析,得写一堆转换代码。更麻烦的是,不同工具对"工具调用"这个概念的字段命名都不一样,你的分析脚本根本没法复用。

OpenInference 的价值就在于它定义了一套语义约定(semantic conventions):LLM 调用该有哪些属性、工具调用该有哪些属性、检索该有哪些属性。只要大家都遵守这套约定,你的分析代码就能跨工具、跨框架复用。

我实际用下来的感受是:一旦你的 trace 遵循 OpenInference,你写的 tracelint 规则、统计脚本、dashboard 查询,基本可以做到"一次编写,到处运行"。这在多团队协作、多框架混用的场景下,价值巨大。

4.2 关键属性怎么用

OpenInference 定义了一堆属性,但真正高频用到的其实就那些。我挑几个最关键的说说它们怎么服务于"无 LLM 分析"。

openinference.span.kind这个属性标明了 span 类型,取值有 LLM、TOOL、RETRIEVER、CHAIN、AGENT 等。有了它,你就能按类型分组统计。比如"这次运行里 TOOL span 占比多少"、"RETRIEVER span 平均耗时多少"。

llm.token_count.prompt和llm.token_count.completion记录了 token 消耗。这两个值直接决定了成本,也是判断"这次调用是不是异常膨胀"的依据。我设过一个规则:单次 LLM 调用的 prompt token 超过历史 P99 的 2 倍就报警,抓到过好几次 prompt 拼接出 bug 导致上下文爆炸的情况。

tool.name和input.value记录了工具调用的细节。把 tool.name 按 trace 聚合,你就能看出 agent 的工具使用偏好;把 input.value 拿出来做模式匹配,就能发现参数异常。

这些属性的共同点是:它们是结构化的、有明确语义的、可以直接参与计算的。你不需要"理解"它们,只需要"读取"它们。

4.3 从属性到指标:一个实际例子

我拿一个真实场景举例。我们有个 agent 负责处理用户查询,会调用搜索工具。有段时间用户反馈"回答很慢",但我们看整体平均延迟又没明显变化。

于是我从 OpenInference 属性入手,做了个分组统计:按tool.name分组,算每个工具的 P50、P95、P99 延迟。结果发现搜索工具的 P99 延迟是 P50 的 20 倍——也就是说,大部分请求很快,但有一小撮请求特别慢,把平均值拉平了,掩盖了问题。

这个分析完全基于 trace 属性,没用到任何 LLM。定位到问题后,我们给搜索工具加了超时和降级逻辑,P99 立刻降下来了。

分析维度用到的属性得到的结论
工具延迟分布tool.name, duration搜索工具 P99 异常
token 消耗分布llm.token_count.*某类查询上下文膨胀
错误率status_code, span_kind特定工具错误率偏高
调用频次tool.name, trace_id存在重复调用模式

这张表里的每一行,都是纯结构化分析能得到的。LLM Judge 在这些维度上不仅更贵,而且更不准——它没法精确告诉你 P99 是多少。

5. Phoenix:把 trace 分析变成可视化工作流

5.1 Phoenix 在无 LLM 分析里的定位

Phoenix 是个开源的 LLM 应用可观测性平台,我主要用它做两件事:可视化 trace 结构和跑基于规则的评估。它原生支持 OpenInference,所以前面讲的属性在 Phoenix 里都能直接看到、直接查。

很多人用 Phoenix 只用来"看 trace",其实它更大的价值在于把 trace 数据变成可查询、可聚合、可对比的数据集。你可以按时间范围筛选、按属性过滤、按指标排序。这些操作都是确定性的,不涉及模型推理。

5.2 用 Phoenix 做 trace 对比

我特别喜欢 Phoenix 的一个功能是对比两条 trace。比如 agent 改了一版 prompt 之后,我想知道行为有没有变化。传统做法是跑 LLM Judge 对比两次输出,但更直接的做法是对比 trace 结构:span 数量变了吗?工具调用序列变了吗?token 消耗变了吗?延迟变了吗?

这些对比都是结构化的。如果新版 trace 的 span 数量从 8 个变成 15 个,那说明 agent 变"啰嗦"了,可能是陷入了多余的循环。如果工具调用序列从 A→B→C 变成了 A→C,那说明 B 这一步被跳过了,可能是路由逻辑变了。这些结论不需要任何模型判断,看一眼 trace 结构就清楚。

5.3 把规则评估接进 Phoenix

Phoenix 支持自定义评估器,你可以把前面 tracelint 那套规则逻辑接进去,让它在 Phoenix 里批量跑。这样你既能看到单条 trace 的细节,又能看到全量数据的规则命中统计。

我的工作流大概是这样:先在 Phoenix 里按时间筛选出一批 trace,然后跑规则评估,看哪些规则命中率高。命中率高的规则,要么说明 agent 有系统性问题需要修,要么说明规则阈值需要调。这个循环跑几轮,agent 的质量就会稳步提升。

实操心得:规则评估的结果最好存下来,做成时间序列。这样你能看到"某个规则的命中率是在上升还是下降"。如果某条规则命中率突然飙升,往往意味着上游有变更(比如某个 API 改了行为),能帮你快速定位回归。

6. 从 trace 里能直接读出的六类信号

前面讲了工具和标准,这一节我把"trace 本身能告诉你什么"系统性地梳理一遍。这六类信号,每一类都不需要 LLM Judge。

6.1 失败与异常信号

最直接的一类。trace 里的 status_code 明确记录了每一步成功还是失败。你可以统计:整条 trace 的失败 span 数量、失败发生在哪个环节、失败的类型分布。

我一般会关注几个指标:首次失败位置(第一个 error span 出现在哪)、失败传播路径(一个失败是否导致了后续连锁失败)、失败恢复情况(失败后 agent 有没有成功兜底)。这些都能从 span 的父子关系和状态码推出来。

6.2 性能与延迟信号

每个 span 都有 duration,这是最客观的性能数据。你可以算:整条 trace 的总时长、每个 span 的耗时占比、最慢的 span 是哪个、延迟的分布情况。

有个技巧是看关键路径:从根 span 到叶子 span 的最长路径,就是这条 trace 的实际耗时下限。如果关键路径上有个 span 特别慢,那它就是优化重点。这个分析纯靠遍历 span 树就能做,不需要模型。

6.3 成本与资源信号

token 消耗、模型调用次数、工具调用次数——这些都是资源消耗的直接度量。把 token 按 span 聚合,你能看出成本花在哪。我见过一个案例,agent 在某个环节反复把整个对话历史塞进 prompt,导致 token 消耗是正常情况的 5 倍。这个从 token_count 属性一眼就能看出来。

6.4 结构与编排信号

trace 的树形结构本身就携带信息。span 的深度、宽度、分支情况,反映了 agent 的编排逻辑。比如深度过深可能意味着递归失控,某个 CHAIN 下子 span 过多可能意味着并行任务没控制好。

我还会看调用序列模式:把一条 trace 里所有 span 按时间排序,得到一串操作序列。对比正常序列和异常序列,往往能发现 agent 的"坏习惯",比如总是先调 A 再调 B 但其实 B 应该先调。

6.5 内容与输出信号

span 的 input 和 output 是文本,但很多判断不需要"理解"文本就能做。比如:输出是否为空、输出长度是否异常、输出是否包含特定关键词、输入参数是否符合预期格式。这些用字符串匹配和长度统计就能搞定。

6.6 一致性与漂移信号

把同一类任务的 trace 放在一起对比,能发现一致性问题。比如同一个 query 跑两次,工具调用序列不一样,那说明 agent 行为不稳定。这种对比是纯结构化的,比 LLM Judge 的"两次回答哪个好"要客观得多。

信号类别核心数据来源典型用途
失败异常status_code, 父子关系定位故障点、评估恢复能力
性能延迟duration, 关键路径找瓶颈、优化响应时间
成本资源token_count, 调用次数控成本、发现资源浪费
结构编排span 树、调用序列发现逻辑问题、优化流程
内容输出input/output, 长度检测空输出、格式异常
一致性同类 trace 对比评估稳定性、发现漂移

7. 什么时候还是得请 LLM Judge 出场

我不是说 LLM Judge 一无是处。恰恰相反,明确它的边界才能用好它。我的原则是:能用结构化信号回答的问题,绝不交给 LLM Judge;只有需要语义理解的问题,才请它出场。

那什么问题真的需要语义理解?我总结了三类。

第一类是质量判断。比如"这个回答是否准确回答了用户问题"、"语气是否得体"、"有没有幻觉"。这些涉及对自然语言内容的理解,规则很难覆盖。

第二类是意图识别。比如"用户这句话到底想要什么"、"agent 的这步决策是否符合用户意图"。这需要对语义的深层理解。

第三类是开放式评估。比如"这次运行整体表现打几分"、"有哪些可以改进的地方"。这种没有明确标准的评估,模型比规则更擅长。

关键是,即使请了 LLM Judge,也应该先用结构化信号做初筛。比如先用规则筛出"有失败 span"的 trace,再对这些 trace 跑 LLM Judge 判断失败原因。这样能把 LLM Judge 的调用量降下来,同时让它的判断更有针对性。

我的实际配比大概是:90% 的 trace 问题靠结构化规则发现,剩下 10% 需要语义判断的才走 LLM Judge。这个比例下,成本和可靠性都控制得很好。

8. 常见问题与排查技巧实录

8.1 规则误报太多怎么办

这是最常见的问题。我的做法是先观察再定阈值。新规则上线时先设成"只记录不告警"模式,跑一周看看命中分布。如果某条规则命中率超过 20%,那基本是阈值太松或者规则逻辑有问题,得调。

另一个技巧是给规则分级。把规则分成 error、warning、info 三级。error 级是必须处理的(比如工具失败无重试),warning 级是值得关注的(比如延迟偏高),info 级是仅供参考的(比如 token 略高)。这样告警不会淹没你。

8.2 trace 数据量太大跑不动怎么办

采样。但不是随机采样,而是分层采样:错误 trace 全留,正常 trace 按比例采。这样既保证问题不遗漏,又控制数据量。Phoenix 支持按条件筛选,你可以只拉取"包含 error span"的 trace 做分析。

8.3 怎么判断一条规则值不值得留

看它的信噪比和可操作性。如果一条规则命中后,你没法根据它采取任何行动,那它就没价值,删掉。如果一条规则命中率极低但每次命中都是真问题,那它值得留。我定期会 review 规则库,把长期没命中或者命中后没人处理的规则清理掉。

8.4 结构化分析和 LLM Judge 结论冲突怎么办

以结构化分析为准。因为结构化分析是客观事实,LLM Judge 是主观判断。如果 LLM Judge 说"这次运行没问题"但规则报出了"工具失败无重试",那显然是 LLM Judge 漏判了。这时候应该反思的是 LLM Judge 的 prompt,而不是怀疑规则。

8.5 排查速查表

现象可能原因排查方向
整体延迟高某 span 慢看关键路径、P99 分布
成本突增token 膨胀按 span 聚合 token
行为不稳定编排逻辑问题对比同类 trace 序列
频繁失败工具/依赖问题看失败 span 分布
输出质量差需要语义判断结构化初筛后上 LLM Judge

9. 我踩过的几个坑

第一个坑是过早引入 LLM Judge。项目初期就搭了一套 LLM Judge 评估流程,结果发现大部分问题其实 trace 里直接能看出来,白白花了一堆评估费用。后来把评估流程重构成"规则优先",成本降了一个数量级。

第二个坑是规则写得太具体。一开始针对某个具体 bug 写了条很窄的规则,结果那个 bug 修了之后规则就废了。后来学乖了,规则要写模式而不是个例。比如不写"检测工具 X 失败",而写"检测任何工具失败后无重试"。

第三个坑是忽略 trace 的结构信息。早期只看 span 的内容和状态,忽略了 span 之间的父子关系和时序。后来发现,很多问题恰恰藏在结构里——比如某个 span 本该是另一个 span 的子节点,结果挂错了位置,导致编排逻辑出错。

第四个坑是不做基线对比。单看一条 trace 很难判断好坏,得有基线。我现在的做法是维护一组"黄金 trace"(已知正常的运行记录),新 trace 跟黄金 trace 对比,差异大的就重点看。

10. 一个可落地的最小工作流

如果你现在想上手,我建议从最小闭环开始,别一上来就搞大而全。

第一步,确保 trace 遵循 OpenInference。不管你用什么框架,先把 trace 采集和属性标准化做起来。这是所有后续分析的基础。

第二步,用 Phoenix 把 trace 看起来。先能可视化,才能谈分析。把 trace 结构、耗时、状态都看清楚。

第三步,写三条最基础的 tracelint 规则:工具失败无重试、单 span 超时、输出为空。这三条能覆盖大部分常见问题。

第四步,跑一周数据,看命中情况。根据实际命中调整阈值和规则。

第五步,把规则接进 CI 或告警。让问题在发生时就暴露,而不是等用户反馈。

这个闭环跑通之后,你再考虑要不要引入 LLM Judge 做补充。记住,结构化信号是地基,LLM Judge 是锦上添花。地基没打好就上花活,最后一定是又贵又不准。

我自己这套流程跑下来,最大的体会是:agent 的可观测性,本质上是个数据工程问题,不是个模型问题。把 trace 当成结构化数据来对待,你会发现里面能挖的东西远比想象的多,而且挖起来又快又稳又便宜。LLM Judge 是个好工具,但它不该是你的第一选择,更不该是你的唯一选择。

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

不依赖LLM Judge:用tracelint与OpenInference实现Agent Trace质量监控

1. 为什么我开始怀疑 LLM Judge 这层“万能滤镜”做 Agent 可观测性这两年,我踩过最大的一个坑,就是过度依赖 LLM Judge 做质量评估。一开始觉得这方案简直完美:把 agent 的 trace 丢给一个更强的模型,让它打分、判断对错、归类失…

作者头像 李华
网站建设 2026/10/26 7:14:31

MongoDB存储引擎与核心架构深度解析

1. MongoDB 的存储引擎探秘MongoDB 最核心的竞争力之一就是其灵活的存储引擎架构。作为一个文档型数据库,MongoDB 采用了可插拔的存储引擎设计,这种设计理念让它能够适应不同的工作负载需求。WiredTiger 是目前 MongoDB 默认的存储引擎,它采用…

作者头像 李华
网站建设 2026/10/26 7:14:12

Go内存逃逸分析:原理、优化与实践指南

1. Go 内存逃逸分析核心概念解析内存逃逸分析是 Go 编译器在编译阶段执行的关键优化技术,它决定了变量应该分配在栈(stack)还是堆(heap)上。当变量在函数执行结束后仍然可能被访问时,就会发生"逃逸"现象,此时变量必须分配在堆上。G…

作者头像 李华
网站建设 2026/10/26 7:13:47

数据库设计优化与性能调优实战指南

1. 数据库设计基础:从底层逻辑开始数据库设计是每个开发者必须掌握的核心技能,但很多人直接从建表开始,忽略了底层逻辑的重要性。我在15年数据库优化工作中发现,90%的性能问题都源于早期设计缺陷。数据库底层逻辑包含三个关键维度…

作者头像 李华
网站建设 2026/10/26 7:12:29

e2e测试落地实战:从核心链路设计到Playwright工程化实践

1. 从一次线上故障说起:我为什么重新审视e2e测试上个月我们上线了一个订单查询功能,单测覆盖率90%以上,单元测试全绿,结果上线当天就出了事故。用户反馈查询超时,排查半天才发现是前端传参格式和后端接口定义不一致&am…

作者头像 李华
网站建设 2026/10/26 7:11:53

DeepSeek开源算力地基:国产基础设施适配与推理优化实践

昨天下午刷开源社区,看到 DeepSeek 又上了热榜。第一反应以为又是发了什么新模型,点进去才发现,这次动态的关键词不是"模型",而是"国产算力"和"基础设施"。说实话,模型开源这件事&#…

作者头像 李华