
1. 从一次线上事故说起对话记录为什么不够用去年冬天我负责的一个客服类 AI Agent 在凌晨两点突然开始给用户回复完全无关的内容。用户问退货政策它回答天气用户问物流进度它开始背诵产品说明书。值班同事第一反应是打开对话记录一条条翻看用户和 Agent 的往来消息。看了半小时只看到“输入正常、输出异常”但完全不知道问题出在哪一步——是意图识别错了是工具调用返回了脏数据还是记忆模块把上一轮的内容串了这就是只盯着对话记录做排查的典型困境。对话记录Conversation Log本质上只是 Agent 与用户之间的输入输出快照它告诉你“发生了什么”但几乎不告诉你“为什么发生”。一个 AI Agent 在运行时内部可能经历了意图解析、上下文组装、工具选择、参数生成、外部 API 调用、结果解析、回复生成等七八个环节对话记录只记录了首尾两端中间全是黑盒。可观测性Observability这个词在传统后端服务里已经讲了很多年但放到 AI Agent 场景下它的内涵完全不一样。传统服务的可观测性主要靠 Metrics、Logs、Traces 三件套而 AI Agent 的可观测性还要额外关注推理链路——也就是模型在每一步“想了什么”“选了哪个工具”“传了什么参数”“拿到了什么结果”。没有这层观测能力排查故障就像在黑暗中找一根掉在地上的针。这篇文章适合两类人看一类是正在搭建或维护 AI Agent 的工程师另一类是团队里负责 AI 应用稳定性的 SRE 或技术负责人。我会从实际故障场景出发拆解为什么对话记录不够用、trace ID 和 span ID 在 Agent 场景下怎么落地、以及如何用一套可观测性体系把“黑盒排查”变成“白盒定位”。中间会穿插大量实操细节和踩坑经验你可以直接抄作业。2. 对话记录的三个致命盲区2.1 盲区一看不到中间步骤的决策依据对话记录最致命的问题是它只记录最终输出。假设用户问“帮我查一下上周的订单”Agent 最终回复“您上周没有订单”。这个回复可能是对的也可能是错的。如果它是错的问题可能出在意图识别把“查订单”误判成了“查物流”时间解析把“上周”算成了“上上周”工具调用时传入了错误的用户 ID订单查询 API 返回了空结果但 Agent 没有做二次确认这四个环节在对话记录里全部不可见。你只能看到用户输入和 Agent 输出中间发生了什么完全靠猜。更麻烦的是AI Agent 的决策往往带有概率性——同一个输入不同时间可能走不同的工具调用路径。没有中间步骤的记录你连复现都做不到。我试过在一个 LangChain 项目里只靠对话记录排查结果花了三个小时才定位到一个简单的参数拼接错误。后来接入了 trace 体系同样的故障五分钟就找到了根因。这个效率差距不是线性提升是数量级的差异。2.2 盲区二无法关联外部依赖的调用状态AI Agent 和普通聊天机器人最大的区别在于它会调用外部工具。查数据库、调 API、读文件、发消息这些外部依赖的调用状态在对话记录里几乎不可见。我遇到过好几次这样的情况Agent 回复“查询失败请稍后重试”但对话记录里只有这句回复没有记录它调了哪个接口、返回了什么状态码、耗时多久。如果外部 API 返回了 500 错误但 Agent 的容错逻辑把它吞掉了你从对话记录里根本看不出来。更隐蔽的是超时场景——外部 API 响应慢Agent 等了三秒后放弃并返回兜底话术对话记录里只会显示兜底话术你不会知道背后有一次超时。注意很多 Agent 框架默认不记录工具调用的详细日志你需要显式开启或自己埋点。LangChain 的verboseTrue只能打印到控制台生产环境需要接入结构化日志系统。2.3 盲区三缺少跨会话的上下文追踪一个用户可能在一个会话里先问“我的订单在哪”然后问“帮我退了吧”再问“退款什么时候到”。这三轮对话在业务上是强关联的但在对话记录里它们是三条独立的消息。如果退款环节出了问题你需要把三轮对话串起来看才能理解 Agent 为什么在第三轮调用了退款查询接口。更复杂的是多用户并发场景。当几十个用户同时和 Agent 交互时对话记录是交织在一起的。没有 trace ID 做关联你根本分不清哪条工具调用属于哪个用户。我见过一个团队在排查并发问题时把两个用户的对话记录混在一起分析得出了完全错误的结论。3. Trace ID 与 Span ID给 Agent 装上“行车记录仪”3.1 用生活化类比理解 Trace 和 Span把一次 AI Agent 的完整处理过程想象成一次快递配送。Trace ID 就是快递单号它唯一标识这一次配送任务。Span ID 则是配送过程中的每一个节点揽件、分拣、运输、派送、签收。每个节点有自己的开始时间、结束时间、状态和附加信息。当用户投诉“快递没收到”时你不可能只看“已签收”这个最终状态就下结论。你需要看每个节点的记录揽件时间对不对分拣有没有出错运输途中是否滞留派送员是谁这些信息对应到 AI Agent 场景就是Trace ID一次用户请求的全局唯一标识Span ID请求在 Agent 内部经过的每个环节意图识别、工具选择、API 调用、回复生成Span 属性每个环节的输入、输出、耗时、状态、错误信息有了这套体系排查故障时你不再需要“猜”而是可以沿着 trace 树一层层往下看直到找到异常的那个 span。3.2 在 Agent 中落地 Trace ID 的三种方式方式一框架原生支持。LangChain、LlamaIndex 等主流框架已经内置了 callback 机制可以接入 LangSmith、LangFuse 等可观测性平台。你只需要配置环境变量框架会自动为每次请求生成 trace ID 并上报 span 数据。这种方式最省事但灵活性受限某些自定义环节可能覆盖不到。方式二手动埋点。在 Agent 的每个关键环节手动创建 span通过 OpenTelemetry SDK 上报。这种方式最灵活但工作量大需要你对 Agent 的执行流程非常熟悉。我一般建议在核心链路上用这种方式比如工具调用和外部 API 请求。方式三混合模式。框架原生支持覆盖通用环节手动埋点补充自定义环节。这是我在生产环境最常用的方案。比如用 LangChain 的 callback 自动记录 LLM 调用同时在工具函数里手动加 span 记录参数和返回值。from opentelemetry import trace tracer trace.get_tracer(__name__) def query_order(user_id: str, time_range: str): with tracer.start_as_current_span(query_order) as span: span.set_attribute(user_id, user_id) span.set_attribute(time_range, time_range) try: result order_api.query(user_id, time_range) span.set_attribute(result_count, len(result)) return result except Exception as e: span.set_attribute(error, str(e)) span.record_exception(e) raise这段代码的关键在于span 不仅记录了“调用了 query_order”还记录了入参、出参数量和异常信息。当订单查询返回空结果时你可以立刻判断是 API 真的没数据还是参数传错了。3.3 Span 的粒度怎么定太粗没用太细爆炸Span 粒度是实操中最容易踩坑的地方。我见过两种极端一种是只记录一个顶层 span相当于什么都没记另一种是每个函数调用都加 span结果一次请求产生上千个 span存储成本爆炸排查时也找不到重点。我的经验法则是按“可能出错的独立环节”来划分 span。具体来说LLM 调用必须单独一个 span记录 prompt、模型名、token 数、耗时工具调用每个工具一个 span记录工具名、入参、出参、耗时外部 API 请求每个请求一个 span记录 URL、状态码、耗时记忆读写如果用了向量数据库或缓存读写各一个 span意图识别/路由如果这一步逻辑复杂单独一个 span其他辅助函数、格式化逻辑、字符串拼接之类的不需要单独 span否则噪音太大。4. 从零搭建一套 Agent 可观测性体系4.1 整体架构设计一套完整的 Agent 可观测性体系包含四个层次采集层负责在 Agent 运行时收集 trace、log、metric 数据。常用工具是 OpenTelemetry SDK它支持多种语言和框架数据格式统一。传输层负责把采集到的数据送到后端。可以用 OpenTelemetry Collector 做中转它支持批处理、重试、采样等策略避免数据丢失或压垮后端。存储层负责持久化数据。Trace 数据通常存到 Jaeger、Tempo、Zipkin 等系统Log 数据存到 Elasticsearch、LokiMetric 数据存到 Prometheus。展示层负责查询和可视化。Grafana 是最常用的选择它可以同时展示 trace、log、metric支持关联查询。对于中小团队我建议直接用 LangFuse 或 LangSmith 这类专门为 LLM 应用设计的平台它们开箱即用省去了搭建和维护基础设施的成本。对于有自建能力的大团队OpenTelemetry Jaeger Grafana 的组合更灵活数据也完全可控。4.2 关键埋点位置与参数设计埋点位置决定了你能否定位到问题。以下是我在实践中总结的必埋点清单埋点位置关键属性排查用途请求入口trace_id, user_id, session_id, input_text关联用户和会话意图识别intent, confidence, candidates判断意图是否识别错误上下文组装context_length, memory_hits, truncated判断上下文是否丢失或超长LLM 调用model, prompt_tokens, completion_tokens, latency判断模型是否超时或输出异常工具选择tool_name, tool_args, selection_reason判断工具是否选错工具执行tool_result, status, latency, error判断工具是否失败回复生成output_text, finish_reason判断回复是否被截断请求出口total_latency, status, error整体成功率统计这张表里的每一行都对应一个真实的故障场景。比如“上下文组装”这一行如果truncatedtrue说明上下文被截断了Agent 可能因为丢失关键信息而做出错误决策。再比如“工具执行”的status字段如果大量出现timeout说明外部依赖不稳定需要加熔断或重试。4.3 采样策略全量还是抽样生产环境全量采集 trace 数据成本很高尤其是 LLM 调用频繁的场景。我的建议是分层采样错误请求全量采集任何 status 为 error 的 trace 必须完整保留慢请求全量采集超过 P99 延迟的请求完整保留正常请求按比例采样比如 10% 或 1%用于统计和趋势分析特定用户全量采集VIP 用户或测试账号的请求全量保留OpenTelemetry Collector 支持基于属性的采样策略你可以根据status、latency、user_tier等属性动态决定是否采样。这样既控制了成本又保证了关键数据不丢失。提示采样策略不是一成不变的。上线初期建议全量采集摸清正常和异常的分布后再逐步调整采样率。我见过团队一上来就设 1% 采样结果故障发生时发现关键 trace 没被采集到排查陷入僵局。5. 实战排查三个真实故障的定位过程5.1 故障一工具调用参数错位现象用户反馈 Agent 查询订单时经常返回别人的订单信息。对话记录只显示用户问“查订单”Agent 回复了订单详情看起来正常。Trace 排查打开 trace 树发现query_orderspan 的user_id属性值和请求入口的user_id不一致。进一步看发现是上下文组装环节把上一个请求的user_id缓存了下来没有随新请求更新。根因Agent 用了全局变量存储用户 ID并发场景下发生串号。修复把用户 ID 改为请求级变量通过参数传递而不是全局存储。这个故障如果只看对话记录你甚至不会意识到有问题——因为 Agent 确实返回了订单信息只是返回了错误的订单。只有 trace 里的user_id对比才能暴露这个 bug。5.2 故障二LLM 输出被截断导致回复不完整现象Agent 回复经常在句子中间断掉比如“您的订单预计明天”就没了。对话记录显示 Agent 输出确实不完整但不知道为什么。Trace 排查查看 LLM 调用 span发现finish_reason是length说明输出达到了 max_tokens 限制被截断。再看completion_tokens数值正好等于配置的 max_tokens。根因max_tokens 设置太小复杂回复被截断。修复调大 max_tokens同时在回复生成后加校验如果finish_reasonlength则触发续写或兜底话术。这个故障的排查关键在于finish_reason这个属性。对话记录不会告诉你输出为什么断掉但 trace 里的 LLM span 会明确记录截断原因。5.3 故障三外部 API 超时引发级联失败现象Agent 在高峰期大量返回“系统繁忙请稍后重试”。对话记录全是兜底话术看不出具体原因。Trace 排查按时间范围筛选 trace发现大量query_logisticsspan 的latency超过 5 秒status为timeout。进一步看物流 API 的 P99 延迟从平时的 200ms 飙升到 8 秒。根因物流 API 在高峰期过载Agent 没有设置合理的超时和熔断策略导致请求堆积。修复给物流 API 调用设置 2 秒超时超时后走缓存或降级话术同时接入熔断器连续失败达到阈值后直接跳过调用。这个故障的排查完全依赖 trace 数据。对话记录只能告诉你“失败了”trace 才能告诉你“哪一步失败了、失败了多少次、耗时多久”。5.4 常见问题速查表现象可能原因排查入口Agent 回复答非所问意图识别错误或上下文丢失查看 intent span 和 context span工具调用返回空结果参数错误或 API 异常查看 tool span 的 args 和 status回复不完整max_tokens 截断或超时查看 LLM span 的 finish_reason响应时间过长LLM 慢或外部 API 慢查看各 span 的 latency 分布并发场景下数据串号全局变量或缓存污染对比 trace 间的 user_id 一致性偶发性失败无法复现概率性决策或竞态条件按 trace_id 回放完整链路6. 工具选型自建还是用现成平台6.1 主流方案对比方案优势劣势适用场景LangSmith与 LangChain 深度集成开箱即用闭源数据在云端按量收费快速验证小团队LangFuse开源可自建功能全面自建需要维护基础设施中大型团队数据敏感OpenTelemetry Jaeger标准化灵活可控需要自己埋点和搭建有 SRE 能力的大团队自研方案完全定制开发成本高容易重复造轮子有特殊合规要求我的建议是先用 LangFuse 或 LangSmith 快速跑起来等业务规模上来后再考虑自建。很多团队一上来就追求自建结果花了两个月搭基础设施真正用来排查故障的时间反而少了。工具是手段不是目的。6.2 选型时容易忽略的三个点数据保留策略Trace 数据量很大默认保留 30 天可能不够排查历史问题保留一年又成本太高。我一般建议热数据保留 7 天温数据保留 30 天冷数据归档到对象存储保留一年。查询性能当 trace 数量达到千万级时查询性能会成为瓶颈。选型时要关注平台是否支持索引优化、是否支持按属性过滤、是否支持聚合分析。告警集成可观测性不只是事后排查还要能事前告警。选型时要确认平台是否支持基于 trace 属性的告警规则比如“工具调用失败率超过 5% 时触发告警”。7. 我踩过的坑和总结的经验第一个坑是埋点太多导致性能下降。早期我在每个函数入口都加了 span结果 Agent 的响应时间增加了 30%。后来精简到只保留关键环节性能影响降到 5% 以内。埋点本身有开销尤其是序列化和网络上报一定要控制粒度。第二个坑是trace 数据没有关联 log。Trace 告诉你“哪一步慢了”但有时候你需要看那一步的详细日志才能知道“为什么慢”。后来我在 span 里加了log_ref属性指向对应的日志 ID排查时可以一键跳转。第三个坑是忽略了采样策略的动态调整。有一次线上故障我需要看某个时间段的完整 trace结果发现那段时间正好被采样策略过滤掉了。后来我加了一个机制当错误率超过阈值时自动临时切换到全量采集持续 10 分钟后恢复。最后一个经验是可观测性建设要趁早。很多团队等到出了大故障才想起来补埋点但那时候已经积累了大量技术债补起来很痛苦。我的建议是在 Agent 开发的第一天就把 trace 体系搭好哪怕一开始只记录最基础的几个 span后面再逐步完善。这就像盖房子先打地基后面加楼层才稳。如果你现在还在靠对话记录排查 Agent 故障我强烈建议你从下一个项目开始接入 trace 体系。刚开始可能会觉得麻烦但当你第一次在五分钟内定位到一个困扰团队一周的 bug 时你会觉得所有投入都值得。