普通接口出了问题,通常可以顺着请求一路查下去:API、Service、Database,哪里报错,日志里大多能找到线索。

Multi-Agent 则相对麻烦。比如,用户看到的结果可能只是“报告少了一段结论”,真正的问题却发生在十几分钟前:Research Agent 搜索不完整,Analysis Agent 调 Tool 超时后重试,又读取了旧 Artifact,Writer Agent 最后正常生成了一份错误报告。

这种问题只看最终日志很难查清。还要知道一次任务经过了哪些 Agent、调用了哪些 Tool、读取了什么 State 和 Artifact、发生过几次重试。

Tracing 和 Observability 的作用,就是把这条执行链完整记录下来。

一、日志为什么不够

假设系统有三个 Agent:Research Agent 负责收集资料,Analysis Agent 负责分析,Writer Agent 负责生成报告。

某次任务结束后,用户发现报告引用的是昨天的数据。后台日志看起来却完全正常:

代码片段Text
10:01:02 Research Agent started
10:01:08 Research Agent completed
10:01:08 Analysis Agent started
10:01:14 Analysis Agent completed
10:01:14 Writer Agent started
10:01:21 Writer Agent completed

继续查才发现,Research Agent 已经生成 artifact-102 v2,Analysis Agent 当时读取的却是 artifact-102 v1。

没有 Exception,没有 HTTP 500,也没有 Agent 明确失败。每一步都执行成功,最终结果仍然错了。

这类问题在 Agent 系统里很常见:单独看每一步都正常,连起来以后结果却错了。

普通日志更适合回答“某个时间发生了什么”。Multi-Agent 排查的问题比较多:

  • 这一步属于哪个任务?
  • 由哪个 Agent 执行?
  • 是谁触发的?
  • 调用了哪个 Tool?
  • 读取了哪份 Artifact?
  • 发生过几次 Retry?

这些关系如果没有提前记录,事故发生后很难靠日志重新拼出来。

二、一次任务一条链

工程上通常把一次完整任务记录成一条 Trace。

比如用户提交:

研究最近一周数据库领域的变化,并生成一份报告。

内部可能经历:

代码片段Text
Task 42
│
├── Research Agent
│   ├── LLM
│   ├── Search
│   └── Write Artifact
│
├── Analysis Agent
│   ├── Read Artifact
│   ├── LLM
│   └── Write Artifact
│
└── Writer Agent
    ├── Read Artifact
    └── LLM

整条执行链就是 Trace。

其中每个具体操作叫 Span。

Agent 执行、LLM 调用、Tool 调用、A2A 调用、Artifact 读写,都可以分别记录成 Span。

每个 Span 会保存自己的开始时间、结束时间、状态和属性,再通过父子关系组成完整调用链。

监控平台里可能看到:

代码片段Text
Task 42                         34.8s
├─ Research Agent               9.3s
├─ Analysis Agent              18.6s
│  ├─ LLM                       5.2s
│  ├─ Tool                      5.0s
│  └─ Tool Retry                5.1s
└─ Writer Agent                 6.9s

如果现在要查“为什么 Task 42 变慢了”,范围很快就能缩小到 Analysis Agent,以及发生 Retry 的 Tool。

Trace 的作用很直接:把一堆离散事件还原成完整执行过程。

三、关键标识要贯通

真正落地时,还有一个常见问题:每个 Agent 都有 Trace,但彼此没有关联。

例如 Agent A 通过 HTTP 调用 Agent B,Agent B 再通过 Queue 触发 Agent C。如果三个 Agent 分别创建新的 trace_id,监控平台最终只能看到三条互不相关的调用记录。

所以跨 Agent 调用时,要把追踪上下文继续传下去。

最基础的三个字段是:

  • trace_id:标识整条执行链;
  • span_id:标识当前操作;
  • parent_span_id:记录当前 Span 的上一级操作。

例如整个任务使用 trace_id=T100。Research Agent 的 span_id=S10,它调用 Search 时创建 S11,并把 parent_span_id 设置为 S10。

这样系统才能恢复出:

代码片段Text
Research Agent S10
├── Search S11
└── Analysis Agent S12

Multi-Agent 还应该额外记录一些业务 ID:

  • task_id:业务任务;
  • context_id:相关上下文;
  • agent_id:执行 Agent;
  • tool_call_id:具体 Tool 调用;
  • artifact_id:读取或生成的 Artifact。

比如某个 Span 同时记录:

task_id=task-42、agent_id=analysis-agent、artifact_id=artifact-102-v1。

出现问题时,就能直接确认 Analysis Agent 当时到底读取了哪一份数据。

很多所谓的“Agent 偶尔出错”,最终找到的原因其实很具体:旧 Artifact、旧 State、Tool 重复调用、Retry 重复执行,或者上下文关联错误。

四、Span 应该记录什么

Span 记录得太少,没有排查价值;记录得太多,又会带来隐私、成本和安全问题。

实际工程中,可以优先把执行信息记录完整。

一个 Agent Span 可以包含:

代码片段JSON
{
  "task_id": "task-42",
  "agent": "analysis-agent",
  "agent_version": "1.8.2",
  "model": "deepseek-flash",
  "prompt_version": "analysis-v7",
  "duration_ms": 5240,
  "input_tokens": 8321,
  "output_tokens": 1287,
  "retry_count": 1,
  "input_artifact_id": "artifact-102-v1",
  "output_artifact_id": "artifact-103-v1",
  "status": "success"
}

