JavaPub聊AI头像

AI Agent 出问题怎么排查?从 Trace、日志到 Token 监控,搭建完整的可观测性体系

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

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--