针对 AI Agent 结构缺陷(工具报错被忽略)推出的 CI lint 工具,基于执行轨迹静态检测,无需第二模型判断。
你发布了一个 AI Agent。它调用工具、读取结果、继续调用更多工具、给出回答。大多数时候它都能正常工作。直到某天用户报告出了问题,你打开 trace 一看,发现了问题所在:charge_card 工具返回了 402,而 Agent 居然……继续往下走,告诉客户订单已发货。
这不是"捏造事实"那种幻觉问题。这是运行过程中的结构性缺陷——一个被忽略的工具错误。结构性缺陷的特点是:你不需要另一个 LLM 来发现它们。通过查看 trace 就能确定性地判断。
这就是 tracelint 的核心前提:一个针对 Agent 运行轨迹的 linter。它读取执行 trace——也就是 Agent 实际上做了什么——然后以确定性的方式标记结构性 bug,并提供确切的 trace 行作为证据,同时返回 CI 退出码。它在运行结束后对 trace 进行检查,而不是对你的代码进行检查。不需要第二个模型来评判。
为什么不直接用 LLM 做判断?
因为这类 bug 根本不适合用 judge 来处理。现有的 trace-error 基准测试表明,LLM judge 的定位精度很低——它们会说"似乎有些不对劲",但无法可靠地指出具体是哪一步出了问题。而且它们是非确定性的,每条 trace 都要花钱,无法在 CI 中有效地拦截(你会因为掷硬币来决定是否让构建失败吗?)。
与此同时,有一类 Agent bug 是可以通过结构特征来判断的:
一个工具调用的参数违反了工具的 JSON Schema。这不是主观判断——直接运行 schema 验证器就能确定。
一个工具返回了错误,但 Agent 继续往下执行,仿佛什么都没发生。
同一个工具被调用了 5 次,参数完全相同、结果也完全相同(陷入死循环)。
参数在 Agent 观察到的内容中从未出现过(疑似幻觉值)。
这些都不需要模型。它们需要的是 trace 和验证器。tracelint 就是干这个的。
60 秒快速上手
pip install tracelint
tracelint demo --html demo.html
demo 运行一套无需密钥的验证集——每种 defect 都植入了一个实例,外加干净的对照组——然后生成一份 HTML 报告。无需 API key,无需下载模型。
对真实 trace 进行 CI 拦截:
tracelint check ./trace.json --tools ./tools.json # exit 2 on a structural defect
退出码:0 干净,2 结构上可证明的缺陷,3 输入错误。启发式发现永远不会单独导致 CI 失败。
关键部分:它作用于你已经收集的 trace
这里有个分发层面的洞察。你可能已经在对 Agent 进行遥测了——通过 OpenInference(OpenTelemetry 的 AI 语义约定),接入 Arize Phoenix、Langfuse 或 OTel collector。tracelint 直接读取这些遥测数据。你不需要学习新的 trace 格式,直接用你已有的 spans 就行。
tracelint check spans.json --format openinference # Phoenix, OTLP, TRAIL
tracelint check trace.json --format langfuse
tracelint check messages.json --format openai
或者直接从运行中的 Phoenix 实例获取,用 Python:
import phoenix as px
from tracelint import lint_otel_trace
spans = px.Client().get_spans_dataframe().to_dict("records")
report = lint_otel_trace(spans)
print(report.exit_code) # 0 or 2
for f in report.active_findings:
print(f.rule, f.tier.value, f.summary)
我用的是真实的 OpenInference 导出数据来验证的,不是手工构建的 fixtures——包括真实的 Phoenix trace、OTel-SDK span 导出和 Phoenix dataframe 结构。在一条真实的 Phoenix trace 上,tracelint 确定性地定位到了一个真实的工具失败:
[hard_event] R2a tool_error_event (step 9)
'add_spans_to_dataset' returned an error (GraphQL query 'exampleMutation' ... 'an unexpected error occurred')
循环中没有模型介入。只是说:这个 TOOL span 的状态是 ERROR,就在这步,这是错误信息。
下面是经由 OpenInference adapter 跑出的一个 stuck-loop 示例:
[candidate] R4 loop (step 2,4,6)
'search' called 3 times in a row with identical arguments and no change in result state (ok)
[candidate] R5 redundant_call (step 2,6)
'search' repeats an earlier identical call with no mutating call in between
两个我会坚持的设计决策
候选,而非判定。 只有结构上可证明的东西(schema 违反、格式错误的 JSON)才是会直接导致 CI 失败的硬缺陷。启发式信号——循环、冗余调用、可疑参数——都以候选的形式展示,并附带证据,供人工审查,永远不会声称是确定事实,也永远不会单独导致你的构建失败。重试循环和死循环在结构上看起来很相似;tracelint 展示证据,让你自己判断,而不是假装它知道答案。
它会告诉你它无法检查什么。 这是我最在意的一点。如果 trace 缺少某个规则需要的字段——没有 tool schema、没有 result payload——那条规则不会静默通过。它会被抑制,并附带说明原因,打印在报告中:
suppressed (2) — not checked, not a clean pass:
R1 schema_violation: no tool schema available for any called tool
R7 unknown_tool: no tool registry supplied — cannot know which tools were declared
一份有隐藏缺陷的干净报告比没有报告更糟糕——那是虚假的信心。tracelint 拒绝给你那种东西。
它捕获结构性缺陷,而不是最终答案是否正确。它不会告诉你 Agent 给出了糟糕的建议;但它会告诉你 Agent 在过程中忽略了一个失败的工具调用。
幻觉参数、循环和冗余调用的发现除非被结构性地证明,否则都是候选级别。合法的值转换和有意图的重试也可能触发它们——这就是为什么它们被展示为候选,而不是被直接判定。
Trace 的质量取决于你收集的 instrumentation。缺失的字段意味着规则被抑制,而不是被捏造。
pip install tracelint
tracelint demo --html demo.html
它开源(MIT)、轻量依赖(jsonschema + 标准库)、支持 Python 3.10–3.12,整个测试套件都是离线且确定性的。
Repo: https://github.com/AshwinUgale/tracelint
如果你正在收集 agent traces 并想对它们进行确定性检查,我真的很想知道在你的真实导出上什么东西会出问题——上一个真实世界 shape 修复就是这样来的。欢迎提交 issue 和 traces。