LangSmith实战:搭建RAG量化评估体系,告别“感觉还行”

发布时间:2026/10/6 22:09:52
LangSmith实战:搭建RAG量化评估体系,告别“感觉还行” 做过RAG项目的朋友应该都有过这种体验本地跑demo的时候随手扔几个问题进去回答像模像样心里想着这玩意儿稳了。结果一上线真实用户的问题五花八门同一个知识点换个问法就答得牛头不对马嘴。你这时候才意识到感觉还行这四个字是RAG开发里最虚的评价。我从去年开始系统性接触LangSmith最初只是拿它当链路追踪工具看看报错后来慢慢把整套RAG评估流程搬了上去才真正体会到什么叫数据说话。这篇文章就把我在LangSmith上搭建RAG量化评估体系的完整过程、踩坑记录和落地经验分享出来特别适合那些已经在做RAG应用、但评估方式还停留在人工抽几条问题看看回答质量阶段的团队。1. RAG评估的现状感觉还行背后的隐患1.1 为什么人工抽检和直觉判断撑不住RAG项目先说一个很典型的场景。你搭好了一个知识库问答系统检索用向量数据库生成用大模型Prompt调了三轮问了几个自己觉得有代表性的问题回答都挺完整。你觉得可以交付了结果业务方转头给你丢来一个真实用户问题——表述口语化、夹杂着专有名词的俗称、甚至还有几个错别字——系统一下就露馅了。这不是个例是RAG开发中几乎必然遇到的坎。原因在于人工抽检有几个天然局限覆盖样本太少。一个知识库可能有上万条文档片段你手工测试时最多覆盖几十个问题而且这些问题是你在写系统时就已经知道答案的舒适区问题根本代表不了真实用户的长尾提问。评判标准不统一。你上午觉得回答完整就行下午改完Prompt觉得必须给出处才行标准一直漂移你怎么知道这次改动是真的变好了还是变坏了无法做回归对比。这可能是最要命的。你优化了检索器把Top-K从4调到了6然后发现某几个问题的回答确实更详细了但你没注意到另外几个问题的回答开始答非所问。没有系统性的回归你根本发现不了这种退化。我见过不少项目死在最后一步系统修修补补上线了各种小问题不断但谁都说不清楚问题集中在哪一层——是检索层没把相关文档捞出来还是生成层没用好检索到的上下文这种模糊状态会让团队陷入无休止的Prompt微调而且越调越迷茫。1.2 量化评估到底在量化什么很多人一听量化评估就想当然地以为是算准确率——回答对了几道题错了几个。但RAG系统比单纯的分类或抽取任务复杂得多一个答案好不好至少包含几个不同的维度它是否忠于检索到的上下文它是否确实回答了用户的问题检索模块有没有把需要的文档找出来回答链路花了多长时间、消耗了多少TokenLangSmith的RAG量化评估做得比较聪明的一点就是把这几个维度拆开来看而不是糊成一个总分。后面我会详细拆解这套指标体系。先把一个观念摆正量化评估的目的不是给你一个系统得了88分的好看数字而是让你在改动任何一环——换Embedding模型、调检索参数、改Prompt——之后都能精确回答这个改动到底让哪方面变好了、让哪方面变差了。2. LangSmith为什么适合做RAG评估2.1 从链路追踪到完整评测LangSmith的定位LangSmith是LangChain生态推出的可观测性平台我最早用它是为了看日志——每次调用LLM用了多少Token、耗时多少、哪一步报错了。后来发现它不只是个日志面板而是一套完整的LLM应用研发运维基础设施。它对RAG项目尤其有价值的地方在于RAG链路天然是分层的。用户输入进来先经过检索器从向量库捞文档然后拼进Prompt喂给大模型最后输出答案。这条链路里任何一个环节出错最终答案都会受影响。LangSmith的Trace功能能把这条链路的每一步——从输入、检索到的文档列表、Prompt拼接结果、LLM的原始输出——全部记录下来。这些Trace数据就是量化评估的原材料。你评估一个回答好不好不能只看最终输出还得看检索阶段到底返回了什么。没有这种可观测性你说答案不好都说不清楚是哪一步的锅。2.2 评估器的设计哲学不迷信单一模型的打分LangSmith的Evaluator评估器体系设计得比较务实。它不强迫你用某一种固定的评估方式而是提供了一套组合框架基于规则的评估器。比如判断输出是否包含特定字段、是否匹配正则表达式、是否在禁词列表里。这类评估器速度快、零成本适合检查格式类问题。基于LLM的评估器。让一个独立的LLM充当裁判根据你定义的评分标准给输出打分。这是目前RAG质量评估的主力因为回答质量本身是强语义判断规则写不出来。混合方式。先跑规则检查再用LLM打分最后人工抽检异常样本。我踩过的第一个坑就在这里一开始我用了一个LLM评估器打天下结果发现它有时候对答案相关性打分很飘——同一个答案换个Prompt措辞分数能从8分掉到5分。后来我改成LLM打分规则过滤异常值的组合情况才稳下来。LangSmith另一个做得好的点是评估器可以配置成带标准答案的评估和不带标准答案的评估两种模式。带标准答案的比较直观——拿模型输出和人工标注的参考答案比。不带标准答案的则纯粹靠语义判断比如忠实度评估判断输出是否被检索到的上下文支撑。这两种模式对应不同的评估场景后面实操部分我会具体演示。3. 拆解RAG量化评估的四个核心维度LangSmith社区里关于RAG评估的指标讨论得很多我结合自己在项目里的实际使用整理了四个最核心的维度。这四个维度不是互相替代的关系而是从不同角度约束同一个答案的质量。3.1 答案正确性和忠实度输出层两个侧面的区别**正确性Correctness**衡量的是答案内容是否符合事实。通常情况下你需要一个参考答案Ground Truth让评估器对比模型输出和参考答案的语义一致性。这个指标非常直观但也比较死板——如果用户提问的角度比较刁钻参考答案没覆盖到模型给出了一个合理但表述不同的答案正确性分数就会偏低。**忠实度Faithfulness**衡量的是答案是否忠于检索到的上下文它不关心答案是否与外部事实一致只关心答案有没有使用上下文里根本不存在的信息。这个维度对RAG特别重要因为LLM天然倾向于编造——哪怕上下文里没提到它也会顺着逻辑把话圆下去。忠实度低的答案表面上流畅完整实际是用幻觉填充的。正确性和忠实度经常被混淆但它们的业务含义完全不同。正确性低可能有两个原因一是检索到的上下文本身是错的二是生成层无视了正确的上下文自己发挥了。忠实度低则直接指向生成层的幻觉问题。区分开它们你在做优化定位时才能有的放矢。3.2 上下文相关性和检索质量指标定位问题在哪一层如果说正确性和忠实度是在看输出结果那**上下文相关性Context Relevance**就是在看输入原料。它评估检索模块返回的文档片段到底有多少和用户问题是真正相关的。我见过一个非常典型的案例用户问这款产品的退货政策Embedding检索出来的结果有产品介绍页面、FAQ、售后条款但排序第一的结果居然是一个产品参数表。这种事在向量检索里太常见了——语义相似的文本不等于能回答问题。上下文相关性低意味着检索层出了问题你优化Prompt是没用的得去调检索策略。在LangSmith的评估体系里你还可以叠加一些经典的信息检索指标来量化检索层的表现指标含义适用场景RecallK前K个检索结果中相关文档的覆盖率判断检索器能否找到该找的文档MRR第一个相关文档排名的倒数均值判断最相关文档是否排在前面Hit Rate前K个结果中至少包含一个相关文档的查询占比粗粒度判断检索器是否够用3.3 工程化指标延迟、成本与Token消耗很多RAG评估教程只讲质量指标但在我实际项目中工程化指标往往决定了一个RAG系统能不能上线。你有两个选择一个是检索精度稍微差一点但响应只要1秒的方案一个是质量更高但每次回答要等5秒还贵三倍的方案。业务方几乎都会选第一个。LangSmith在Trace里天然记录着每次运行的延迟和Token消耗你可以在仪表盘里按时间维度聚合也可以在评估结果里把它们当成附加指标一起看。我建议每个RAG项目都给自己定一个成本预算——比如单次问答平均不超过X个Token、P95延迟不超过X秒超了就当成评估失败来处理哪怕答案质量得分再高。这里顺便说一句这几个评估维度在LangSmith里是可以自由配置权重和组合的你可以针对不同业务场景定义不同的评估标准。客服场景可能更看重忠实度和延迟知识库问答可能更看重正确性和上下文相关性没有一套万能指标只有适合你的指标。4. 实操落地在LangSmith上搭一套完整的RAG评估流程4.1 准备一份能打的评估数据集很多人在这一步就草草了事——从训练文档里抽几个问题扔进去就完事。我建议评估数据集的构建多用点心因为它是整套评估体系的基石。一份能打的评估集应该包含真实用户问题。如果你已经有线上日志抽一部分代表性的用户提问这比你自己想的问题有价值得多。边界情况与变体。同一个知识点至少准备两种不同的问法——正式问法、口语化问法甚至包含一个带歧义的问法。RAG系统对问题表述的变化极其敏感我在测试中就发现系统对怎么退钱理解得很好但换个钱怎么回来就直接检索失败。答案标注。如果做带标准答案的评估就得为每条问题人工写参考答案。这个工作很费人力我的建议是第一批样本控制在50到100条覆盖知识库的各个主要模块不要贪多质量优先。LangSmith支持从CSV、JSON或LangSmith数据集实例导入评估样本。我习惯用JSON格式每条样本包含question用户问题和reference_answer参考答案如果还希望测检索质量就额外加一个expected_docs字段标注应该检索到哪些文档。4.2 最小化评估脚本从Tracing到Evaluating这是整套流程的核心环节。我用一个常见的LangChain RAG链路为例展示如何在LangSmith上完成从追踪到评估的全过程。项目环境里需要安装langchain、langsmith、langchain-openai这几个Python包另外配好环境变量打开LangSmith追踪功能import os os.environ[LANGCHAIN_TRACING_V2] true os.environ[LANGCHAIN_API_KEY] 你的LangSmith_API_Key os.environ[LANGCHAIN_PROJECT] rag_eval_demo接着构造一个最基础的RAG链路也就是向量检索加LLM生成的标准组合。这里我的检索器用OpenAI的Embedding接口和FAISS向量库你也可以换成任何你实际项目里在用的组件LangSmith的追踪能力对各种检索方案都适用不强绑LangChain里的某一种from langchain_community.vectorstores import FAISS from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore FAISS.load_local(your_index, embeddings, allow_dangerous_deserializationTrue) retriever vectorstore.as_retriever(search_kwargs{k: 4}) prompt ChatPromptTemplate.from_template( 请根据以下上下文回答问题。 如果上下文中没有相关信息请明确说明上下文中未找到相关内容不要自行编造。 上下文 {context} 问题{question} ) llm ChatOpenAI(modelgpt-4o-mini, temperature0) def build_rag_chain(): def retrieve_and_generate(question: str) - dict: docs retriever.invoke(question) context_text \n\n.join([d.page_content for d in docs]) answer (prompt | llm | StrOutputParser()).invoke({ context: context_text, question: question }) return { answer: answer, retrieved_docs: [d.page_content for d in docs], context: context_text } return retrieve_and_generate rag_chain build_rag_chain()上面的函数已经把检索到的文档列表和最终答案都封装在返回值里了这个结构对我们做评估非常重要因为后续评估器不仅能拿到最终答案还能拿到模型到底看到了什么上下文这样才有办法分别判断检索质量和生成质量。然后从LangSmith的数据集里读取测试样本逐个跑链路把结果凑成评估所需的格式。再定义几个评估器正确性评估器需要标准答案做参照忠实度评估器只需要把模型输出和上下文交给裁判模型就行上下文相关性评估器则把检索文档和用户问题交给裁判模型。LangSmith的RunEvaluator体系允许一次性挂载多个评估器跑完自动汇总from langsmith import Client from langsmith.evaluation import evaluate, LangChainStringEvaluator, RunEvaluator from langsmith.schemas import Example, Run from typing import Optional client Client() # 1. 基于LLM的正确性评估器拿模型输出和参考答案对比 correctness_eval LangChainStringEvaluator( qa_correctness, config{ llm: llm, criteria: 比较模型输出与参考答案的语义一致性输出0到10的分数 } ) # 2. 基于LLM的忠实度评估器判断答案是否被检索上下文支撑 class FaithfulnessEvaluator(RunEvaluator): def evaluate_run(self, run: Run, example: Optional[Example] None) - dict: inputs run.inputs outputs run.outputs question inputs[question] answer outputs[answer] context outputs[context] judge_prompt f你是RAG评估专家。请判断以下回答是否忠于所提供的上下文。 上下文 {context} 用户问题{question} 模型回答{answer} 请用10分制给忠实度打分。如果回答中存在上下文未提及的幻觉信息请给出低分如果完全基于上下文作答给8-10分。 只输出JSON格式{{score: 分数, reason: 简短原因}} import json resp llm.invoke(judge_prompt) parsed json.loads(resp.content) return {key: faithfulness, score: parsed[score] / 10, comment: parsed[reason]} # 3. 检索质量评估器根据expected_docs计算Hit Rate和Recall class RetrievalQualityEvaluator(RunEvaluator): def evaluate_run(self, run: Run, example: Optional[Example] None) - dict: expected set(example.outputs.get(expected_docs, [])) retrieved set(run.outputs[retrieved_docs]) if not expected: return {key: hit_rate, score: 0, comment: 无期望文档标注} hit 1.0 if expected retrieved else 0.0 recall len(expected retrieved) / len(expected) return { key: retrieval_quality, score: (hit recall) / 2, comment: fhit_rate{hit}, recall{recall} }跑评估之前最后一步是确保我们传给评估器的Run对象能拿到完整的输入输出。这里有个我自己踩过的坑如果你的RAG链路封装成了内置函数LangSmith只会记录顶层的那次调用内部我们自定义的返回字段未必会写进example的预期输出里导致自定义评估器拿到的字段是空的。只要在跑测试的时候不需要额外处理主要靠链路本身被追踪后LangSmith会记录输出字典但在离线从数据集批量跑的时候我们需要让运行结果显式带上answer、context、retrieved_docs这几个Key否则后面的自定义评估器会取不到值。下面是执行评估的完整逻辑它会遍历数据集里的每一条测试用例、跑一次RAG链路、再把结果和三名评估器挂在一起执行评分from langsmith.evaluation import evaluate dataset_name rag_qa_dataset def run_rag_case(example: dict): question example[question] result rag_chain(question) return { answer: result[answer], context: result[context], retrieved_docs: result[retrieved_docs] } evaluation_results evaluate( dataset_namedataset_name, llm_or_run_fnrun_rag_case, evaluators[ correctness_eval, FaithfulnessEvaluator(), RetrievalQualityEvaluator() ], experiment_prefixrag_eval_first_run, metadata{note: 基线评估chunk_size600, top_k4} )evaluate函数返回的结果里包含了每条样本在每个评估器上的得分还有整体汇总。第一次跑完的数据我强烈建议你原封不动地存成一个baseline实验——后面每一次改动都要跟这个baseline比否则量化评估就失去了意义。4.3 基线、阈值与实验对比我第一次跑完基线评估看到仪表盘上那个Correctness: 7.2 / Faithfulness: 8.1 / Context Relevance: 6.4的时候第一反应是这分数还行。然后我随手点开一条Context Relevance得了3分的样本才发现检索器返回的4篇文档里只有1篇和问题真正相关其余3篇全是同知识库里的长尾噪音。这就是基线的价值——它让你看到你以为还行和实际数据反馈之间的差距。设阈值也是一门学问。我目前的建议是先定质量底线再定优化目标。比如忠厚度低于6分的答案必须视为不可接受——这意味着模型在幻觉正确性低于7分视为需要人工复核。底线用于拦截上线目标用于指引优化方向。每个团队业务要求不同、数据分布不同没有统一标准但你要确保自己对哪个分数到底意味着什么有体感否则评估分数就只是一个悬浮的数字。在实验管理上LangSmith的Experiment功能非常好用。每跑一次评估系统自动生成一个实验记录包含所用数据集、评估器配置、每条样本的得分。你可以在同一个数据集上跑不同Prompt模板或不同检索Top-K的对比实验系统会把两次实验的分数差异按样本级别列出来哪几个问题变好了、哪几个变坏了一目了然。5. 数据解读与优化闭环评估结果不是终点5.1 按维度拆解失败案例的分布评估跑完如果你只看一眼平均分就关掉控制台那这套体系基本白搭。我每次跑完评估做的第一件事永远是把得分最低的Top20样本拉出来按评估维度分类。我对一个具体项目的评估结果做过一次完整的失败案例归因分析用LangSmith导出了全量样本和分数按正确性低忠厚度低上下文相关性低三个维度做了分类得到的分布是这样的失败类型占比典型表现可能的根因上下文相关性低45%检索结果偏题有效信息没被召回Embedding模型区分度不足、Chunk切分不合理、Top-K太小忠实度低30%上下文有答案但模型自己发挥Prompt约束不严格、参数温度过高正确性低25%上下文本身缺少关键信息模型强行回答知识库覆盖不全、切分后信息被截断这个表一出来优化重点就非常清晰了这个项目的问题主要在检索侧占比将近一半而忠实度问题有相当一部分是因为答案里引用了上下文之外的背景常识——虽然这些常识是对的但业务要求严格限定在给定上下文范围内作答那这种回答就算幻觉。你可能也遇到过类似情况忠厚度低不一定意味着模型在瞎编可能只是它引入了通用知识。这时候你要做的不是换模型而是调整评估标准——比如明确告诉评估器的裁判LLM涉及常识背景的补充不算幻觉只要核心答案忠于上下文即可。评估标准的细化本身就是一次对业务需求的深度梳理。5.2 优化动作与效果验证的对照根据评估结果做优化效果最好的是先修大头。我在上面那个项目里优先做了几个动作优化Chunk切分。把原先按固定字数600字切分的方式改成了按Markdown标题和语义段落切分降低检索召回时把不相关内容拼在一段里的概率。增加Top-K和重排。把Top-K从4提到8外加一个Cohere Rerank重排提高有效文档的召回率。强化Prompt约束。把不得使用上下文之外的任何信息直接写在Prompt里并且用few-shot示例引导模型在信息不足时明确拒绝回答。改完再跑一遍同一份评估集上下文相关性从6.4直接跳到8.2正确性也升到7.9代价是单次查询的Token消耗涨了约40%。这个Trade-off被量化出来之后业务方反而更容易拍板了——他们宁可多花点钱也要先把回答质量提上去。这个例子让我确信量化评估的作用不只是给你一个分数而是把我不知道问题在哪、不知道要不要这样改变成我知道问题在哪、我知道改了以后值不值。没有量化数据业务方嫌你优化太慢有了量化数据你能拿证据说话这其实是研发话语权的基础。6. 踩坑实录LangSmith评估过程中的常见问题与对策6.1 大模型当裁判时的评估分数漂移大模型自己有方差这是不可避免的事实也是用LLM做评估器最大的痛点。我在项目里遇到过评分漂移的情况比如同一个评估样本间隔几小时跑了两次实验忠实度分数差了0.8分两次跑用的还是同一个评估器配置。我后来总结出的对策有三条都是在实际使用中验证过有效的方式每次评估至少并行跑3次取平均。LangSmith支持给同一个评估器配置不同模型或者不同参数的变体你可以在Evaluator里重复挂载几遍取均值。增加的调用成本有限但评估结果稳很多。评估标准写得越具体越不容易漂移。把请对忠厚度打分换成如果回答中出现上下文完全没有提到的事实直接从0分起评如果回答完全基于上下文信息组织给8到10分同样的裁判模型打分方差立刻缩小。异常分数要人工复核。我一般抽每条样本的原始LLM评估理由来看不看分数只看Reason也能识别出一半的评估事故。6.2 数据集污染你的测试集是否干净做评估时间长了还会遇到一个隐蔽但致命的坑数据集污染。为了调模型效果我反复用同一份评估集做实验结果某天发现所有分数都在往上走但我并没有改任何Prompt或检索配置。后来才意识到原因是我悄悄把几个失败样本的参考答案改得更接近理想输出了导致评估器给分变松。这就是用测试集调参数导致的过拟合和机器学习里的数据集污染是同一个概念。防范办法有两个评估集锁定。基线评估集一旦确定除了补充新场景之外尽量不要修改已有样本的答案和标准。定期补充新样本。从线上真实日志里抽新问题加入评估集防止模型在旧测试集上背答案。LangSmith的Dataset里支持版本管理每次改动都会记录历史版本这个功能在这个场景下非常实用。6.3 评估成本与速度的平衡最后说下钱和时间的事。用LLM做评估本质上是用模型的Token换认知能力一个完整RAG评估跑下来100条样本、3个评估器、每个评估器重复3次你大概需要支付100乘3乘3次LLM调用的费用。我用gpt-4o-mini做裁判时的实测成本100条样本评估一轮大约是0.5美元左右如果换成gpt-4o成本会跳到3到5美元。建议线上Pipeline用迷你版模型评估发现异常后再用大模型复核。速度方面如果你把评估配置成一键跑完拖住开发流程的概率很高。我现在的工作流是本地开发时用20到30条子集评估2分钟出结果作为快速回归信号。每次重要改动上线前全量100到200条评估拿到完整的基线对比报告。线上定期抽查用真实用户日志中的新样本每周跑一次评估监控模型悄悄退化的情况。写在最后的一点体会LangSmith这套体系我前前后后用了大半年最大的收获不是学会了几个API而是改变了整个团队的协作方式。以前讨论RAG问题大家说我感觉检索不太行这个回答好像变差了这种对话注定是公说公有理谁也说服不了谁。现在我们在LangSmith上建立统一数据集、统一评估标准、统一定义打分维度一切用跑出来的数据说话连和业务方对接都顺畅了很多。如果你恰好也卡在RAG效果全凭感觉这个阶段我的建议是先别急着优化系统赶紧把评估体系搭起来。跑上第一轮基线哪怕只是50条数据收获也会远超你现在的直觉判断。希望这篇分享能帮你少踩一些坑顺利从感觉还行走到数据说话。