作者分析十万条真实 Agent 轨迹后发现,检测规则失效的核心不是阈值,而是不同数据源的轨迹结构不一致。文章以误报和规则沉默为例,讨论如何改进 Agent 运行诊断。
刚开始构建 agent-exec-trace 时,我以为自己知道难点会在哪里。开源项目 agent-exec-trace 是一个面向 AI Agent 的可观测性层。它使用 OpenTelemetry 风格的 trace 对 Agent 运行过程进行插桩,然后分析这些 trace,识别循环、重试风暴、成本激增、幻觉模式以及其他不良运行时行为。目标很简单:当一次 Agent 运行让人感觉不对劲时,我希望能有比日志和直觉更好的东西来解释原因。
我原以为,最难的是 detector 逻辑。
定义异常。选择阈值。接入 trace。运行分析。发布仓库。
这是故事的理想版本。
真实版本则要狼狈得多。
第一次大规模分析 Hugging Face 的 10 万条真实 Agent trace 时,我的 empty_response detector 在其中 100% 的 trace 上都触发了。每一条都不例外。与此同时,我的 35 个基于规则的 detector 中,有 28 个一次都没有触发。
正是在那一刻,这个项目不再只是“构建一些 detector”,而变成了一次真正的学习经历。
detector 并没有错。
错的是 trace 的数据形态。
而当我意识到这一点后,许多其他假设也随之崩塌了。
发布 Agent eval harness 时,我也有过非常相似的感受。工具不同,模式却一样。真实系统非常擅长摧毁那些整洁漂亮的设计故事。
我关心的问题看起来相当直观。
传统可观测性可以告诉我一个服务是否正常、是否缓慢,或者是否发生故障。
但它无法告诉我,为什么一个 Agent 连续八次调用同一个工具、三次改变主意、在一条死胡同上烧掉大量 token,或者悄无声息地偏离到一种我绝不希望在生产环境中再次出现的行为模式。
这个缺口远比人们承认的更重要。
大多数团队都能告诉你,某次运行感觉不对劲。但很少有人能够指出,它究竟是从哪一步开始出错的。
所以我构建了 agent-exec-trace:一个面向 Agent 行为、采用 OpenTelemetry 风格的可观测性层。
它的理念解释起来很简单。
对运行过程进行插桩。捕获行为路径。为规划、工具调用、检索、记忆、审批和成本生成 span。然后在这些 trace 上运行 detector,并通过一个真正可用的 UI,把异常运行呈现出来。
仓库地址:github.com/deghosal-2026/agent-exec-trace
它以三个 PyPI package 的形式发布,拥有 794 个 Python 测试和 34 个 Playwright 端到端测试。SDK 被刻意设计得足够小巧,让人们无须重新设计现有系统,就能把真实 Agent 接入进来。
from agent_exec_trace import AgentTracer, trace_agent, tool_span
AgentTracer.setup(otlp_endpoint="http://localhost:4317")
@trace_agent(agent_name="my-agent", agent_version="1.0.0")
async def handle(query: str) -> str:
with tool_span("search", tool_args={"q": query}):
return await search(query)
这部分并不是我得到的教训。
真正的教训,来自我拥有了一个足够真实、可以接受测试的系统之后所发生的事情。
我的确安排了现场测试。
只是不够早。
这个区别很重要,因为我的错误并不是忘记了现场测试,而是把它安排得太晚,以至于系统的其余部分已经开始给人一种“大功告成”的感觉。
WBS 很扎实。PRD 很完善。detector 目录有完整文档。里程碑真实存在。质量门槛也很严格:ruff 检查无误、mypy strict 检查无误、测试全部通过、覆盖率超过 90%。
这些都是良好的工程纪律。
但它们都无法保护我免受这样一个事实的冲击:模拟 Agent 很规矩,真实 trace 可不会。
这就是“太晚了”在实践中的样子。等到现场测试真正开始时,detector 目录、关于 trace 数据形态的假设,甚至某些里程碑门槛,都已经给人一种得到验证的感觉。现场测试来得不够早,没能参与塑造设计;它来得足够晚,只能揭露出这个设计此前是在错误的现实基础上得到验证的。
Hugging Face 语料库给了我第一记耳光。
我的 detector 预期响应内容会出现在某个固定位置,但语料库却以许多不同的数据形态存储它。因此,empty_response 并不是在告诉我“所有这些 Agent 都坏了”,而是在告诉我:“你对 trace 数据形态的假设过于狭窄。”
仅这一个 bug,就迫使我做了四轮规范化处理:不同的响应字段、不同的工具命名约定、不同的 operation 名称、时间戳解析问题,以及父子关系不一致的问题。
糟糕的验证数据不只会制造噪声,还会让你信心十足地学到错误的经验。
等到清理工作结束后,结论变得更诚实了,却也更令人不安。
最初的兼容率看起来还不错。
真实兼容率只有 42.4%。
正是在那一刻,我不再把可观测性看作一个 detector 问题,而是开始把它视为一个数据形态问题。
因为一个理论上完全正确的 detector,如果它所依赖的数据在真实环境中几乎从不存在,那它仍然毫无用处。
当真实 trace 语料库暴露出自身的局限后,我转向了另一个方向。
我构建了一个合成 trace 生成器。
100 万条 trace。10 个虚构 Agent。14 个工具。以及刻意设计的行为模式:循环、重试、超时、长时间无活动、等待人工介入、token 爆炸式增长、记忆突增。
它立刻解决了一个问题。
结构兼容率跃升至 99.2%。
现在,detector 终于有了它们真正能够观察到的数据。
35 个基于规则的 detector 中,有 20 个成功触发。
这听起来像是一次胜利。它确实也是。
但合成数据会以另一种方式撒谎。
我的 hallucination detector 在 98% 的合成 trace 上都触发了。不是因为我一不小心构建出了史上最强的 detector,而是因为合成输出与合成工具证据之间存在一种虚假的关系,让 detector 的任务变得过于简单。
成本方面也发生了同样的事情。我的 cost_spike detector 设置了一个真实阈值,但合成生成器产生的成本大多只有几美分。因此,这个 detector 在合成数据中也几乎从不触发。
合成 trace 可以证明 detector 能运行,却无法证明它真的有用。
这让我得到了第二个我迫切需要的教训:
合成 trace 非常适合用来证明 detector 能够运行。
但它们不足以证明 detector 已经针对现实完成校准。
所以,我现在会把验证分成多个层次来看待。
单元测试告诉我逻辑是否有效。
合成 trace 告诉我 detector 是否能够看到它需要的字段。
真实 trace 则告诉我,离开自己的沙盒环境后,这一切是否仍然有意义。
把这个顺序写下来时,它显得理所当然。但当时身处构建过程之中,它一点也不显得明显。
这件事比任何 detector bug 都更让我恼火。
我曾把 OTLP export 里程碑标记为已完成。
文档看起来没有问题。
演示看起来也没有问题。
但端到端 export 路径其实从未得到真正验证。
两个 bug 恰好相互抵消了。
Docker Compose 没有暴露 OTel collector 的 gRPC 端口。
而我使用的 SDK 路径配置的是本地 tracing,不是 OTLP export。
于是,我把一个实际上从未真正运行成功的功能标记成了已完成的里程碑。
这比测试失败带来的教训更糟糕。
失败的测试是诚实的。
两个相互抵消的 bug 所制造出来的绿色门槛,却会以一种看起来纪律严明的方式欺骗你。
这改变了我看待里程碑的方式。
现在,我对绿色勾选标记的信任已经少得多了。
如果一个门槛无法证明真实 Agent 能够发出数据、Jaeger 能够接收数据、analytics 能够摄取数据,并且 API 能够提供结果,那么这个门槛就是不完整的。在此之前通过了多少项子检查,我都不在乎。
这不只是关于可观测性的教训。
这就是软件工程。
一开始,我在隐私方面相当保守。
只采集 metadata 看起来是一种负责任的选择。
不记录原始工具参数。不记录完整工具响应。默认不记录记忆内容。
但这也让我认识到一个更加令人不适的权衡。
如果一个可观测性 SDK 只捕获结构,它恰恰可能在你最需要作出判断的地方变成瞎子。
我的 hallucination detector 就是最清楚的例子。
如果 detector 看不到工具返回了什么,它就无法有意义地判断 Agent 的陈述是否与证据相符。
当我允许采集截断后的内容,而不再只采集 metadata 后,hallucination 的误报率大幅下降。
这并没有让隐私问题消失。
它只是让这项权衡变得更加明确。
“默认更安全”和“默认更有用”并不总是同一个选择。
缺少足够证据的结构,只不过是一种更加干净整洁的失明方式。
我认为,很少有工具愿意把这一点公开说出来。
这些 package 已经发布。
测试套件也是真实可靠的。
但我仍然不会说这个问题已经解决了。
我认为,太多开源项目的发布文章会把一切压缩成“这是我们发布的东西”,却跳过“这些是我仍然不信任的部分”。
以下是我仍然无法完全信任的内容:
那 28 个尚未在真实语料库中以有意义的方式触发过的 detector
生产工作负载中的 LLM detector
API 中的 span tree 物化,它仍然没有达到我的预期
任何声称同一套阈值可以顺利泛化到不同工作负载的说法
任何尚未经过端到端验证的绿色门槛
这并不意味着项目不应该发布。
它意味着,关于发布这件事,更诚实的说法是:这个工具现在已经有用了,而且相比刚开始时,我对它的边界有了深刻得多的理解。
这是一个不错的结果。
但它与我脑海中那个干净利落的版本,也是完全不同的结果。
我会把现场测试大幅提前。
不是因为我当初没有想到它,而是因为“把它纳入计划”和“尽早安排它”并不是一回事。
迟来的现场测试会变成审计。早期的现场测试则会塑造设计。
等到我的现场测试真正开始时,它足以暴露错误的假设,却已经来不及阻止我在这些假设之上建立信心。
我会把 trace 语料库视为一种设计工件,而不只是测试输入。
我会更早要求建立一条端到端 smoke path。
而且,我不会再假装 detector 逻辑是问题的中心。
显然,它确实很重要。
但构建完这个项目后,我认为更困难的问题是:
你的 trace 究竟能看到什么?
真实环境中的数据究竟有多奇怪?
你的 detector 在结构上依赖什么?
你又如何知道一个绿色门槛是真的?
这才是这个项目真正教给我的东西。
不是“如何编写 40 个 detector”。
更准确地说,是:在 40 个 detector 真正具有意义之前,还需要满足多少其他条件。
也许最艰难的结论是:一个从未在真实 trace 上触发过的 detector,并不能算真正完成。它只是经过了充分测试。
我真诚地希望听到一线实践者对此提出反驳和质疑。
如果你如今正在运行 Agent,那么当某次运行感觉不对劲时,你实际上会使用什么?
你是否遇到过某个绿色门槛因为错误的原因而通过?
如果你也在构建 Agent 工具,那么你那个干净利落的故事版本,最先是在哪里破裂的?
github.com/deghosal-2026/agent-exec-trace · PyPI
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。