☰
RAG系统全链路观测:用LangSmith构建可观测性监控方案
2026/10/8 10:59:04 网站建设 项目流程

1. 为什么说RAG系统需要一台"心电监护仪"

我接手过一个典型的RAG项目:知识库文档是新的,向量库也重建过,检索链路看着一切正常,但用户反馈就是"答案越来越不对劲"。不是报错,不是超时,是那种——检索到了文档、也生成了答案,但答案里引用的内容跟问题压根对不上号的"慢性病"。这种问题最折磨人,因为系统表面上是健康的,你甚至拿不出一个报错截图去说服自己"确实出故障了"。

传统监控手段在这时候几乎全部失效。APM能看到延迟、QPS、CPU占用,但看不到"语义层面"的劣化;日志能记录每次检索请求的参数,但记录不了"为什么这个query在向量库里召回了一批不相关的chunk";指标监控更不用提,它对"答案质量下降"这种渐进式劣化完全没有感知。做过RAG的人应该都有同感:这玩意儿的故障链路比普通API长得多,从Embedding到向量召回、从重排到Prompt组装、从LLM生成到引用溯源,每一环都可能带病工作,而传统监控恰好只覆盖了最外层那一点。

这就是我给RAG系统引入全链路观测的动机。说白了,我需要一台能实时看到"心跳、血压、血氧"的设备——不是等到系统彻底宕机才报警,而是在检索质量开始波动、token消耗开始异常、某条链路延迟悄悄升高的时候就给出信号。而目前这个领域里,LangSmith是做得最成熟、上手成本最低的方案之一,这篇文章就先讲讲它的基础能力,以及我实际踩过的坑。

2. LangSmith到底观测了什么:trace的基本功

LangSmith的本质是一个可观测性平台,但它观测的东西跟普通APM有本质区别。普通APM观测的是"系统健康度",LangSmith观测的是"链路语义质量"。这句话怎么理解?

2.1 一次请求被拆解成trace与span

在LangSmith的世界里,一次完整的RAG请求对应一个trace(追踪),trace内部包含多个span(片段)。每个span代表链路中的一个具体步骤:Embedding模型调用、向量库检索、重排序、LLM生成、工具调用等等。LangChain框架在运行时自动完成切分和记录,不需要你在业务代码里手动埋点。

我对LangChain之外的框架没太深入用过,但身边有同事在LlamaIndex里也接入了LangSmith,原理一样,只是初始化方式略有差异。核心机制是一致的:框架层面统一拦截每一次模型调用和检索调用,把入参、出参、耗时、token用量全部结构化写入trace。

每个span一般会记录这些内容:

  • 输入数据:比如检索环节的query原文、LLM环节的完整Prompt
  • 输出数据:检索环节召回的chunk列表及得分、LLM生成的完整文本
  • 元数据:模型名称、温度参数、token用量、耗时、模型返回的finish_reason
  • 运行状态:成功、失败、被取消,失败原因会和报错堆栈一起透传

你可能会问:这不就是日志?区别在于,日志是散落的点,trace是串起来的线。单独看一条日志你只能知道"这步发生了什么",而看一条完整的trace你能还原整个决策过程——为什么最终答案长这样,是检索环节出了问题还是生成环节的理解偏差。

2.2 观测链路和业务链路的映射关系

LangSmith一个很聪明的设计是:它不强迫你改变代码结构,而是顺着LangChain的抽象自动建立trace。LangChain本身把RAG链路抽象成Chain、Retriever、LLM、Tool这几种基础组件,LangSmith就把这些组件一一映射为span类型。

我实际项目的链路长这样:Retriever环节调用Embedding模型做向量化,再查向量库召回Top-K文档,然后是Reranker重排,接着把重排结果和原始query组装成Prompt,最后交给LLM生成。在LangSmith面板上,这条链路对应一串一目了然的span序列:Embedding调用、向量库查询、重排器调用、Prompt组装、LLM调用。谁慢、谁贵、谁出错,展开那一层就看到了。

