可观测性
要点
- 1 传统应用的调试是线性的
- 1 从"打日志"到"可观测性"
- 1 Trace 和 Span 的概念
- 每个 Span 不能只记录"执行过",还要记录足够的上下文,保证你事后能判断"为什么是这个结果"
内容
1. 为什么 AI 系统的调试难度远超传统应用
1.1 传统应用的调试是线性的
传统 Web 应用的调试链路通常是确定性的。假设有个 bug:"用户点击下单按钮后页面报错了"。你的排查路径大致是这样的:
- 打开错误监控平台,找到这条报错日志
- 看堆栈信息,定位到是
orderService.create()方法里抛了异常 - 查看异常详情,发现是数据库连接超时
- 检查数据库状态,确认是连接池配置不合理
- 修改配置,部署上线,问题解决
整个过程的关键特征是:问题可复现,原因可追溯,修复可验证。 你看到同一个报错,就知道是同一个原因。修完之后再触发同样的操作,不报错了就说明修好了。
1.2 AI 应用的调试是非线性的
AI 伴侣的调试完全不同。假设你收到一条用户反馈:"AI 回复不对,我明明告诉过它我养了一只猫,它说我没有养宠物。"
这个「回复不对」可能出在整条管线里的任何环节:
- 记忆写入失败:用户说了"我养了一只猫",但异步后处理没把这条事实真正写入数据库
- 记忆检索失败:事实已经写入了,但向量检索的时候没有把它召回来
- 查询理解错误:查询计划解析偏了,导致检索方向不对,根本没去搜"宠物"相关的记忆
- Prompt 组装遗漏:记忆检索到了,但在拼 Prompt 时因为 Token 预算不足被截断了
- 情绪路由偏差:当前使用的回复模板更强调情绪表达,弱化了事实引用
- LLM 幻觉:上下文里明明有"养了一只猫",但模型在生成时还是忽略了这个事实
你会发现,同一个表面症状(「回复不对」),背后可能对应 6 种完全不同的根因,而且这些根因分布在管线的不同阶段。
更麻烦的是,LLM 具备非确定性。同样的输入再跑一次,结果可能不一样。 你用同样的消息测试,AI 这次答对了,并不代表上次答错的原因已经消失。这意味着你不能像传统系统那样依赖「事后复现」来定位问题。
1.3 核心困难
把上面的分析总结成一句话:
AI 系统调试的核心困难不是"代码复杂",而是"结果不稳定,无法可靠复现"。你不能保证复现,所以必须在问题发生的那一刻就捕获完整现场。
这就引出了"可观测性"这个概念——它就是那套帮你在问题发生时自动捕获现场的机制。
2. 可观测性的本质:给 AI 系统装上黑匣子
2.1 从"打日志"到"可观测性"
很多开发者对"可观测性"的第一反应是:不就是多打一点日志吗?
并不是。console.log 式的日志当然有用,但它有几个明显的局限:
- 信息分散:不同节点的日志散落在不同的地方,你需要手动把它们拼起来才能还原一次请求的完整过程
- 缺少结构:日志通常是自由文本,不容易做自动化查询和统计
- 缺少关联:你很难把"这条安全检查的日志"和"那条 LLM 生成的日志"关联到同一次用户请求上
- 缺少度量:日志告诉你"发生了什么",但不能直接告诉你"整体表现如何"
可观测性要解决的,正是这些问题。它不只是"记录信息",而是在运行时自动记录足够多的、结构化的、可关联的信息,让你能在事后还原一次请求的完整处理过程。
2.2 可观测性的三大支柱
在软件工程领域,可观测性通常被拆解为三大支柱。理解这三个概念,有助于你后续理解我们的设计思路:
Logs(日志) ——记录离散事件
日志是最基础的可观测性手段。每当系统里发生了一件值得记录的事,就写一条日志。比如"用户发了一条消息""安全检查通过""LLM 调用超时"。日志的特点是事件驱动、自由文本、适合排查单点问题。
Metrics(指标) ——度量系统状态
指标是对系统行为的数值化度量,通常以时间序列的形式呈现。比如"过去 5 分钟的平均响应时间""每小时的错误率""当前活跃会话数"。指标的特点是聚合性强、适合看趋势和触发报警。
Traces(追踪) ——还原请求链路
追踪是把一次请求经过的所有节点串联起来,形成一条完整的调用链。它能让你看到"这次请求先经过了安全检查,然后做了记忆检索,再组装 Prompt,最后调用 LLM",以及每一步花了多久、输入输出是什么。追踪的特点是面向单次请求、适合深入排查具体问题。
对于 AI 伴侣系统,这三者的分工是这样的:
- Traces 回答:"这一次请求为什么答错了?"——单次请求层面的排查
- Metrics 回答:"这周整体延迟为什么变慢了?"——系统整体层面的监控
- Logs 作为补充,记录一些不适合放进 Trace 或 Metrics 的离散事件
这篇文章重点讲 Traces。下一篇我们会对比主流 Tracing 工具的选型思路。
2.3 黑匣子类比
没有可观测性的 AI 系统,就像没有黑匣子的飞机。事故发生后,你只能猜。
有了可观测性,你就拥有了一个自动记录的黑匣子:每次请求进来,系统会自动记录每个节点接收了什么输入、输出了什么结果、花了多久、有没有触发降级。当问题发生时,你不需要猜测,也不需要复现——打开那次请求的记录,完整现场就在那里。
3. Trace:一次请求的完整快照
3.1 Trace 和 Span 的概念
Trace 是可观测性里最核心的概念。用一句话概括:
- 一个 Trace 代表一次完整的用户请求——从消息进入系统到回复返回给用户
- 一个 Span 代表请求中的一个独立操作——比如安全检查、记忆检索、LLM 调用
一个 Trace 由多个 Span 组成,它们之间可以是平级关系(依次执行),也可以是父子关系(一个操作内部包含多个子操作)。
映射到我们的 AI 管线里:一条用户消息从进入系统到产出回复,就是一个 Trace;安全检查、查询理解、记忆检索、Prompt 组装、LLM 生成,就是一个个 Span。
Canvas actions100%Exit zen mode
Drawing canvas
3.2 一个完整的 Trace 长什么样
下面是一个 Trace 的示意图。请注意看每一行的结构:节点名称 [耗时] → 结果摘要。缩进表示父子关系——比如 vector_search 和 structured_query 是 memory_retrieval 的子操作。
// trace-example.txt
Trace: req_20260311_abc123
│
├── Span: input_safety_check [2ms] → safe
├── Span: query_understanding [180ms] → { intent: "recall_pet", channels: ["semantic", "structured"] }
├── Span: memory_retrieval [95ms] → 3 条记忆命中
│ ├── Span: vector_search [60ms] → 2 条命中(score: 0.89, 0.76)
│ └── Span: structured_query [35ms] → 1 条命中(user_profile.pets)
├── Span: emotion_read [8ms] → { mood: "calm", intimacy: 72 }
├── Span: prompt_assembly [1ms] → 2847 tokens
├── Span: llm_generation [1200ms] → "我记得你养了一只猫..."(156 tokens)
├── Span: output_safety_check [3ms] → safe
└── Async: post_process [320ms]
├── Span: emotion_update [50ms]
├── Span: memory_write [200ms]
└── Span: intimacy_update [70ms]从这张图里你可以直接读出很多信息:
- 最耗时的环节是
llm_generation(1200ms),这在 AI 应用里很正常,LLM 调用通常是延迟大头 - 记忆检索命中了 3 条结果,其中 2 条来自向量搜索、1 条来自结构化查询
- Prompt 组装只花了 1ms,但它把上下文压缩到了 2847 个 Token
- 异步后处理(情绪更新、记忆写入、亲密度更新)总共 320ms,但因为是异步的,不会影响用户等待时间
3.3 有了 Trace,排查变得非常直接
回到那个"AI 说我没有养宠物"的问题。你只要打开对应的 Trace,按图索骥:
第一步:看 memory_retrieval。 如果这个 Span 的输出为空(0 条命中),说明记忆检索层出了问题。可能是向量索引没有更新,也可能是查询理解生成的搜索关键词不对。
第二步:如果检索到了,看 prompt_assembly。
如果记忆检索命中了"用户养了一只猫",但 prompt_assembly 的输出里没有包含这条信息,说明是 Prompt 组装层的问题。最常见的原因是 Token 预算不足,把这条记忆截掉了。
第三步:如果 Prompt 里有,看 llm_generation。 如果 Prompt 里明明写了"用户养了一只猫",但 LLM 的输出还是说"你没有养宠物",那问题更可能出在模型层面——可能是模型幻觉,也可能是 Prompt 里的 few-shot 示例引导了错误方向。
Trace 的价值就在于:不靠猜测,不靠复现,而是直接看当时到底发生了什么。
4. Span 应该记录什么
4.1 核心字段
每个 Span 不能只记录"执行过",还要记录足够的上下文,保证你事后能判断"为什么是这个结果"。
| 字段 | 含义 | 示例 |
|---|---|---|
| traceId | 本次请求唯一标识 | req_20260311_abc123 |
| spanId | 当前 Span 唯一标识 | span_memory_001 |
| parentSpanId | 父级 Span,用于组装树结构 | span_root |
| name | 节点名称 | memory_retrieval |
| startTime | 开始时间(时间戳) | 1710000000123 |
| duration | 节点耗时(毫秒) | 95 |
| status | 执行状态 | ok / error / degraded |
| input | 输入数据 | { query: "我养过什么宠物?" } |
| output | 输出数据 | [{ content: "用户养了一只猫", score: 0.89 }] |
| metadata | 诊断附加信息 | { vectorTopK: 5, model: "text-embedding-3-small" } |
其中几个字段值得额外说明:
- parentSpanId:用来构建 Span 之间的树形层级。比如
vector_search的parentSpanId指向memory_retrieval,表示它是后者的子操作。没有父节点的 Span 就是顶层操作 - input 和 output:这两个字段是排查问题的核心依据。有了它们,你才能回答"这个节点收到了什么、产出了什么"这个最基本的问题
- metadata:放那些不属于 input/output 但对诊断有帮助的附加信息。比如向量检索用了多大的 topK、用了哪个 embedding 模型、当前的 Token 预算上限是多少
4.2 三种状态:ok、error、degraded
这里最值得深入讲解的是 status 字段。传统应用里,节点状态通常只有两种:成功或失败。但在 AI 系统里,还有一种非常重要的中间状态。
ok:按预期流程完成,质量、延迟、上下文都基本正常。这是理想状态。
error:流程在某一步彻底失败,结果不可用或请求直接中断。比如数据库连接超时、LLM API 返回 500 错误、向量索引服务宕机。
degraded(降级):流程还能继续,但已经进入了次优路径,结果虽然可用,质量已经变差了。
degraded 是 AI 系统里最值得关注的状态。因为 AI 管线天然存在大量"退而求其次"的处理方式——系统为了保证对话不断掉,会主动选择次优方案继续往下走。
4.3 degraded 状态的典型场景
以下这些情况都应该被标记为 degraded:
查询理解失败,退回全通道检索。 正常情况下,查询理解会分析用户意图,只搜索最相关的通道(比如只搜"宠物"相关的记忆)。如果查询理解失败了,系统会退回全通道检索——什么都搜,能找到什么算什么。结果大概率还是有的,但精度已经下降了。
记忆只召回了低分结果。 向量检索返回了几条结果,但相似度分数都很低(比如最高才 0.5)。系统仍然使用了这些结果,但回答的可信度已经下降了。
结构化数据源暂时不可用。 本来同时用向量检索和结构化查询两个通道做记忆检索,结果结构化数据库暂时连不上,只剩向量检索单通道工作。检索虽然还能返回结果,但覆盖面已经变窄了。
Prompt 因 Token 预算被裁剪。 组装 Prompt 时发现上下文太长,超过了 Token 预算上限,不得不截断一部分记忆或对话历史。最终传给模型的上下文不完整,回复质量可能受影响。
主模型超时,切换到备用模型。 调用 GPT-4 超时了,系统自动切换到更快但能力稍弱的 GPT-4o-mini。回复还是有的,但推理深度和准确性可能不如主模型。
4.4 为什么 degraded 比 error 更危险
error 很容易被发现。它会触发报警,错误率指标会上升,开发者很快就会注意到并修复。
degraded 则完全不同。它不会触发报警,表面上的成功率还是 100%。但用户已经开始觉得"它最近变笨了""回复没以前准确了""好像不太记得我说过的话了"。
这种体验下滑是渐进的、隐蔽的。如果你只监控"有没有报错",你会以为系统一切正常。但实际上,越来越多的请求在悄悄走 degraded 路径,每一次降级都让回复质量稍微差了一点。
所以做可观测性时,不能只统计"有没有报错",还必须统计"有没有降级"。很多 AI 产品体验下滑,并不是因为系统挂了,而是因为降级比例在不知不觉中升高了。
5. 总结
这一篇的核心目标是把"为什么要做可观测性"和"可观测性的核心概念"讲明白。
回顾一下关键结论:
- AI 系统调试的核心困难不是"代码复杂",而是"结果不稳定,无法可靠复现"。所以必须在问题发生时就捕获完整现场
- 可观测性的三大支柱是 Logs(日志)、Metrics(指标)、Traces(追踪)。对于 AI 系统,Trace 用于排查单次请求问题,Metrics 用于监控系统整体健康度
- Trace 的价值是把一次请求变成可回放、可审计、可逐节点检查的处理快照。有了它,你不需要猜测,也不需要复现
- degraded 状态比 error 更隐蔽也更危险——系统表面上没有报错,但用户体验已经在悄悄下滑
下一篇我们会对比 LangSmith、Langfuse 和自建 Tracing 三种方案的选型思路,帮你理清在不同工程阶段该选哪条路。