LLM应用可观测性实践:从Span、三层Trace到Prompt Diff的工程化方案
1. 从“炼丹”到“工程”:为什么你的LLM应用一上线就失控?
最近和几个团队聊,发现一个挺普遍的现象:大家用大模型(LLM)做应用,开发阶段都挺兴奋,感觉无所不能。一旦要上线,或者上线后用户量一上来,整个系统就变成了一个“黑盒”。用户反馈“回答不对”,你只能去翻日志,结果日志里只有“调用成功”或者“调用失败”,至于模型为什么给出这个答案,中间经历了什么思考,prompt到底长什么样,一概不知。排查一个问题,动辄几小时甚至几天,效率极低。这感觉,就像回到了软件工程的“刀耕火种”时代。
这背后的核心问题,是可观测性(Observability)的缺失。传统的微服务监控三板斧——日志(Logs)、指标(Metrics)、链路追踪(Traces),在面对LLM应用时,突然失灵了。你记录了一个API调用耗时,但这能告诉你模型为什么把“苹果公司”理解成“卖水果的”吗?你看到了错误率飙升,但这能帮你定位是哪个环节的prompt写崩了吗?显然不能。
LLM应用的可观测性,需要一套全新的、分层的工程实践。它不能只停留在“系统跑没跑通”的层面,必须深入到“AI是怎么想的”这个层面。今天我想分享的,就是我们团队在实践中摸索出来的一套三层Trace体系,它从最基础的调用Span,到核心的Prompt工程,再到顶层的业务逻辑,层层递进,让AI系统从“不可知”变得“真正可调试”。这套方法的核心,就是标题里的三个关键词:Span、三层Trace、Prompt Diff。
2. 第一层:基础设施Span——建立可观测性的“地基”
任何可观测性体系,都需要一个可靠的数据采集基础。对于LLM应用,这个基础就是调用链(Trace)和跨度(Span)。但这里的Span,和传统微服务的Span有本质不同。
2.1 重新定义LLM的Span:不止于HTTP调用
在传统系统中,一个Span通常对应一次HTTP或RPC调用,记录的是网络IO层面的元数据:起止时间、状态码、请求/响应大小。对于LLM调用(比如调用OpenAI的ChatCompletion接口),如果只记录这个,价值非常有限。你只知道“调了API,花了3秒,成功了”,仅此而已。
一个真正有用的LLM Span,必须包含以下核心信息:
- 模型与参数:调用的具体模型标识(如
gpt-4-turbo-preview)、温度(temperature)、最大token数(max_tokens)、top_p等。这些参数直接决定了输出的随机性和成本。 - Token用量与成本:本次调用的输入token数、输出token数、总token数。这是成本核算和配额管理的直接依据。很多诡异的“超时”或“失败”,根源是token超限,而非网络问题。
- 完整的Prompt与Completion:这是最关键的。必须记录发送给模型的完整提示词(prompt),以及模型返回的完整内容(completion)。注意,这里要记录的是原始、未经截断的内容。很多日志系统默认截断长文本,这对于调试LLM是致命的。
- 供应商元数据:如果使用云服务,需要记录请求ID(如OpenAI的
request_id)、模型版本等,便于在出问题时与供应商侧日志关联排查。
实操心得:Span的存储与采样策略记录如此丰富的信息,尤其是长文本,对存储是巨大挑战。我们的策略是分级存储:
- 高采样率(如100%)记录元数据:模型、参数、token数、耗时、状态,这些数据量小,必须全量记录,用于做实时监控和统计(如每分钟平均耗时、token消耗速率)。
- 低采样率(如1%-10%)记录完整内容:完整的prompt和completion,存储到对象存储(如S3)或专门的日志平台,并建立索引。在控制台查询时,默认只展示元数据,点击详情时才去拉取完整内容。这样既保证了调试能力,又控制了成本。
2.2 构建调用链:串联起AI的“思考”过程
单一的LLM调用Span价值有限。真实的LLM应用,往往是一个复杂的管道(Pipeline)。例如,一个客服机器人可能包含以下步骤:
- 用户输入 -> 2. 意图识别(调用一次LLM) -> 3. 查询知识库 -> 4. 信息合成(再调用一次LLM) -> 5. 格式化输出。
你需要将步骤2和步骤4的LLM调用Span,与步骤1、3、5的业务逻辑Span,串联在同一条Trace中。这样,当用户得到一个错误答案时,你可以沿着这条Trace,清晰地看到:
- 意图识别阶段,模型把用户问题理解成了什么?
- 知识库查询返回了哪些片段?
- 合成阶段,模型基于哪些上下文生成了最终答案?
这个调用链,就是你可观测性的“主干道”。没有它,所有Span都是孤岛,你无法复原一次请求的完整生命周期。
避坑指南:异步与并发的Trace上下文传递LLM调用往往是应用中最耗时的环节,因此代码中大量使用异步(Async)或并发。这会导致Trace上下文(Trace ID, Span ID)在跨线程/跨协程时丢失。你必须确保你的追踪SDK(如OpenTelemetry)能够正确处理异步上下文传播。在Python的asyncio中,这通常意味着使用contextvars;在并发场景下,需要手动传递上下文。这一步没做好,调用链就会断掉,前功尽弃。
3. 第二层:Prompt工程与Agent Trace——洞察AI的“思维链路”
有了基础设施层的Span,我们知道了“发生了什么”。但要理解“为什么发生”,就需要进入第二层:Prompt与Agent层面的可观测性。这一层关注的是AI内部的“思维过程”,尤其是当使用复杂提示技巧或智能体(Agent)框架时。
3.1 记录思维链(Chain-of-Thought)与工具调用
许多高级应用会要求模型“一步一步思考”(Chain-of-Thought, CoT),或者让模型自主决定调用哪些工具(如计算器、搜索API)。这些内部过程,对于调试至关重要。
例如,一个数学解题Agent的Trace应该记录:
- 用户输入:“一个篮子里有5个苹果,拿走2个,又放进3个,现在有几个?”
- 模型第一次思考(CoT):“首先,最初有5个。拿走2个,剩下5-2=3个。然后放进3个,变成3+3=6个。所以答案是6。”
- (如果涉及工具调用):模型决定调用计算器,输入“5-2”和“3+3”,并记录工具返回的结果。
- 模型最终输出:“现在有6个苹果。”
在可观测性控制台,你应该能像看剧本一样,看到模型完整的“内心独白”和“动作”。这能帮你判断:是模型逻辑推理错了,还是它调用的工具返回了错误结果?
3.2 核心武器:Prompt Diff——定位问题的“显微镜”
这是调试LLM应用最锋利的工具,没有之一。所谓Prompt Diff,就是对比不同请求之间,发送给模型的完整提示词(包括系统指令、上下文、用户问题等)的差异。
为什么它如此重要?因为LLM的输出对输入极其敏感。一个标点的改变、一个示例顺序的调整、甚至上下文列表中多了一条不相关的信息,都可能导致输出天差地别。
实战场景:线上突然有大量用户投诉,说客服机器人开始胡言乱语。你查看错误率指标,一切正常;查看耗时,也没有异常。这时,你打开Prompt Diff工具:
- 选择一个正常请求的Trace和一个异常请求的Trace。
- 工具自动高亮显示两者prompt的差异点。
- 你立刻发现,异常请求的prompt中,系统指令(system message)被意外地截断了一部分,导致模型失去了关键的约束条件。根源是上游某个服务在拼接上下文时,发生了字符串截断bug。
如果没有Prompt Diff,你可能需要人工逐个对比上百行的prompt文本,效率极低且容易遗漏。有了它,问题根源一目了然。
技术实现要点: Prompt Diff的实现,依赖于第一层中记录的完整prompt。在界面上,它就是一个代码对比视图(类似Git Diff)。关键在于,diff的粒度要足够细,最好能到单词级别,并且能智能地忽略一些无关紧要的格式变化(如多余的空格、换行符)。
4. 第三层:业务语义与评估集成——让可观测性产生业务价值
前两层解决了技术层面的可观测性问题。第三层要回答的是:“这对我的业务意味着什么?” 这一层的目标是将AI的Trace与业务语义和效果评估挂钩。
4.1 为Trace打上业务标签
单纯的技术Trace(Model: gpt-4, Tokens: 1500)对业务同学没有意义。我们需要在Trace上附加业务维度标签(Tags)。
- 场景(Scenario):
customer_service.intent_classification(客服-意图识别)、content_generation.blog_outline(内容生成-博客大纲)。 - 会话类型(Session Type):
new_user_onboarding(新用户引导)、troubleshooting(故障排查)。 - 用户属性(User Segment):
vip_user、trial_user。 - 业务结果(Business Outcome):
conversion_successful(转化成功)、escalated_to_human(转人工)。
打上这些标签后,你的监控和排查就拥有了业务视角。你可以轻松地回答以下问题:
- “VIP用户在使用知识问答功能时,平均响应时间是否比普通用户慢?”
- “在‘产品推荐’场景下,使用gpt-3.5-turbo和gpt-4的输出质量(通过后续评估)差异有多大?”
- “有多少‘投诉类’会话最终被转给了人工客服?它们对应的AI Trace中,常见的失败模式是什么?”
4.2 集成评估结果:连接“表现”与“效果”
可观测性告诉你系统“如何运行”,评估(Evaluation)告诉你运行得“好不好”。将两者结合,才能形成闭环。
我们的做法是,在每次LLM调用完成后(或一个会话结束后),异步触发一个评估流程。这个评估可以是:
- 基于规则的(Rule-based):检查输出是否包含敏感词、是否符合指定的JSON格式。
- 基于模型的(LLM-as-a-Judge):用另一个LLM,根据预设的标准(相关性、准确性、友好度)对输出进行打分。
- 人工反馈(Human-in-the-loop):将不确定的case推送给标注平台,由人工打分。
关键一步:将评估结果(分数、是否通过、评语)作为一个Span,附加到原始的调用Trace上。
这样,当你在可观测性平台查看一条Trace时,你不仅能看到输入输出,还能直接看到这次调用的“得分”。你可以快速筛选出“低分”或“失败”的Trace,直接分析其对应的prompt和上下文,从而快速定位模型或Prompt的缺陷。
经验分享:评估的采样与成本对每一次调用都进行LLM评估成本过高。我们的策略是:
- 关键业务路径全量评估:对于核心流程(如订单处理、法律咨询),不惜成本,全量评估。
- 非关键路径抽样评估:对于其他场景,按比例(如5%)抽样评估,用于发现潜在问题。
- 异常检测驱动评估:利用第一层的指标(如响应时间异常长、输出token数异常多)作为触发器,自动对这类“异常请求”进行重点评估,往往能高效地发现边缘case。
5. 实战:搭建三层可观测性体系的工具箱与流程
理论说完了,具体怎么落地?这里分享我们技术栈的核心选型和操作流程。
5.1 技术栈选型:开源与云服务的权衡
没有银弹,需要根据团队规模和需求组合。
| 层级 | 核心需求 | 开源方案(自主可控) | 云服务/商业方案(开箱即用) | 我们的选择与理由 |
|---|---|---|---|---|
| 第一层(Span) | 分布式追踪,Span记录与收集 | OpenTelemetry (OTEL)+ Jaeger/Tempo | Datadog APM, New Relic, AWS X-Ray | OTEL + 自研存储。OTEL已成为行业标准, instrumentation丰富。我们将Span数据导出到ClickHouse,兼顾查询性能与成本。商业方案太贵,且对LLM特定字段支持不深。 |
| 第二层(Prompt/Agent) | 记录复杂工作流,实现Prompt Diff | LangSmith, Phoenix | 同上(部分商业APM开始支持) | LangSmith。它对LangChain/LlamaIndex等主流框架有原生深度集成,能自动记录Chain、Agent的每一步思考、工具调用和中间结果,并内置了强大的对比调试功能。这是目前生态中最成熟的选择。 |
| 第三层(业务/评估) | 打标签,集成评估结果 | OTEL Attributes + 自研评估服务 | Arize, WhyLabs, TruEra | OTEL Attributes + 自研评估服务。业务标签通过OTEL的Attributes API注入。评估逻辑因业务而异,我们自研了一个轻量评估服务,接收Trace数据,调用规则或模型进行评估,再将结果写回Trace。 |
注意:如果你刚开始,不建议所有都自研。可以从LangSmith开始,它覆盖了第一层和第二层的核心需求,能让你快速获得可观测能力。待需求明确后,再考虑是否替换或补充其他组件。
5.2 核心实施流程:从代码到洞察
代码埋点(Instrumentation):
- 在你的LLM调用客户端(如OpenAI SDK、LangChain)中,集成OTEL或LangSmith的SDK。现在主流框架都提供了便捷的集成方式,通常是几行配置代码。
- 关键点:确保在所有异步边界和并发操作中正确传播Trace上下文。
数据收集与存储:
- 部署OTEL Collector,接收来自应用的数据。
- 配置Processor,对Span进行采样(如:元数据全采样,完整内容1%采样)。
- 将数据导出到后端存储:元数据到ClickHouse/Prometheus,完整内容到S3或Elasticsearch。
可视化与查询:
- 使用Grafana(对接ClickHouse)或Jaeger UI来查看调用链和基础指标。
- 使用LangSmith的界面来深入分析Agent工作流和进行Prompt Diff。
- 建立统一的Trace查询门户,能够通过Trace ID、业务标签、时间范围、评估分数等多维度筛选Trace。
告警与联动:
- 基于第一层指标(错误率、P99延迟、token消耗速率)设置基础告警。
- 更高级的是,基于评估分数设置告警。例如:“过去10分钟内,‘合同审核’场景的平均准确性评分低于0.8”触发告警,直接关联到对应的低分Trace列表,供工程师立即排查。
5.3 一个完整的排错案例
假设收到告警:“VIP用户会话转人工率上升20%”。
- 定位:在可观测平台,筛选过去1小时,标签为
user_segment:vip且business_outcome:escalated_to_human的所有Trace。 - 洞察:发现这些Trace中,有一个共同的模式:在“查询知识库”步骤后,LLM调用的耗时异常高(>10秒)。
- 深入:打开其中一条高耗时Trace,进入第二层视图。发现知识库查询返回了极大量的上下文(超过1万token),导致后续的LLM合成调用触发了模型的输入长度限制,响应缓慢且质量下降,最终用户不满转人工。
- 根因:Prompt Diff对比正常Trace,发现是知识库检索的相似度阈值(similarity_threshold)被一个错误的配置更新为了0,导致返回了过多不相关文档。
- 解决:修复配置,并增加一个监控项:知识库查询返回的上下文token数。设置阈值告警,避免未来类似问题。
这套流程,将原本需要跨多个系统、手动拼接日志的排查工作,变成了在统一平台上的几次点击和筛选,效率提升是数量级的。
三层可观测性体系,不是一个一蹴而就的项目,而是一个需要持续建设和运营的工程实践。它的回报是巨大的:它让LLM应用从“黑盒魔法”变成了“白盒工程”,让团队能够自信地迭代Prompt、优化流程、定位故障,最终构建出稳定、可靠、可信任的AI产品。开始行动的最佳时间,就是现在。从一个核心场景入手,先实现最基本的LLM调用Trace,你就会立刻感受到那种“一切尽在掌握”的踏实感。