这套映射机制的价值在于:它给"排查RAG问题"提供了一个标准的锚点。之前排查问题靠猜——先看向量库有没有数据,再看Prompt拼得对不对,最后怀疑模型参数,每换一个怀疑对象就要改一次代码加日志。现在不用猜了,直接看trace,链条上哪个环节的数据不符合预期,一眼就能锁定。

3. 30秒接入:从LangSmith配置到第一条可用trace

接入LangSmith的成本比我预想的低得多。前提是你已经在用LangChain来搭建RAG链路,如果你是自己手写的Embedding和检索调用,没有LangChain这一层抽象,前置工作量会大不少。

3.1 环境变量配置与基础初始化

LangSmith的接入核心就是设置环境变量,官方推荐三个:

export LANGSMITH_TRACING=true export LANGSMITH_API_KEY=ls__你的密钥 export LANGSMITH_PROJECT=rag-demo

第一个变量是开关,第二个是身份凭证,第三个是项目名(在LangSmith控制台里区分不同应用)。设置完这三个变量之后,只要你用的是LangChain的API,比如ChatOpenAI、OpenAIEmbeddings、Chroma这些,框架会自动把调用记录下来,发到LangSmith后端。

一个容易忽略的细节:LANGSMITH_TRACING这个变量在较新的LangChain版本里默认就是true,不设也行。但如果你用的是老版本,或者项目里混用了其他框架,最好还是显式设一下,避免出现"代码跑完了但面板上一条trace都没有"的情况。

另外在macOS上有个挺烦人的问题:终端会话的export不会永久生效。你开个新终端窗口,环境变量就没了,然后代码里LLM调用正常,但trace就是传不上去。我在Mac本上折腾了十分钟才想起来是这个原因,后来直接把三个变量写进了~/.zshrc(如果你用的是bash就写~/.bash_profile)。更稳妥的做法是放进项目的.env文件用dotenv加载,这样团队成员clone代码后一条pip install python-dotenv加一个.env就能启动。

3.2 跑通一条最小链路

初始化环境变量后,跑一个最简单的RAG demo验证:

import os from langchain_openai import ChatOpenAI from langchain_core.runnables import RunnablePassthrough from langchain_core.prompts import ChatPromptTemplate # 环境变量已在外部配置好 llm = ChatOpenAI(model="gpt-4o-mini") prompt = ChatPromptTemplate.from_template( "你是知识库问答助手,请根据{context}回答:{question}" ) chain = prompt | llm resp = chain.invoke({ "context": "LangSmith是LangChain官方的可观测性平台,支持全链路追踪。", "question": "LangSmith是什么?" }) print(resp.content)

这段代码跑完后,登录LangSmith控制台,找到你设置的project,正常情况下会看到一条trace。展开这条trace,你会看到两个span:一个Prompt组装(或叫Chain span),一个LLM调用。每个span右侧都展示着完整的输入输出和token消耗。

我第一次看到这个结果时的真实感受是:这比我之前在代码里print大半天Prompt要直观太多了。原来为了调试一个"LLM回答不按格式来"的问题,我得在代码里显式打印Prompt和response,确认Prompt没拼错、再确认模型确实收到了。现在直接打开trace面板,Prompt里写了什么、模型回了什么,清清楚楚,根本用不着碰代码。

3.3 手动追踪:不依赖LangChain包装器的情况

有一种情况是必须手动追踪的:你的链路里包含自研组件。比如自研了一个rerank服务、或者自己实现了一套混合检索逻辑,这些代码不在LangChain的组件体系里,框架不可能自动记录。

LangSmith为此提供了@traceable装饰器(或者LangChain的langchain_core.callbacks),可以给自定义函数加观测。效果是在trace里新增一个span,你指定它的名字、类型,还可以往里塞元数据:

