AI 电子伴侣
创建时间: 2026-03-24最后更新: 2026-08-04

1. AI 系统为什么更难调试

1.1 传统应用的确定性链路

传统 Web 应用的调试链路通常比较确定。假设用户点击下单按钮后页面报错,我们大致会按照下面的顺序排查:

  1. 打开错误监控平台,找到这条报错日志
  2. 看堆栈信息,定位到是 orderService.create() 方法里抛了异常
  3. 查看异常详情,发现是数据库连接超时
  4. 检查数据库状态,确认是连接池配置不合理
  5. 修改配置,部署上线,问题解决

这类问题通常具备三个特征:可以复现、能够追溯、便于验证。看到相同的报错,我们往往可以沿着同一条调用链找到原因;修复后再执行相同操作,也能直接判断问题是否已经解决。

1.2 AI 应用的非确定性

AI 伴侣的情况就不同了。假设我们收到一条用户反馈:“AI 回复不对,我明明告诉过它我养了一只猫,它却说我没有养宠物。”

表面看是回复内容错了,问题却可能出在整条处理管线的任何一环:

  • 记忆写入失败:用户说了“我养了一只猫”,但异步后处理没有把这条事实真正写入数据库
  • 记忆检索失败:事实已经写入了,但向量检索的时候没有把它召回来
  • 查询理解错误:查询计划解析偏了,导致检索方向不对,根本没有搜索“宠物”相关的记忆
  • Prompt 组装遗漏:记忆检索到了,但在拼 Prompt 时因为 Token 预算不足被截断了
  • 情绪路由偏差:当前使用的回复模板更强调情绪表达,弱化了事实引用
  • LLM 幻觉:上下文里明明有“养了一只猫”,但模型生成回复时还是忽略了这个事实

同一个“回复不对”,背后可能对应 6 种完全不同的根因,而且它们分布在处理管线的不同阶段。只看最终回复,很难判断问题究竟发生在哪里。

LLM 本身还具有非确定性,同样的输入再次执行,得到的结果可能并不相同。用同一条消息重新测试时,AI 这次答对了,并不代表上次答错的原因已经消失。因此,我们不能再像排查传统应用那样,把定位问题完全寄托在事后复现上。

1.3 捕获问题现场

AI 系统难调试,并不只是因为代码更多,而是因为结果不稳定,问题也未必能够可靠复现。既然无法保证重新触发同一个结果,就需要在问题发生的那一刻,把完整的处理现场记录下来。

这正是可观测性要解决的问题:系统在运行过程中自动留下足够的信息,让我们可以在事后还原当时发生了什么。

2. 从日志到可观测性

2.1 日志为什么不够

提到可观测性,很容易先想到多写几条日志。console.log 式的日志当然有用,但仅靠它还不足以还原一条完整的 AI 请求,原因主要有以下几个:

  • 信息分散:不同节点的日志散落在不同的地方,你需要手动把它们拼起来才能还原一次请求的完整过程
  • 缺少结构:日志通常是自由文本,不容易做自动化查询和统计
  • 缺少关联:很难确认一条安全检查日志和另一条 LLM 生成日志是否属于同一次用户请求
  • 缺少度量:日志能够说明发生了什么,却不能直接反映系统的整体表现

可观测性并不是简单地增加日志数量,而是让系统在运行时自动记录足够多的、结构化且能够相互关联的信息。这样,即使问题已经过去,我们仍然可以还原一次请求的完整处理过程。

2.2 Logs、Metrics 与 Traces

软件工程中的可观测性通常由 Logs、Metrics 和 Traces 三部分组成。它们记录的对象不同,解决的问题也不同。

**Logs(日志)**记录离散事件。系统里每当发生一件值得关注的事情,就可以写入一条日志,例如用户发送消息、安全检查通过,或者 LLM 调用超时。日志由事件触发,内容通常是自由文本,适合补充上下文和排查单点问题。

**Metrics(指标)**用数值描述系统状态,通常按照时间序列存储。过去 5 分钟的平均响应时间、每小时错误率和当前活跃会话数,都属于指标。它们经过聚合后更容易观察趋势,也适合设置报警规则。

**Traces(追踪)**关注单次请求经过的完整链路。它会把安全检查、记忆检索、Prompt 组装和 LLM 调用等节点串联起来,同时记录每一步的耗时、输入与输出。需要深入排查某一次具体请求时,Trace 往往最有价值。

放到 AI 伴侣系统中,我们可以这样理解三者的分工:

  • Traces 回答“这一次请求为什么答错了”,用于排查单次请求
  • Metrics 回答“这周整体延迟为什么变慢了”,用于监控系统整体状态
  • Logs 作为补充,记录一些不适合放进 Trace 或 Metrics 的离散事件

这一篇先把重点放在 Traces 上,下一篇再继续比较主流 Tracing 工具的选型思路。

2.3 记录问题发生时的现场

可以把可观测系统理解为飞机上的黑匣子,只不过它记录的是 AI 应用的运行过程。没有这些记录,问题发生后只能依靠猜测;有了它,每次请求经过的节点都会留下输入、输出、耗时和降级状态。

当用户反馈异常时,我们不必强行复现同一个回答,只需要找到那次请求的记录,就能查看当时的完整现场。

3. Trace 与 Span

3.1 两个基本概念

Trace 是可观测性中的核心概念。理解它之前,我们先区分 Trace 和 Span:

  • Trace 代表一次完整的用户请求,从消息进入系统开始,到回复返回给用户结束
  • Span 代表请求中的一个独立操作,例如安全检查、记忆检索或 LLM 调用

