Agent 日报

Agent 可观测性:日志、链路追踪与调试

Cover image for Agent 可观测性:日志、链路追踪与调试

用结构化日志、分布式链路追踪和 span 级成本追踪在生产环境调试 AI Agent。该抓什么、该忽略什么,以及那些藏住真实故障的坑。

TL;DR — Agent 可观测性不是”把 prompt 和响应记下来”那么简单。你需要给每次模型调用和工具调用打 span、记录每一步完整的消息历史、每个 span 的 token 和成本,再用一个稳定的 trace ID 把它们串起来。没这些,一次失败的 Agent 运行就是个黑盒。有了,你几秒就能定位坏掉的那一步。OpenTelemetry 加一个追踪工具(Langfuse、LangSmith、Arize Phoenix)基本就够了。

黑盒问题

用户报告你的 Agent 给了个错答案。你打开日志,找到的是……最终响应。八步里哪一步歪了、第 4 步模型看到了什么、它为什么调错了工具——一概没有。你在盲调。

Agent 可观测性就是来干掉这个黑盒的。传统应用可观测性假设代码是确定性的:同样输入、同样路径、同样输出。Agent 打破了这个假设。同样的输入,每次运行可能走不同步数、调不同工具、花不同的钱。看不见的东西没法调,而 Agent 的决策过程,除非你刻意埋点,否则是不可见的。

我盯 Agent trace 熬过的夜,足够让我对”该抓什么”有很强的看法。下面是真正能让你在生产环境调试 Agent 的那套配置,以及那些大家埋了点、结果发现是噪声的东西。

一次 Agent 运行到底包含什么

埋点之前,先把心智模型摆正。一次 Agent 运行是一棵树,不是一条线:

flowchart TD
    T[Trace:一个用户请求] --> S1[Span:模型调用 #1]
    S1 --> S2[Span:工具调用 - 搜索]
    S2 --> S3[Span:模型调用 #2]
    S3 --> S4[Span:工具调用 - 抓取]
    S4 --> S5[Span:模型调用 #3 - 最终]

每个方块是一个 span。整棵树是一条 trace,靠 trace ID 串起来。这和 OpenTelemetry 给微服务用的模型是一样的,这不是巧合:Agent 就是个分布式系统,只不过”服务”是模型调用和工具。这么一看,工具选型就明了了。

你必须抓的三层

1. 结构化日志(发生了什么)

忘掉 print(response)。每个事件都应该是一条之后能查询的结构化记录。

import json, time, uuid

def log_event(trace_id, span_id, event_type, payload):
    record = {
        "ts": time.time(),
        "trace_id": trace_id,
        "span_id": span_id,
        "type": event_type,        # model_call | tool_call | error
        "payload": payload,
    }
    print(json.dumps(record))      # 发到你的日志管线

trace_id = str(uuid.uuid4())
log_event(trace_id, "span-1", "model_call", {
    "model": "claude-sonnet-4",
    "messages": messages,          # 模型在这一步看到的【完整】历史
    "tools_offered": [t.name for t in tools],
    "tokens_in": 1820, "tokens_out": 240,
    "latency_ms": 1430,
})

绝不能省的字段是模型在那一步看到的完整消息历史。Agent 出问题时,90% 的原因在上下文里:注入了一条过时记忆、某个工具返回了垃圾污染了下一轮、或者系统 prompt 被截断了。如果你只记用户问题和最终答案,你永远找不到它。

2. 分布式链路追踪(什么时候、花多久)

日志告诉你发生了什么;trace 告诉你形状和时序。一条 trace 让你看到第 3 步花了 8 秒(慢工具),或者 Agent 在第 4、5 步之间来回循环了三次。用 OpenTelemetry span,数据就能在工具之间通用。

from opentelemetry import trace

tracer = trace.get_tracer("agent")

def run_agent(query):
    with tracer.start_as_current_span("agent_run") as root:
        root.set_attribute("user.query", query)
        for step in range(MAX_STEPS):
            with tracer.start_as_current_span(f"step_{step}") as span:
                resp = call_model(history)
                span.set_attribute("tokens.in", resp.usage.input)
                span.set_attribute("tokens.out", resp.usage.output)
                if not resp.tool_calls:
                    span.set_attribute("terminal", True)
                    return resp.content

3. 成本与 token 追踪(花了多少)

给每个 span 挂上 token 数,你的 trace 就顺带成了成本账本。可观测性在这里直接回本:你能逮到那个本该 3 步、却悄悄走了 15 步的 Agent,或者那个返回 40KB JSON、害得模型之后每一轮都要重读一遍的工具。这俩在聚合仪表盘里看不见,在单条 trace 里一目了然。归因不清的 token 账单是没法优化的,而控制成本的设计模式首先依赖你能看见钱花在哪。

选你的工具

这套不用从零造。2026 年的生态已经很扎实了。

工具强项适合
Langfuse开源、可自托管、OTel 原生想掌握数据所有权的团队
LangSmith深度集成 LangChain/LangGraph重度用 LangChain 的栈
Arize Phoenix评测 + 追踪组合强也做离线评测的团队
OpenTelemetry(原生)厂商中立标准接入已有基建(Grafana、Datadog)

我的默认做法:用 OpenTelemetry 埋点,导出到上面任意一个。这样你不会被锁死。哪天追踪厂商不够用了,你换的是 exporter,不是整套埋点。

大家容易过度埋的点

三样团队常抓、但基本只制造噪声的东西:

  • Embedding 向量。 每次检索都记那个 1536 维原始向量,是你永远不会读的几个 GB 数据。记检索到的文本和分数,别记向量。
  • 流式输出的每个 token。 你要的是最终拼好的输出和 token 数,不是 200 个单独的 delta 事件。在 span 边界聚合。
  • 冗长的框架内部回调。 LangChain 之流会喷出一大堆内部回调。全抓下来会把真正重要的三个事件埋掉。过滤到模型调用、工具调用、错误。

可观测性的功夫不在抓得更多,而在以对的粒度抓对的东西。一条 30 秒能读完的 trace,胜过一条啥都有、要花 30 分钟解析的。

真正管用的调试流程

一次运行失败时,下面这个顺序定位 bug 最快:

  1. 打开 trace,找最后一个正常的步骤。 Agent 的状态从哪儿开始不对劲了?
  2. 读下一步的完整输入。 通常就崩在这:上下文坏了、工具输出被污染了、记忆丢了。
  3. 看工具结果,不只是工具调用。 Agent 用对的参数调了对的工具,但工具返回了一个错误字符串,模型却把它当数据用了。极其常见。
  4. 找循环。 同一个工具、同样的参数、出现多次 = Agent 卡住了还没察觉。你的设计模式该给它封顶,但可观测性才是你发现”顶设太高”的方式。
  5. 比较各步的 token 数。 突然飙升意味着上下文膨胀,常常是某个工具倒了太多数据。

关于生产安全的提醒

Agent 日志是个隐私面。完整消息历史里经常有用户 PII、内部数据,以及泄进上下文的密钥。上结构化日志之前,先定好哪些要脱敏、设好留存期限,并确认追踪厂商的数据驻留地符合你的合规要求。OWASP LLM Top 10 把敏感信息泄露列为首要风险:把密钥明文悄悄存下来的可观测性,本身就是一桩等着发生的事故,尤其当它和需要安全沙箱的代码执行 Agent 配在一起时。

FAQ

我需要专门的工具,还是用现有 APM 就行? 你可以把 OpenTelemetry span 路由到 Datadog 或 Grafana,看延迟和错误足够了。但 Agent 专用工具(Langfuse、LangSmith、Phoenix)会以通用 APM 做不到的方式渲染消息历史和工具 I/O,而那恰恰是你调质量问题要的数据。

最重要的一件事记什么? 模型在每一步看到的完整消息数组。只能记一样,就记它。几乎每个 Agent bug 都是上下文 bug。

可观测性的开销有多大? 如果异步导出,追踪带来的延迟可以忽略(每个 span 微秒级)。真正的成本是存储,所以才要过滤到模型调用、工具调用和错误,而不是什么都记。

多 Agent 系统怎么追踪? 在各 Agent 之间传递同一个 trace ID,让每个 Agent 的工作成为子 span。trace 树就会展示完整协作,包括是哪个子 Agent 导致了失败。

这能用来评估质量,不只是调试吗? 能。Phoenix、Langfuse 这类工具让你把评测分数挂到 trace 上,于是你可以拿一个裁判模型跑过往的 trace,追踪质量随时间的回退,而不只是抓一次性的失败。

猜你喜欢