from langsmith import traceable @traceable(run_type="chain", name="hybrid_retrieve") def hybrid_retrieve(query: str): # 自研的混合检索逻辑:BM25 + 向量召回再融合 bm25_results = keyword_search(query) vector_results = vector_search(query) return fuse_results(bm25_results, vector_results)

这个装饰器在纯非LangChain项目中同样可用,算是一个很好的"渐进式接入"方案。我的建议:别追求一步到位把整个链路的span都规划好,先从最核心的检索和生成环节开始,跑通之后再加中间环节也不迟。

4. RAG全链路观测点逐一拆解:每个环节看什么、依据是什么

接入只是开始,真正值钱的是能看穿链路里每一步的健康状况。我拆开讲一下在LangSmith里每个RAG环节具体观测什么,以及什么指标预示可能有隐患。

4.1 检索链路:召回文档的质量与分布

检索环节的span是最值得盯的。这里有三个核心观测点:

召回文档的score分布。常见向量库召回时会返回相似度或距离分数,LangSmith会在span里透传检索结果。如果Top-5文档的分数差别很大(比如最高分0.82、第二名直接掉到0.61),说明检索器对主文档非常自信,但备选文档可能都不可靠,这种时候答案很容易受杂音影响。

召回文档的chunk粒度。trace里能看到每个召回chunk的文本片段。很多RAG质量问题的根源就藏在chunk粒度上——切得太碎则每个chunk信息不完整,切得太粗则一个chunk里混杂了多个主题。在trace里扫一眼召回chunk的文本长度和内容边界,比在配置里盲调chunk_size高效得多。

检索延时。向量检索本身通常在毫秒级,但如果你的数据量大、且没做分区过滤,检索延时会慢慢爬升。LangSmith的trace会自动记录每个span的耗时,你可以按耗时排序,快速定位链路里最慢的一环。

4.2 生成链路:Prompt输入与模型输出的质量验证

生成环节的span在trace里通常是信息量最大的那一个,因为输入输出的原始数据都有。但要说一下,在LangSmith的trace里,我们能看到输入给模型的完整Prompt,这个巨大的价值在于可以做"输入侧归因"。

早期RAG项目遇到"答非所问"的常见原因之一是Prompt里提供了太多无关上下文,把模型带偏了。这个问题在传统排查方式下非常隐蔽,因为代码里你看着Prompt结构觉得挺合理,但运行时真正拼出来的文本里,无关chunk的比例可能已经高到干扰模型判断。有了trace,你把几次坏的回复对应的Prompt展开看一遍,一眼就能发现"这个Prompt的context里至少有两条是跟问题毫不相干的文档片段",后面就该去修检索而不是反复调LLM温度参数。

Token消耗观测同理。trace里精确记录了每次LLM调用的输入token数和输出token数。我踩过一个教训:为了让模型"更严谨",在Prompt里加了大量约束性描述,结果每次请求消耗的token数直接从3K涨到8K,成本翻了快三倍,但回答质量几乎没有可感知的提升。当时还以为是用量上涨的正常波动,后来看trace才定位到是Prompt模板膨胀导致的。

4.3 Agent工具的调用轨迹与参数合理性

如果你的RAG系统带Agent能力(可以调用额外工具,比如搜索、查数据库、调用业务API),LangSmith会把工具调用的输入参数和输出结果记录在子span里。

我常用这个功能来排查"工具调用幻觉"——就是LLM构造出了根本不存在的参数值。比如一个查询工具有个region参数,模型实际传了个"湖南",但业务系统里根本没这个值,工具调用就会失败。如果没有观测链路,这类问题只能从业务系统日志里反查,有了trace就能看到模型的完整决策路径:模型在哪个步骤决定去调工具、传入参数是什么、工具返回了什么错误、模型之后又如何基于这个错误结果继续生成。

4.4 引用溯源:答案可信度的唯一依据

面向用户的RAG系统,引用溯源能力几乎决定产品可信度。LangSmith不直接帮你生成引用标注,但通过trace里的检索结果和生成结果对照,你能验证"模型声称引用的信息和实际召回chunk是否对应"。

