AI Agent 出问题怎么排查?从 Trace、日志到 Token 监控,搭建完整的可观测性体系
作者:王仕宇(JavaPub)
最近写了不少 AI Agent 工程化相关的文章,从 Tool Calling、上下文管理,到 Memory、任务队列、成本优化和评测体系。
但如果真的准备把 Agent 做成一个长期运行的产品,我觉得还有一个特别重要的问题:
Agent 自己出问题以后,应该怎么排查?
比如用户反馈:
“为什么我只是让 Agent 修改一个简单的接口,它执行了两分钟,最后还失败了?”
如果是普通后端服务,我们可能打开 Grafana,看接口耗时、CPU、内存,再通过 Trace ID 找到具体的慢请求。
但 Agent 不一样。
它的一次任务,内部可能包含几十次模型调用、工具调用、上下文压缩、记忆检索和失败重试。
如果没有完整的可观测性,你甚至不知道这两分钟究竟花在哪里。
所以我认为,Agent 工程化做到最后,一定绕不开 Observability。
一、普通 API 的监控,为什么不够用了?
以前我们写一个 HTTP 接口:
用户请求
↓
Nginx
↓
Go API
↓
MySQL
↓
返回结果
通常关注几个指标:
请求耗时
请求成功率
错误码
CPU
内存
数据库耗时
这些当然重要。
但 Agent 的执行链路可能是:
用户提交任务
↓
Planner
↓
LLM Call #1
↓
Tool Call #1
↓
LLM Call #2
↓
Tool Call #2
↓
Memory Retrieval
↓
LLM Call #3
↓
Tool Call #3
↓
Verifier
↓
最终回答
如果任务耗时 120 秒,你只记录:
POST /api/agent/run
Duration: 120s
Status: 200
其实几乎没有排障价值。
因为 HTTP 200 只能说明接口层可能正常返回了响应。
它不能说明:
Agent 有没有真正完成任务。
更不能告诉你究竟哪一步消耗了最多时间。
二、Agent 至少需要三类可观测数据
我更建议从传统可观测性的三个基础概念入手。
Metrics、Logs、Traces。
也就是:
Metrics
指标
Logs
日志
Traces
链路追踪
但要针对 Agent 做扩展。
Metrics 关注整体运行情况,比如任务成功率、Token 消耗和平均执行时间。
Logs 记录具体发生的事件,比如某个工具参数不合法、模型返回限流、任务进入等待审批状态。
Traces 则把一次任务内部的执行步骤串起来。
我觉得这三种数据各有作用,不能简单互相替代。
三、一个 Agent Task 应该有自己的 Trace ID
假设用户提交任务:
“帮我排查 API 为什么一直返回 502。”
系统创建:
{
"task_id": "task_10001",
"trace_id": "trace_abc123",
"status": "running"
}
接下来无论调用 Nginx Agent、Docker Agent,还是 Linux Agent,都应该关联到同一个 Trace。
例如:
Trace: trace_abc123
10:00:00 Task Started
10:00:01 Planner Started
10:00:03 LLM Call #1
10:00:05 Nginx Tool Call
10:00:08 LLM Call #2
10:00:10 Docker Tool Call
10:00:13 LLM Call #3
10:00:15 Task Completed
这样你就可以从一个用户任务,追踪整个执行过程。
如果使用 OpenTelemetry,还可以采用符合规范的 Trace ID 和 Span ID,让 Agent 的数据进入现有分布式追踪体系。
这里的关键不是 ID 长什么样。
而是:
同一个任务产生的所有模型调用、工具调用和子任务,都能被关联起来。
四、每一次 LLM 调用都应该是一个 Span
在 OpenTelemetry 里,Span 可以理解为一次具有开始和结束时间的操作。
例如:
Agent Task
│
├── Planner
│ │
│ └── LLM Call
│
├── Docker Tool
│
├── Log Analysis
│ │
│ └── LLM Call
│
└── Final Response
│
└── LLM Call
每个 LLM Span 可以记录:
模型名称
输入 Token
输出 Token
缓存 Token
请求耗时
首 Token 延迟
重试次数
调用状态
例如:
{
"span": "llm.generate",
"model": "example-model",
"duration_ms": 4250,
"input_tokens": 12500,
"output_tokens": 850,
"cached_input_tokens": 8000,
"status": "success"
}
这只是一个数据结构示例。实际接入 OpenTelemetry 时,应尽量遵循所用 SDK 和 GenAI 语义约定支持的字段,并注意不同模型供应商返回的 Token 统计口径可能不同。
有了这些数据,就可以回答一个非常具体的问题:
Agent 慢,到底是模型慢,还是工具慢?
五、模型响应慢,还要区分 TTFT 和总耗时
对于流式模型请求,我觉得至少应该关注两个时间指标。
TTFT:
Time To First Token,首个输出 Token 到达的时间。
Total Latency:
整个模型请求完成的时间。
例如:
请求 A:
TTFT:0.8s
总耗时:28s
这可能说明模型很快开始输出,但后续生成时间较长。
另一个请求:
请求 B:
TTFT:15s
总耗时:18s
这种情况更值得关注首 Token 之前发生了什么。
可能涉及:
上游排队
请求网络
模型推理准备
服务商负载
但仅凭 TTFT 不能直接判断具体原因。
还需要结合模型供应商状态、网关耗时和网络指标分析。
这也是为什么只记录一个 duration_ms 不够。
六、Tool Call 也需要完整追踪
比如 Agent 调用了:
get_container_logs
我们至少应该记录:
{
"tool": "get_container_logs",
"duration_ms": 2300,
"success": true,
"output_bytes": 18420,
"truncated": true
}
如果失败:
{
"tool": "get_container_logs",
"success": false,
"error_code": "TOOL_TIMEOUT",
"duration_ms": 15000
}
这些信息很有价值。
因为真实 Agent 经常会出现:
模型很快,但工具很慢。
比如:
LLM 分析:2 秒
SSH 连接:15 秒
读取日志:3 秒
Docker Build:90 秒
如果不拆分,用户只会觉得:
“AI 怎么这么慢?”
而你可能错误地去更换大模型。
实际上瓶颈根本不在模型。
七、王仕宇的观点:Agent 慢了,不应该第一时间换模型
我是王仕宇,JavaPub。
我觉得现在很多 AI 产品出现延迟问题,第一反应就是:
是不是模型不够快?
换一个更快的模型试试。
但 Agent 的耗时不完全由模型决定。
比如一个任务:
总耗时:
120 秒
模型调用:
20 秒
工具执行:
85 秒
其他:
15 秒
这时候你把模型速度提升一倍,理论上也只能减少部分总耗时。
如果最主要的时间花在 Docker Build、浏览器加载或者数据库查询上,那么优化这些工具往往更有效。
这其实就是经典的性能分析思路:
先找到瓶颈,再决定优化哪里。
不要凭感觉优化。
八、Token 成本应该跟踪到每个 Task
前面已经写过 Agent 成本优化。
但成本监控要真正落地,就应该关联到具体任务。
比如:
Task ID:
task_10001
LLM Calls:
18
Input Tokens:
145000
Output Tokens:
12000
Cached Tokens:
85000
Tool Calls:
23
Cost:
$0.24
这里的数据只是示例。
实际成本需要根据请求时的模型价格、输入输出计费规则和缓存折扣计算。
我更建议同时统计:
单次模型调用成本
单个 Agent Task 成本
单个用户的累计成本
每类任务的平均成本
每个模型的成本
因为有时候整体账单上升,并不是用户变多了。
可能是某个 Prompt 修改以后,Agent 开始反复调用工具。
或者某个模型不再命中 Prompt Cache。
又或者任务成功率下降,导致大量重试。
所以 Token 成本监控不能只看每天花了多少钱。
还需要能够解释:
为什么今天比昨天花得多。
九、工具调用循环可以直接通过 Trace 发现
比如某个 Agent 执行:
get_container_status
↓
get_container_logs
↓
get_container_status
↓
get_container_logs
↓
get_container_status
连续查询相同的内容。
如果有完整 Trace,你可以很快发现:
同一工具
相同参数
相同返回结果
重复调用 5 次
这时候就可以建立一个指标:
Repeated Tool Call Rate
也就是重复工具调用比例。
不过不是所有重复调用都是问题。
比如运维 Agent 正在等待容器状态从 Starting 变成 Running,定期轮询可能完全合理。
所以还应该结合:
工具类型
时间间隔
返回结果是否变化
任务是否处于等待状态
判断。
关键是能够区分:
有目的的轮询,和没有进展的无限循环。
十、Agent 的失败,不能只统计 HTTP 500
这也是一个非常典型的问题。
假设 Agent API 返回:
HTTP/1.1 200 OK
内容:
{
"status": "completed",
"answer": "很抱歉,我无法完成这个任务。"
}
从 HTTP 监控看,请求成功。
但从业务角度看,任务失败。
所以我更建议明确区分:
Transport Success
请求传输成功
Execution Success
执行流程完成
Task Success
用户目标达成
这三者不一定相同。
比如:
HTTP 200
Agent Completed
Task Failed
完全可能同时出现。
所以 Agent 监控必须有真正的任务级状态。
否则 Grafana 上显示 99.9% 成功率,用户却天天抱怨 Agent 不好用。
十一、Agent 的成功率也需要有依据
例如一个代码 Agent:
任务:
修改用户登录接口。
模型最后回答:
“已经修改完成。”
这不一定算成功。
如果:
go test ./...
仍然失败,那么任务可能并未完成。
所以我更喜欢把任务完成情况和 Verifier 关联起来。
例如:
{
"task_id": "task_10001",
"execution_status": "completed",
"verification": {
"build_passed": true,
"tests_passed": false
},
"task_success": false
}
对于运维 Agent,也类似。
它说:
“已经修复 Nginx。”
但如果真实健康检查仍然失败,就不能算完成。
可观测性不只是看 Agent 执行了什么,更应该看任务最终产生了什么结果。
十二、Memory Retrieval 也需要监控
前面写过 Agent Memory 系统。
如果 Memory 发生错误,Agent 可能把旧信息当成当前事实。
所以每次检索最好记录:
检索作用域
检索耗时
候选记忆数量
最终选中数量
记忆版本
是否存在过期信息
比如:
{
"operation": "memory.search",
"scope": "project",
"duration_ms": 85,
"candidates": 24,
"selected": 5,
"filtered_expired": 3
}
这些信息可以帮助你排查:
为什么 Agent 使用了错误的项目配置?
为什么明明保存了记忆,最终却没有检索出来?
为什么 Memory Search 越来越慢?
但不要默认把所有用户记忆原文直接记录进 Trace。
生产环境更应该记录必要的元数据,并且根据访问权限控制是否允许查看具体内容。
十三、在 Go 项目里怎么接入 OpenTelemetry?
如果后端使用 Go,可以从最基本的 Span 开始。
下面是一个已经初始化好 OpenTelemetry Tracer Provider 后的示意代码:
package agent
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)
var tracer = otel.Tracer("agent-runtime")
func RunTask(
ctx context.Context,
taskID string,
) error {
ctx, span := tracer.Start(
ctx,
"agent.run",
)
defer span.End()
span.SetAttributes(
attribute.String(
"agent.task_id",
taskID,
),
)
err := executeAgent(ctx, taskID)
if err != nil {
span.RecordError(err)
span.SetStatus(
codes.Error,
err.Error(),
)
return err
}
return nil
}
这样每次执行 Agent,就可以创建一个根 Span。
后续的模型调用和工具调用继续使用同一个 ctx,便于保持 Trace 关联。
实际项目还需要初始化 Tracer Provider、Exporter、采样配置,以及必要的资源属性。
单纯写一个 tracer.Start(),不会自动把数据送进 Jaeger 或其他后端。
十四、工具调用也可以单独创建 Span
例如:
func ExecuteTool(
ctx context.Context,
name string,
) error {
ctx, span := tracer.Start(
ctx,
"agent.tool.execute",
)
defer span.End()
span.SetAttributes(
attribute.String(
"agent.tool.name",
name,
),
)
err := runTool(ctx, name)
if err != nil {
span.RecordError(err)
span.SetStatus(
codes.Error,
err.Error(),
)
return err
}
return nil
}
这样在追踪平台里,就能看到:
agent.run
├── llm.generate
├── agent.tool.execute
├── llm.generate
├── memory.search
└── llm.generate
如果某个工具耗时很长,就能直接定位。
不过要注意:
不要把 API Key、用户密码、完整 Prompt 或敏感工具参数直接写进 Span Attribute。
Trace 系统通常会集中收集数据。
一旦记录了敏感信息,可能造成额外的数据泄露风险。
十五、Agent 最好有一个独立的 Dashboard
如果让我做一个 Agent 平台,我会优先做一个运行监控面板。
至少展示这些指标:
| 指标 | 作用 |
|---|---|
| Active Tasks | 当前正在执行的任务 |
| Task Success Rate | 真实任务成功率 |
| P95 Task Duration | 任务尾延迟 |
| LLM P95 Latency | 模型响应耗时 |
| Tool Failure Rate | 工具失败比例 |
| Tool Retry Rate | 工具重试情况 |
| Tokens per Task | 单任务 Token 消耗 |
| Cost per Successful Task | 成功任务成本 |
| Queue Wait Time | 任务排队耗时 |
| Context Compaction Count | 上下文压缩次数 |
| Approval Wait Time | 等待人工审批时间 |
我会特别关注两个指标。
第一个是:
P95 Task Duration。
因为平均耗时可能掩盖一部分特别慢的任务。
第二个是:
Cost per Successful Task。
因为如果一个 Agent 花费减少了,但任务失败率大幅增加,那么这种优化不一定值得。
十六、Agent 的性能还应该按任务类型拆开
比如一个平台提供:
代码开发
日志分析
Docker 排障
SQL 优化
文档生成
如果全部混在一起统计平均耗时,很容易误判。
因为代码开发任务可能需要几分钟。
而简单 SQL 解释可能只需要几秒。
所以更合理的方式是:
Task Type
↓
独立统计
比如:
Docker Diagnosis:
P95 35s
Code Review:
P95 80s
Code Generation:
P95 240s
SQL Explanation:
P95 12s
这样才能知道具体哪类任务退化了。
同样也应该按:
Agent Version
Model
Prompt Version
Toolset Version
进行比较。
尤其更换模型以后,可以直接观察任务成功率、工具调用次数和 Token 成本有没有变化。
十七、Agent 还需要区分线上问题和模型质量问题
比如用户反馈:
“Agent 最近经常回答不准确。”
可能有几种原因。
第一种是工具数据错误。
比如某个 Docker 工具返回了过期状态。
第二种是 Memory 检索错误。
比如检索到了另一个项目的历史配置。
第三种是模型判断错误。
第四种是 Prompt 或工具描述调整以后,Agent 的行为发生了变化。
第五种是任务执行中途失败,但最终回答没有明确暴露失败。
这些问题如果只有最终聊天记录,就很难定位。
但如果有完整 Trace,可以逐步判断:
用户输入是否正确?
↓
工具调用是否正确?
↓
工具返回是否正确?
↓
Memory 是否正确?
↓
模型是否正确使用证据?
↓
最终答案是否符合验证结果?
这才是 Agent 调试真正应该具备的能力。
十八、不要把 Observability 做成另一个成本黑洞
这里还有一个实际问题。
如果每次 Agent 调用,都把:
完整 Prompt
完整模型响应
全部工具参数
全部工具结果
全部中间推理
原样记录下来,存储成本可能非常高,还会带来隐私和安全风险。
尤其有些 Agent 会读取:
代码仓库
数据库记录
用户文件
服务端日志
这些内容可能包含敏感数据。
所以更合理的是分级采集。
例如:
基础指标:
默认记录。
执行元数据:
按需记录。
详细调试数据:
仅在授权情况下开启。
敏感内容:
默认不采集,必要时脱敏。
还可以根据任务类型采用采样策略。
但失败任务和高延迟任务,通常值得保留更多诊断信息。
具体保留哪些内容,需要结合隐私政策、数据保留周期和用户权限来设计。
十九、OpenTelemetry、Prometheus、Grafana 应该怎么组合?
如果已经有传统后端监控体系,我觉得完全可以复用。
例如:
Agent Runtime
│
OpenTelemetry SDK
│
▼
OTel Collector
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
Metrics Traces Logs
│ │ │
▼ ▼ ▼
Prometheus Tempo Loki
│ │ │
└────────────┼────────────┘
│
▼
Grafana
这是一个常见的参考架构,不是唯一方案。
也可以采用 Jaeger、其他指标存储或商业可观测平台。
真正重要的不是一定使用哪一套工具。
而是统一采集和关联:
Task ID
Trace ID
Agent Version
Model
Tool
Result
Cost
这些关键维度。
这样从 Grafana 发现某类任务成本异常升高以后,就可以进一步打开相关 Trace,找到具体是哪一步出了问题。
二十、王仕宇的观点:没有可观测性,Agent 就很难真正工程化
我是王仕宇,JavaPub。
我现在越来越觉得,很多 Agent Demo 和真正的 Agent 产品之间,差距不完全在模型能力。
而是在工程体系。
一个 Demo 可能只需要:
用户输入
↓
LLM
↓
Tool
↓
输出
但真正上线以后,还要有:
任务状态
执行记录
Token 统计
工具耗时
错误分类
链路追踪
成本分析
结果验证
告警
因为一旦用户数量增加,你就不能再靠自己打开终端、翻几百行日志去猜 Agent 为什么失败。
你需要能够回答:
“最近一小时,哪个 Agent 的失败率最高?”
“哪个模型的平均任务成本突然增加?”
“为什么同一类任务的 P95 延迟从 30 秒涨到了 90 秒?”
“哪些工具经常超时?”
“某个版本更新以后,任务成功率有没有下降?”
这些问题,才是真正生产环境会遇到的问题。
最后
我是王仕宇,JavaPub。
前面我们已经聊过 Agent 的工具、记忆、上下文、任务调度和评测。
可观测性其实就是把这些能力连接起来的一层基础设施。
它不直接让模型变得更聪明。
但它能让开发者真正知道:
Agent 做了什么、为什么慢、哪里失败、消耗了多少资源,以及最终有没有完成用户的任务。
我更建议在 Agent 项目早期就加入基本的 Task ID、Trace ID、LLM 调用统计和 Tool 执行记录。
哪怕第一版只是简单的结构化日志,也比完全没有记录强。
等到任务量上来,再逐步接入 OpenTelemetry、Prometheus、Grafana 和分布式 Trace。
如果只记住一句话:一个 Agent 能自主执行任务,只能说明它具备了行动能力;而一个 Agent 的每次决策、工具调用、成本和执行结果都可以追踪,才意味着它真正开始具备生产级系统应该有的可维护性。
本文由王仕宇(JavaPub)整理与分享,持续分享后端开发、AI Agent 与工程化实践。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_40374604/article/details/169828979