一个 Trace 由多个 Span 组成。Span 之间既可以平级,表示多个独立操作依次执行;也可以形成父子关系,表示一个操作内部还包含更细的子操作。

映射到 AI 管线中,一条用户消息从进入系统到生成回复,就是一个 Trace;安全检查、查询理解、记忆检索、Prompt 组装和 LLM 生成,则分别对应不同的 Span。

正在加载图示...

3.2 读取一条 Trace

下面是一条 Trace 的完整记录。每一行都遵循 节点名称 [耗时] → 结果摘要 的结构,缩进则用来表示父子关系。例如,vector_searchstructured_query 都是 memory_retrieval 的子操作。

trace-example.txt
01
Trace: req_20260311_abc123
02
03
├── Span: input_safety_check [2ms] → safe
04
├── Span: query_understanding [180ms] → { intent: "recall_pet", channels: ["semantic", "structured"] }
05
├── Span: memory_retrieval [95ms] → 3 条记忆命中
06
│ ├── Span: vector_search [60ms] → 2 条命中(score: 0.89, 0.76)
07
│ └── Span: structured_query [35ms] → 1 条命中(user_profile.pets)
08
├── Span: emotion_read [8ms] → { mood: "calm", intimacy: 72 }
09
├── Span: prompt_assembly [1ms] → 2847 tokens
10
├── Span: llm_generation [1200ms] → "我记得你养了一只猫..."(156 tokens)
11
├── Span: output_safety_check [3ms] → safe
12
└── Async: post_process [320ms]
13
├── Span: emotion_update [50ms]
14
├── Span: memory_write [200ms]
15
└── 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_searchparentSpanId 指向 memory_retrieval,就表示前者是后者的子操作;没有父节点的 Span 则是顶层操作。

inputoutput 是排查问题时最重要的依据。只有记录了这两个字段,我们才能确认一个节点收到了什么,又产出了什么。metadata 则用来保存不属于输入或输出、但对诊断有帮助的信息,例如向量检索使用的 topK、embedding 模型,以及当前的 Token 预算上限。

4.2 三种状态:ok、error、degraded

status 是 Span 中需要重点关注的字段。传统应用通常只区分成功和失败,而 AI 系统还需要记录一种重要的中间状态。

ok 表示流程按预期完成,质量、延迟和上下文都基本正常。

error 表示流程在某一步彻底失败,结果不可用,或者请求直接中断,例如数据库连接超时、LLM API 返回 500 错误、向量索引服务宕机。

degraded(降级) 表示流程仍然可以继续,但已经进入次优路径。结果虽然可用,质量却已经下降。

AI 管线里存在大量退而求其次的处理方式。为了保证对话不中断,系统往往会主动选择次优方案继续执行,因此 degraded 是 AI 系统中尤其需要关注的状态。

4.3 常见的降级场景

下面这些情况都应该标记为 degraded

查询理解失败,退回全通道检索。 正常情况下,查询理解会分析用户意图,只搜索最相关的通道,例如只查询“宠物”相关的记忆。如果查询理解失败,系统会改用全通道检索。虽然大概率仍能找到结果,但精度已经下降。

记忆只召回了低分结果。 向量检索返回了几条结果,但相似度分数都很低(比如最高才 0.5)。系统仍然使用了这些结果,但回答的可信度已经下降了。

结构化数据源暂时不可用。 本来同时用向量检索和结构化查询两个通道做记忆检索,结果结构化数据库暂时连不上,只剩向量检索单通道工作。检索虽然还能返回结果,但覆盖面已经变窄了。

Prompt 因 Token 预算被裁剪。 组装 Prompt 时发现上下文太长,超过了 Token 预算上限,不得不截断一部分记忆或对话历史。最终传给模型的上下文不完整,回复质量可能受影响。

主模型超时,切换到备用模型。 调用 GPT-4 超时后,系统自动切换到速度更快、能力稍弱的 GPT-4o-mini。回复仍然能够生成,但推理深度和准确性可能不如主模型。

4.4 监控 degraded 的必要性

error 通常很容易被发现。它会触发报警,错误率指标也会上升,开发者往往能够较快注意到并处理。

degraded 则更隐蔽。请求没有报错,表面上的成功率仍然是 100%,但用户可能已经开始觉得“它最近变笨了”“回复没有以前准确了”“好像不太记得我说过的话了”。

这种体验下滑往往是渐进且隐蔽的。如果只监控是否报错,我们会以为系统一切正常;实际上,可能有越来越多的请求正在进入 degraded 路径,每一次降级都会让回复质量下降一点。

因此,建设可观测性时不能只统计错误,还必须统计降级。很多 AI 产品的体验下降,并不是因为系统彻底不可用,而是因为降级请求的比例在不知不觉中升高了。

5. 总结

AI 系统的调试难点不只来自代码复杂度,更来自结果不稳定和难以可靠复现。要解决这个问题,系统需要在请求发生时捕获完整现场,而不是等到用户反馈以后再尝试重现同一个回答。

Logs、Metrics 和 Traces 共同构成可观测性的基础。Logs 负责补充离散事件,Metrics 用来观察系统整体状态,Trace 则把单次请求保存为可回放、可审计、能够逐节点检查的处理快照。

在记录成功和失败之外,还需要特别关注 degraded。系统即使没有报错,也可能因为持续进入降级路径而让回复质量逐渐下降。只有把降级比例纳入监控,才能更早发现这种不易察觉的体验变化。

下一篇我们会比较 LangSmith、Langfuse 和自建 Tracing 三种方案,分析它们分别适合怎样的工程阶段。