这个检查我会在测试阶段专门做:拿一批测试问题跑完,逐个trace看"模型生成的答案引用了文档A的某句话,而代码传给模型的context里根本没有文档A"。如果出现这种情况,说明引用生成逻辑有问题——大概率是模型自己编造了引用,它在输出格式里写了[1]之类的标注,但对应的chunk压根不在context里。这种情况在传统视角下很难被发现,因为答案看起来是有依有据的。

5. 那些官方文档不会写清楚的注意事项

整个模块跑通是第一步,实际用起来一个月之后才发现一些文档不会提示的细节。我把最实用的几条列出来。

5.1 Project隔离与端到端追踪

LANGSMITH_PROJECT这个变量一定别所有环境共用一个值。测试环境和生产环境混在一起,trace量会互相污染,最后想单独看生产环境的检索质量都无从下手。我现在的做法至少维护三个Project:dev、staging、prod,环境变量区分开,生产环境的trace保留时间也调得比开发环境更长,方便回溯。

5.2 网络代理与超时问题

LangSmith发送trace数据走的是普通的HTTPS请求,如果你的服务器在受控网络环境里,通常不需要额外配置代理就能通。但如果你为了性能给LangChain的调用设置了很短的超时时间,可能会把trace上报也给掐断——因为这个上报是在同一个执行环境中异步进行的。我遇到过一次:ChatOpenAI请求超时设为2秒,结果LLM调用本身成功了,但trace没传上去。排查半天发现是超时配置太激进,把后台上报链路也截断了。建议给tracer单独设置超时和重试配置,不要让业务链路的超时设置波及它。

5.3 隐私与敏感数据过滤

LangSmith会默认记录完整的输入输出,包括Prompt里的业务数据。如果你的知识库含客户信息、内部策略文档,需要提前配置数据脱敏规则。我见过一个团队接完了之后发现所有用户查询都完整传去了第三方平台,立刻被安全部门叫停。给你两个建议:一是配置LangSmith的datasets_whitelist级别的过滤,二是对敏感字段在代码里做截断或者替换再传回,比如在自定义traceable函数里只记录拼接后的摘要文本。

5.4 采样率控制

生产环境不是每条trace都要全量上报的。LangSmith支持采样率配置,把上报比例从100%降到10%,再看链路最长尾和错误率分布,其实覆盖了大多数异常检测需求。全量上报会带来不少存储成本,同时面板也会被大量低价值trace淹没,反而干扰你发现问题。

6. 从"能看到"到"能判断":观测数据的下一步价值

LangSmith把链路数据结构化收集起来之后,还有一个我刚摸到门槛的高阶玩法:把观测数据用于回归评估。LangSmith提供了Dataset和Evaluator功能,可以给一系列测试问题预先标注标准答案,每次你改了Prompt、换了Embedding模型、调整向量库参数后,批量跑一轮测试,自动对比"标准答案"和"模型产出"的相关性打分。

这个功能解决了RAG项目里非常头疼的一个问题:RAG系统的改动评估往往靠人工肉眼打分,改一版参数到底变好还是变坏,没有客观标准。以前我只能靠项目组成员轮流去问问题、看回答、打A/B分,费时费力还有主观偏差。有了回归测试集,至少能在我改检索参数后24小时内自动跑一个质量分数出来,我再看差的case是哪些,手动过一遍判断"能不能接受"。

关于Evaluator如何配置、如何用LangSmith的Client创建测试集、怎么设计Prompt评估规则,这些放在下篇展开讲。这篇先确保你把trace接入、看明白、用起来,这是后面所有评估和回归能力的地基。

实际操作中我的体会是:给RAG装观测设备这件事,越早越好。不要等线上出问题了再回头接。如果你现在在做RAG项目、能用上LangChain,花半小时把trace跑通,后面每一次调整参数都会有据可依,而不是靠感觉和运气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询