Tool Span 可以再记录 tool、tool_call_id、duration_ms、retry_count、status 和 error_type。

这些结构化数据已经能解决大量问题。

任务突然变慢,可以查 duration_ms;某个版本错误增加,可以按 agent_version 或 prompt_version 对比;怀疑读错数据,直接检查 input_artifact_id。

Prompt、Tool 参数和完整返回内容不一定默认保存。

更实用的做法,是把数据分成两层。

第一层记录行为数据:调用哪个 Agent、用了什么 Tool、重试多少次、耗时多久、读写了哪些 Artifact。

第二层才是内容数据:用户输入、Prompt、模型输出、Tool 参数和 Tool 返回结果。

很多故障只看行为数据已经足够。

比如 Research Agent 一次任务调用 Search 17 次、Retry 4 次,总耗时 89 秒。即使完全不看用户内容,也能判断这次执行过程有异常。

内容数据则可以根据业务情况做脱敏、截断、采样、加密和权限控制。

五、指标负责发现异常

Trace 适合调查某一条具体任务。

比如:

Task 42 为什么失败?

如果问题变成:

最近一周系统是不是越来越慢?

就需要 Metrics。

常用指标可以包括:

  • task_success_rate
  • task_latency_p95
  • agent_error_rate
  • agent_latency
  • tool_error_rate
  • tool_timeout_rate
  • retry_count
  • input_tokens
  • output_tokens
  • cost_per_task

Multi-Agent 还可以增加 agents_per_task、handoff_failure_rate、artifact_read_failure、duplicate_execution_count 等指标。

例如平时 create_ticket 每五分钟只有两三次 Retry,某个时间段突然增加到 37 次,这时就可以确认该 Tool 出现异常,再进入相关 Trace 找具体任务。

整个排查过程通常是:

Metrics 发现异常 → Trace 找到具体调用 → Logs 查看细节。

三者各有用途。

Metrics 看趋势,Trace 看过程,Log 看具体事件。组合起来,比直接翻大量日志有效得多。

六、实战示例

先看一个性能问题。

用户反馈:

部分报告需要两分钟才能生成,平时只有二十秒。

找到对应的 task_id=task-8912,打开 Trace:

代码片段Text
Task 8912                     121s
├─ Research Agent              18s
├─ Analysis Agent              94s
└─ Writer Agent                 9s

问题已经缩小到 Analysis Agent。

继续展开,发现其中的 database_query 连续发生五次 Retry,每次都接近十秒。

再看 Tool Span:

代码片段JSON
{
  "tool": "database_query",
  "error_type": "timeout",
  "retry_count": 5
}

这时就没有必要继续研究 Prompt。

真正要查的是数据库查询、连接池、网络或者 timeout 设置。

问题从“为什么 Agent 这么慢”,缩小成了“为什么 database_query 连续 timeout 五次”。

范围明确以后,排查就简单多了。

再看一个更典型的问题:重复创建 Ticket。

线上偶尔出现两个内容完全相同的 Ticket。如果只有最终日志,很容易怀疑 Agent 调用了两次 create_ticket。

Trace 显示的实际过程却是:

代码片段Text
create_ticket
├─ POST /tickets
├─ Server 创建 Ticket 8271
└─ Client 等待返回时 timeout
        ↓
      Retry
        ↓
再次 POST /tickets
        ↓
Server 创建 Ticket 8272

第一次请求已经成功,只是结果没有正常返回给调用方。调用方收到 timeout 后触发 Retry,于是出现问题。

这类问题需要给有问题的操作增加 Idempotency Key,也就是幂等键。

例如使用:

task-42:create-ticket:user-193

第一次调用时,服务端创建 Ticket 8271,并保存这个 Key。

即使客户端超时,第二次请求仍然携带相同 Key。服务端发现这个操作已经执行过,直接返回 Ticket 8271,不再创建新的记录。

没有 Trace 时,很容易花大量时间检查 Prompt 和模型。完整调用链出来以后,它就是一个典型的“超时 + Retry + 非幂等操作”问题。

Replay 复现偶发故障

还有一类问题更难查:

昨天失败了,今天重新运行又正常。

这时需要 Replay。

为了尽量恢复当时的执行环境,Trace 最好保存模型、Prompt 版本、Agent 版本、Workflow 版本、State 版本、Artifact ID 和 Tool 版本。

例如系统从 Workflow 2.8.0 升级到 2.8.1 后,平均 Retry 次数从 0.3 上升到 3.8,排查范围就可以直接缩小到 2.8.1 的改动。

Replay 一般有两种方式。

一种直接使用历史 Tool Result,主要验证后续 Agent 逻辑。

另一种重新调用真实 Tool,更接近线上环境,但外部数据可能已经变化。

如果 Tool 会写数据库、发邮件、创建订单、创建 Ticket 或支付,Replay 应使用 Mock、Sandbox 或测试环境,避免调试时再次产生不必要的问题。

总结

Multi-Agent 难调试,很大一部分原因是执行链变长了。一次任务可能经过多个 Agent、LLM、Tool、State 和 Artifact,其中任何一步出现问题,最后都可能表现成一句“结果不对”。如果系统只记录最终结果,很难找到真正的故障位置。

Tracing 把完整调用链记录下来,Metrics 用来发现异常趋势,Logs 用来查看具体细节,Replay 用来恢复事故现场。再配合 task_id、trace_id、artifact_id、版本号和幂等设计,很多模糊的 Agent 故障,都能缩小到某个 Agent、某次 Tool Call、某份 Artifact 或某次 Retry。