靠猜排查问题
Agent 回答错了,你不知道它走了什么路径、看了什么资料、做了什么判断。只能重新跑一遍,指望能复现。
Agent 跑错了,你知道错在哪吗?不知道的话,下次还会再错。观测、Trace 和日志的作用,就是让每一次执行都有迹可循,出了问题能复盘,能审计,能找到根因,然后避免再犯。
你不知道它为什么这么回答,不知道它调了哪些工具,不知道它在哪里卡住了。出了问题,只能靠猜。
Agent 回答错了,你不知道它走了什么路径、看了什么资料、做了什么判断。只能重新跑一遍,指望能复现。
Agent 回答错了,你能直接看 Trace,看到它第 3 步调用了错误的工具,第 5 步的 LLM 输入缺少关键上下文。
观测是让 Agent 系统可调试、可审计、可改进的基础。通过 Trace 记录执行轨迹,通过 Log 记录每一步的输入输出,出了问题能复盘根因,能把失败案例沉淀成规则,形成持续改进的闭环。
这三个词经常混着用,但其实各有各的用处。搞清楚它们的关系,排查问题时才不会乱。
从用户发起请求开始,到最终返回结果结束,中间经过的所有步骤都串在一起。每个 Trace 有个唯一 ID,方便追踪。
比如"调用 LLM"是一个 Span,"查询数据库"是另一个 Span。每个 Span 记录开始时间、结束时间、输入输出和状态。
Span 是结构化的,Log 更灵活。可以记录调试信息、错误堆栈、业务状态等。Log 要带上 Trace ID 和 Span ID,才能关联起来。
{
"traceId": "abc-123-def",
"spans": [
{
"spanId": "span-1",
"name": "receive_request",
"startTime": "2026-06-12T10:00:00Z",
"endTime": "2026-06-12T10:00:01Z",
"status": "ok"
},
{
"spanId": "span-2",
"parentId": "span-1",
"name": "call_llm",
"startTime": "2026-06-12T10:00:01Z",
"endTime": "2026-06-12T10:00:05Z",
"status": "ok",
"input": {"prompt": "..."},
"output": {"text": "..."}
},
{
"spanId": "span-3",
"parentId": "span-1",
"name": "query_database",
"startTime": "2026-06-12T10:00:05Z",
"endTime": "2026-06-12T10:00:06Z",
"status": "error",
"error": "connection timeout"
}
]
}
一次请求可能并行调用多个工具,每个工具又可能调用子工具。Trace 的层级结构能反映出这种父子关系。
不同类型的日志,记录的东西不一样,给谁看的也不一样。混在一起写,排查问题时很难过滤。
| 日志类型 | 记录什么 | 谁看 | 例子 |
|---|---|---|---|
| 业务日志 | 业务流程的关键节点 | 产品经理、运营 | 用户发起了退款请求,订单号 xxx |
| 调试日志 | 开发调试信息 | 开发人员 | LLM 输入 prompt 长度: 1234 tokens |
| 错误日志 | 异常和错误堆栈 | 开发人员、运维 | 数据库连接超时,重试 3 次失败 |
| 审计日志 | 敏感操作记录 | 安全团队、合规 | 用户 xxx 删除了订单 yyy |
| 性能日志 | 耗时和性能指标 | 运维、开发 | LLM 调用耗时 3.2s,tokens 消耗 500 |
{
"timestamp": "2026-06-12T10:00:05.123Z",
"level": "INFO",
"traceId": "abc-123-def",
"spanId": "span-2",
"userId": "user-456",
"requestId": "req-789",
"message": "call_llm",
"metadata": {
"model": "gpt-4",
"promptTokens": 1234,
"completionTokens": 567,
"latencyMs": 3200
}
}
等你把系统写完了再想怎么加观测,通常会发现到处都要改。不如一开始就把 Trace 和 Log 的设计考虑进去。
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
@tracer.start_as_current_span("call_llm")
def call_llm(prompt: str) -> str:
span = trace.get_current_span()
span.set_attribute("prompt.length", len(prompt))
# 调用 LLM
result = llm_client.complete(prompt)
span.set_attribute("result.length", len(result))
span.set_attribute("model", "gpt-4")
return result
有观测不代表能自动找到问题。你得知道怎么查,从哪开始查,查到什么程度算够了。
# 错误复盘报告
## 问题现象
用户在查询订单状态时,Agent 返回了错误的订单信息。
## Trace 分析
- Trace ID: abc-123-def
- 问题 Span: query_order (span-3)
- 错误类型: 查询参数错误
## 根因
Agent 在提取订单号时,把用户输入的"订单 12345"解析成了"1234",漏掉了最后一位。
原因是 prompt 里对订单号格式的描述不够明确。
## 修复措施
1. 更新 prompt,明确订单号是 5 位数字
2. 增加参数校验,不符合格式的要追问用户
3. 把这类错误加入测试集,防止复发
## 跟进
- 负责人: @张三
- 完成时间: 2026-06-15
审计是给合规、安全、法务看的。他们不关心技术细节,只关心谁在什么时候做了什么,有没有越权,有没有违规。
记录用户的每一个操作,特别是敏感操作,比如删除、修改权限、导出数据。
记录 Agent 调用了哪些工具,访问了哪些数据,有没有超出权限范围。
记录 Agent 的输出有没有违反合规要求,比如泄露敏感信息、承诺不该承诺的事。
| 审计项 | 记录什么 | 保留多久 | 谁来看 |
|---|---|---|---|
| 用户操作 | 用户发起的请求、上传的文件、修改的配置 | 至少 1 年 | 安全团队、客服 |
| Agent 决策 | Agent 选择了什么工具、传了什么参数、给了什么回答 | 至少 1 年 | 安全团队、产品 |
| 工具调用 | 调用了哪些外部 API、访问了哪些数据、修改了什么 | 至少 3 年 | 安全团队、合规 |
| 敏感数据 | Agent 有没有接触到敏感数据、有没有泄露 | 至少 5 年 | 合规、法务 |
找到根因、修复问题还不够。要把失败案例沉淀下来,变成规则和测试,让系统越跑越稳,避免同样的问题再出现。
{
"issueId": "INC-2026-0612",
"discoveredAt": "2026-06-12T10:00:00Z",
"discoveredBy": "用户反馈",
"traceId": "abc-123-def",
"rootCause": "prompt 对订单号格式描述不明确",
"impact": "返回错误的订单信息",
"actions": [
{
"type": "prompt_update",
"description": "明确订单号是 5 位数字",
"completedAt": "2026-06-12T15:00:00Z"
},
{
"type": "test_case_added",
"description": "加入订单号解析错误的测试用例",
"completedAt": "2026-06-12T16:00:00Z"
},
{
"type": "validation_added",
"description": "增加订单号格式校验",
"completedAt": "2026-06-13T10:00:00Z"
}
],
"status": "resolved",
"verifiedAt": "2026-06-14T09:00:00Z"
}
每 1000 次请求里有多少次出错。改进后应该持续下降。
同样的问题再出现的比例。沉淀案例后应该趋近于 0。
从发现问题到修复完成的平均耗时。流程成熟后应该越来越短。
测试集覆盖的场景比例。每次失败后都应该提升。
不用纠结用哪个工具,重要的是理解核心概念。换工具的时候,思路能迁移。
CNCF 的开源项目,支持多种语言和框架。可以对接 Jaeger、Zipkin、Prometheus 等后端。
专为 LLM 应用设计的观测平台,能自动记录 LangChain 的调用链,支持调试和评估。
功能全面的可观测性平台,支持 Trace、Log、Metrics 的统一视图,适合企业级应用。
Uber 开源的分布式追踪系统,适合微服务架构,能可视化调用链。
Elasticsearch + Logstash + Kibana,经典的日志分析和可视化方案。
数据可视化和告警平台,支持多种数据源,能展示 Trace、Log 和 Metrics。
| 选型考虑 | 问什么 | 为什么重要 |
|---|---|---|
| 语言支持 | 支持你的技术栈吗? | 不支持的话,接入成本很高。 |
| LLM 集成 | 能自动记录 LLM 调用吗? | Agent 系统的核心是 LLM,这部分必须能观测到。 |
| 查询性能 | 查询速度快吗?支持复杂查询吗? | 排查问题时,查询慢会严重影响效率。 |
| 存储成本 | 存储费用怎么算?数据保留多久? | Trace 和 Log 的数据量很大,成本要控制好。 |
| 合规性 | 符合你们的合规要求吗? | 有些行业对数据存储和访问有严格要求。 |
用一个真实的案例,把前面讲的内容串起来。看看观测、Trace 和日志是怎么在实际工作中发挥作用的。
# 订单状态查询错误复盘
## 问题
用户询问订单状态,Agent 返回"已发货",实际未发货。
## Trace 分析
Trace ID: abc-123-def
问题 Span: query_order_status
错误原因: 读取了过期的缓存数据
## 根因
1. 订单状态更新后,缓存未在 5 分钟内刷新
2. Agent 没有检查数据时效性
3. 缺少"数据可能延迟"的提示
## 修复
1. 修复缓存刷新机制,状态变更后 1 分钟内刷新
2. Agent 回答前检查 last_updated 字段
3. 超过 5 分钟的数据,回答时标注"数据可能延迟"
## 改进
1. 测试集新增:缓存过期场景的测试用例
2. 监控新增:缓存延迟超过 1 分钟告警
3. 审计规则:Agent 必须标注数据来源和时效
## 跟进
- 负责人: @李四
- 完成时间: 2026-06-15
- 验证时间: 2026-06-16
- 结果: 回归测试通过,监控运行正常
不是背概念,而是要体现出你真正做过、踩过坑、有方法论。下面这些问题,都是面试官常问的。
传统系统出错,看代码基本能猜到原因。Agent 系统不一样,它的行为是非确定性的,同样的输入可能走出完全不同的路径。没有 Trace 和 Log,出了问题根本不知道它走了什么路径、看了什么资料、做了什么判断。观测是调试、审计和改进的基础。
Trace 是结构化的,记录的是一次请求的完整执行流程,有层级关系,能看到每一步的耗时和状态。Log 更灵活,记录的是具体的调试信息、业务事件、错误堆栈。Trace 像地图,能让你快速定位问题在哪个环节;Log 像放大镜,能让你看清楚那个环节到底发生了什么。
我们有一套标准流程:拿到用户反馈和 Trace ID 后,先看 Trace 全貌,定位到出问题的 Span;然后进入那个 Span,看它的输入输出和日志;分析根因,是数据问题、逻辑问题还是外部依赖问题;最后写复盘报告,把失败案例加入测试集,更新规则和护栏,防止复发。
审计日志是给合规、安全、法务看的,记录的是敏感操作和关键决策,比如用户删了什么数据、Agent 调了什么工具、访问了什么敏感信息。审计日志不能被篡改,要长期保留,通常至少 1 年,有些要 3 年甚至 5 年。普通日志主要是给开发调试用的,保留时间短一些。
几个措施:采样,不是每个请求都记录,可以按比例采样或者只对关键路径全量记录;异步写入,Trace 和 Log 先写到本地缓冲区,后台异步发送到存储系统;批量发送,不要每条日志都发一次网络请求,攒一批再发;过滤,低级别的日志可以过滤掉,只记录有价值的信息。
每次发现问题,都会写复盘报告,把根因和修复措施记录下来。然后把失败案例加入测试集,更新护栏规则和审计规则。每次改动都跑回归测试,确保没有引入新问题。我们还有几个关键指标:错误率、复发率、平均修复时间、测试覆盖率。这些指标持续跟踪,能看到改进效果。
学完这节,你应该能把"Agent 出错了"变成"Agent 出错了,我通过 Trace 定位到第 3 步的 LLM 调用缺少上下文,已经修复并加入测试集"。
我把 Agent 的可观测性设计成四层:
第一层是追踪层,用 Trace 记录每次请求的完整执行流程,从入口到出口,每个步骤都是一个 Span,记录输入输出和状态。
第二层是日志层,在 Span 里记录详细的业务日志、调试日志、错误日志和审计日志。所有日志都带上 Trace ID 和 Span ID,方便关联查询。
第三层是复盘层,出了问题,通过 Trace 快速定位到出问题的 Span,看它的输入输出,分析根因。写复盘报告,把失败案例加入测试集,更新规则和护栏。
第四层是审计层,记录所有敏感操作和关键决策,不可篡改,长期保留,满足合规要求。
这样做的目的是让 Agent 系统可调试、可审计、可改进。出了问题能复盘根因,能沉淀经验,能持续优化,而不是每次都靠猜。