
做 RAG 应用快两年了最让我头疼的从来不是模型选型也不是向量库调优而是根本说不清楚系统到底哪里出了问题。用户说回答不对你问他怎么个不对法他说就是感觉不对。你翻日志、查 prompt、调参数忙活一整天最后还是靠猜。后来我把 LangSmith 用起来才算是把这条链路从黑盒变成了透明再从透明升级到自动把关。这篇就聊聊我怎么从零开始接链路追踪又怎么一步步把它变成一套 RAG 自动化评估体系的。1. 链路追踪到底在追什么先弄懂 LangSmith 的工作机理很多朋友一上来就急着看 UI、看 span 列表其实没搞清楚 LangSmith 的底层逻辑后面排查问题就很容易被界面上的信息带偏。1.1 一次 RAG 请求在 LangSmith 里是怎样被拆解的LangSmith 的核心概念其实只有几个Run、Span、Trace。一次用户请求进来LangSmith 会把它记为一条Trace里面嵌套若干个Run。以最常见的 RAG 流程为例一个Trace大致长这样RetrieveRun查向量库 └── QueryEmbeddingRun问题向量化 └── VectorStoreSearchRun相似度检索 └── DocumentPostProcessRun重排/截断 GenerateRun模型生成 └── PromptBuildRun拼 prompt └── LLMInvokeRun调用大模型每条Run都会记录输入、输出、耗时、token 消耗以及自定义的元数据。真正好用的地方在于你可以给任意一个Run挂上你自己关心的指标比如检索结果里到底有几个是真正相关的最终答案引用了哪几个文档片段。1.2 埋点方式选择自动埋点与手动埋点的适用时机LangSmith 提供两种接入方式很多人不知道该怎么选。我的建议很简单如果用的是 LangChain 框架直接用自动埋点如果你像我一样是手写 RAG 管线老老实实手动埋点。自动埋点的好处是零侵入wrap_openai()或设置环境变量后框架内部会自动生成 trace。但代价是你只能看到框架替你记录的内容想记录检索结果相关性评分这种自定义字段还得额外写代码。手动埋点虽然多写几行但灵活度完全可控。我现在的做法是半自动框架调用用自动埋点自定义逻辑用run_tree手动包一层两边互不干扰。from langsmith import Client, traceable traceable(run_typechain, nameRetrieveDocuments) def retrieve_documents(question: str, top_k: int 5): # 这里可以是任意检索逻辑 docs, scores vector_store.search_with_score(question, top_ktop_k) return {docs: docs, scores: scores} traceable(run_typellm, nameGenerateAnswer) def generate_answer(question: str, context: list[str]): prompt build_prompt(question, context) return llm.invoke(prompt)加了traceable之后函数输入输出会自动出现在 LangSmith 的 trace 详情页里。注意run_type不要乱填chain、llm、retriever、embedding这几个类型对应的 UI 渲染和信息聚合逻辑差很多。1.3 只记录最有价值的信息很多人埋点走极端要么什么都不记要么把所有东西全都塞进去。token 多的请求会让 trace 页面变得极其卡顿而且真正排查问题时反而找不到重点。我习惯在每个 Run 里至少挂上这几类信息- input_question用户原始问题 - retrieval_scores各候选文档的相似度得分 - context_truncation上下文截断策略与实际保留长度 - model_params实际生效的温度、top_p 等参数 - ground_truth_doc_ids这个请求关联的标准答案文档 ID评估阶段用尤其是ground_truth_doc_ids这个字段很多团队会忽略。它能在你回看 trace 时快速判断当时这个答案到底有没有按预期引用正确的文档是后面构建评估集的重要锚点。2. 从 trace 里定位 RAG 的高发事故现场接好埋点后LangSmith 的 traces 页面会开始积累数据。但数据多不代表有价值你得学会在 trace 里快速定位那几类典型问题。我总结了 RAG 应用最高发的四类问题每个都有对应的 trace 特征。2.1 检索召回偏移问题在 Embedding 而不是关键词表现用户在 UI 上问苹果公司的创始人是谁回答却引用了苹果作为一种水果的营养价值。这类问题看 trace 最容易发现。点开RetrieveRun你会发现 QueryEmbeddingRun 生成向量没问题VectorStoreSearchRun 的得分也不算低但返回的文档片段在语义上明显跑偏。问题不在链路而在 Embedding 模型本身对某些领域词汇的区分度不够。排查思路别急着换 Embedding 模型。先在 trace 里对比问题向量与正确答案向量的相似度和问题向量与错误候选向量的相似度。很多时候你会发现两者差距很小这属于模型能力边界问题。此时可以考虑对原始输入做查询改写或者用 Hybrid Search 搭配 BM25把关键词匹配这一路也拉进来兜底。2.2 上下文爆炸还记得自己输出了多少 token 吗表现答案质量没变差但响应延迟从 1.5 秒慢慢爬升到 5 秒以上账单金额也在悄悄变多。查看 trace 里的 GenerateRun重点关注prompt_tokens和raw_input里的上下文长度。这个问题通常不是 Retrieval 阶段查多了而是你塞进去的补充资料太多了。比如我在某个项目里发现开发同学为了提升回答质量在 prompt 里加了行业知识库的 8 个固定条目每个条目都有上千字而 RAG 检索答案只需 1-2 个条目就足够。解决方向有两个一是检索阶段严格控制top_k比如从 5 降到 3配合重排序模型只保留 top 2二是对检索到的文档做相关性截断低于阈值的坚决不放行宁缺毋滥。2.3 幻觉根因追踪回答内容到底引用了哪一句表现答案听起来头头是道但每条论据在原始文档里都找不到出处。LangSmith 的 trace 页有个很实用的功能你可以在GenerateRun的 output 里直接看到最终答案的哪句话引用了哪个文档块。但如果你的 prompt 里没有让模型输出引用标记如 [1][2]这里只会在引用文档列表区域显示检索片的得分看不出具体对应关系。我的习惯是在 prompt 里强制模型按上下文引用的文档序号标注答案比如说回答时请基于提供的上下文并在句子末尾使用 [数字] 标注依据的文档序号。这样 trace 里就能看到answer: 苹果公司由乔布斯创立[1]再点开[1]所对文档的实际内容问题是否出在模型自己脑补了内容还是上下文里其实有但模型漏看了一目了然。如果发现答案引用了文档 [2]但 trace 里显示的检索得分很低那就说明检索链路把不相关的内容也放进了上下文回到了 2.1 的范畴。2.4 延迟瓶颈定位耗时到底花在哪一步KPI 飙升的另一大来源是延迟。看 trace 的每个Run耗时分布如果 QueryEmbedding 和 VectorStoreSearch 加起来只占 200msLLM 调用占 3.5s瓶颈在生成环节。此时你该检查是不是 prompt 塞得太长或者温度、max_tokens是否合理而不是去优化向量库。如果 VectorStoreSearch 本身就占了 2s先检查是不是没走 ANN 索引而是全表扫描或者自定义过滤条件太多导致索引失效。这些在常规日志里很难定位但在 trace 里一眼就能看出来。3. 从看一眼到有标准用 LangSmith 沉淀真实的评估数据集链路追踪解决的是出问题时快速定位但如果每次都要人肉看 trace团队规模一大还是扛不住。更关键的问题是你怎么知道这次改动是变好了还是变坏了要回答这个问题就得把评估从人看变成自动跑这是 LangSmith 最有价值的部分也是标题里自动化评估的真正含义。3.1 数据集从哪来直接复用线上真实请求LangSmith 里可以直接把某条 trace 创建为数据集样本。这个功能实用程度远超想象。具体操作在 trace 详情页选一条有代表性的回答点击添加到数据集LangSmith 会自动把输入问题抓下来。你可以顺手在outputs里补上 ground truth 答案或相关文档 ID。我个人的做法是每条样本至少包含三项内容——输入问题、标准答案或标准引用文档 ID、期望的拒绝行为可选。from langsmith import Client client Client() dataset client.create_dataset( dataset_nameRAG-Eval-Regression-Set, description面向核心业务场景的 RAG 回归标准集 ) client.create_examples( dataset_iddataset.id, inputs[ {question: 我们产品的退款政策是什么}, {question: 对接 API 时出现 401 错误怎么解决} ], outputs[ {answer: 退款政策见帮助中心条款 3.2 节支持 7 天无理由退款。, doc_ids: [faq-3.2]}, {answer: 401 错误通常由 API 密钥无效或过期导致详见对接文档第 4 节。, doc_ids: [api-doc-4]} ] )数据集不需要一开始就做到几百上千条。我通常从真实用户反馈中挑 30~50 条最典型的再让运营同学补充一部分边角场景的毒问题比如意图刁钻、含敏感词的基本就能撑起第一版回归集。3.2 评估器的选择谁来判断这个答案好不好有了数据集接下来是定义评估逻辑。LangSmith 支持两类评估器LLM-as-Judge和Heuristic就是规则判断。很多初学者会陷入一个误区只选一个 LLM 评估器然后让它判所有维度。结果就是评分忽高忽低没法解释。我建议做组合拳评估维度评估器类型说明答案正确性LLM-as-Judge用强模型对比标准答案判断语义一致性忠实度/幻觉检测LLM-as-Judge判断答案内容是否都能从给定上下文中找到依据答案引用率Heuristic检查答案中的引用标记是否都指向真实存在的文档片段检索相关性Heuristic计算检索结果中相关文档的覆盖率比如 top-k 命中率回答拒绝率Heuristic对不该回答的问题是否正确触发拒答流程自定义评估器其实就是一个函数输入run和example输出一个字典。LangSmith 会在评估时自动把expected和prediction传进来。下面写一个简单的引用合规评估器from langsmith import evaluate def validate_citations(run, example): answer run.outputs.get(answer, ) doc_ids run.outputs.get(doc_ids, []) # 解析答案中的引用标记 cited_ids extract_citation_ids(answer) invalid [cid for cid in cited_ids if cid not in doc_ids] score 1.0 if not invalid else 0.0 return {key: citation_validity, score: score, comment: f无效引用: {invalid}} evaluate( lambda input: app.invoke(input[question]), # 你的 RAG 入口函数 dataRAG-Eval-Regression-Set, evaluators[validate_citations] )用evaluate跑完LangSmith 会生成一份评估报告按数据集逐条给出分数并汇总成矩阵。这样每次代码改动后你都可以快速跑一遍回归不需要人肉看十几条 trace。3.3 评估数据集要像测试用例一样管理既然叫回归集就得像代码测试一样做版本管理。LangSmith 的数据集支持指定dataset_id和对应version。我建议每次修改评估集都新建一个版本不要在原数据集上直接改新功能发布前先往数据集里加入对应的新用例至少让模型见过这类场景线上出现严重 bad case 后第一时间把它沉淀进数据集再调优而不是修完就完事这样做的好处是三个月后你再回头看能清楚知道这个版本的评估集覆盖了哪些场景遗留了哪些坑。4. 把评估从手工触发变成发布必修课自动化评估的落地细节数据集和评估器都就绪后核心问题是调度。LangSmith 的自动化评估有两种常见玩法我分别说下适用场景和容易踩的坑。4.1 离线回归每次代码合并前自动跑一遍最理想的状态是每次 PR 提出时CI 里自动触发一轮评估。LangSmith 提供了 Python SDK 和 REST API可以很方便地集成进去。我的做法是在 CI 的某个 Job 里拉取最新代码启动一个测试环境然后运行一条评估脚本pytest tests/evaluate_regression.py --dataset RAG-Eval-Regression-Set --threshold 0.85脚本内部核心逻辑是调用 LangSmith 的evaluate()把线上测试 split 跑一遍结束后拿到评估报告。我可以设定一个阈值比如忠实度平均分低于 0.85 则构建失败。这样就把感觉上好像没问题变成了指标上必须过关。注意评估时尽量用与线上一致的 Embedding 模型和 LLM 模型否则测出来的成绩没有参考价值。我见过有团队评估用大模型线上用小模型结果评估全绿上线后回归效果一塌糊涂。更合理的做法是准备一组评估专用模型配置和线上一致环境变量直接注入。4.2 在线监控不是所有请求都能拿标准答案评估真实线上环境里大多数用户请求没有 ground truth 可对照。但这不代表不能做监控。我常用的思路是反馈回路在生成答案的同时给每条请求生成一个隐式反馈事件比如在 UI 上加上点赞/点踩按钮。LangSmith 支持通过client.create_feedback()把评分挂到对应 trace 上。之后可以在 LangSmith 里筛选出所有点踩的 trace集中分析。启发式监控对纯规则可判断的问题比如答案是否为空、答案是否包含我不知道、检索是否返回 0 条结果用 LangSmith 的run元数据做聚合超过阈值自动告警。client.create_feedback( run_idrun_id, keyuser_thumb, score0.0, comment用户反馈答案不相关 )这些反馈数据积累到一定量后可以回流到评估数据集里形成线上问题→回归样本的闭环。这也是为什么我在前面强调数据集要用真实 trace 创建反哺链路才顺。4.3 评估器自身的可信度小心Judge 被带偏LLM-as-Judge 存在一个隐蔽但是致命的问题当评估用的强模型本身也受 prompt 影响时评分会产生系统性偏差。比如你让 GPT-4 当 Judge给一个答案简洁但精准的候选评分Judge 可能会因为回答不够热情而打低分。这种偏差会通过回归测试传导到整个研发流程让团队为了讨好 Judge 而调整 prompt最后线上用户的真实感受却越来越差。针对这个坑我的经验是Judge 的 prompt 要写得像评分规则而不是喜好描述。明确说明只依据是否与标准答案语义一致来评分忽略风格差异。定期人工抽检评估报告。随机抽 10 条评估结果人工判别 Judge 是否误判。如果误判率超过 20%说明 Judge prompt 需要重新设计。不一致时以 Heuristic 为准。涉及可量化指标如引用合规时不要用 Judge 覆盖规则结果。评估器本身也要像被测系统一样管理版本。我一般把每条评估器的 prompt 固定下来写在代码仓库里配上system_prompt_version字段出问题能回滚。5. 进阶把评估指标延伸到 RAG 的业务效果很多团队用 LangSmith 做到追踪、评估就停在了回答正确性这一步。但对业务方来说答案正不正确只是中间指标他们更关心用户能不能一次解决问题回答有没有促进转化。这部分没法纯靠 LLM-as-Judge 完成但可以在 LangSmith 的 trace 里加一层业务埋点。5.1 给关键业务动作打上 trace 标记我在不少项目里会在 RAG 应用之外再上报一组业务事件。比如用户是否在看完回答后点击了详情页用户是否在答案里找到了他想要的商品并加入购物车用户是否重复提问同一类问题说明第一次没解决这些事件不直接在链路追踪里体现但它们可以关联到同一个trace_id。LangSmith 允许你给 trace 附加元数据和反馈因此可以在业务后端把这些事件统一上报。client.create_feedback( run_idtrace_id, keytask_success, score1.0, comment用户在回答后完成了下单动作 )5.2 构建指标树从链路指标回归业务目标更完整的做法是建立一个三层指标树第一层 链路健康检索延迟、生成延迟、token 消耗、错误率 第二层 内容质量忠实度、引用合规率、上下文相关性、拒答准确率 第三层 业务结果任务成功率、用户满意度、重复提问率LangSmith 的 trace 和 feedback 天然适合承载三层数据。你可以自定义一个数据面板Dashboard把这三层指标按天聚合观察趋势变化。一旦业务结果指标出现波动顺着 trace 往下钻取通常能定位是在链路哪个环节出了偏差。5.3 坏样本驱动的持续优化闭环从工具使用角度链路追踪和评估最终需要形成一个运转闭环线上异常 → 沉淀到评估集 → 离线回归暴露根因 → 修复/调优 → 再次回归验证 → 发布上线 → 持续监控LangSmith 只是让这个闭环里的每一步都有了数据支撑。真正决定效果的是团队有没有养成主动沉淀坏样本而不是只救火的习惯。我见过一些团队把 LangSmith 用得风生水起核心差异就在于他们每周会固定留出时间把一周的新 bad case 转化为新的评估样本让系统越用越聪明。6. 我的经验补遗接入 LangSmith 前后容易忽略的细节最后分享几个实操中容易踩的坑能省下不少折腾时间。6.1 环境变量与 API Key 隔离LangSmith 的客户端通过环境变量控制上报目标。如果你有多个环境dev、staging、prod一定要严格隔离 API Key 和 project 名。我遇到过因为没有区分环境开发时用的噪音数据污染了正式项目的评估集导致回归测试越来越离谱。建议每个环境单独申请一个 API Key且在代码里显式声明LANGSMITH_TRACINGtrue LANGSMITH_ENDPOINThttps://api.smith.langchain.com LANGSMITH_API_KEYyour_env_specific_key LANGSMITH_PROJECTrag-prod6.2 埋点别忘排除隐私字段RAG 应用经常输入包含用户隐私信息的内容比如企业内部资料、个人信息片段。在开启自动埋点前先想想是否需要脱敏。我这里用traceable的方式会在函数输入输出里自动记录实际内容所以我在传入前先做一层脱敏只保留问题与文档 ID不保留完整文档正文。这样既不会让敏感数据进入 trace又能满足排查需求。6.3 评估阈值不要太激进评估指标全绿并不代表线上一定没问题这是所有自动化评估体系的共同边界。我建议把阈值设定在能拦住明显回归的程度就够不要追求 100% 通过。因为模型有随机性同一问题在不同温度下可能会输出不同答案设定过高的阈值只会让团队把时间浪费在重跑和调整 prompt 上。具体来说忠实度平均分和引用合规率可以设 0.85 作为底线但单条样本低于 0.5 才会被标记为明显回归。真正高优先级的告警是出现在核心场景样本上的大幅下滑而不是某个边角样本的单点波动。6.4 别忘了人工抽检工具的终极目标是辅助决策而不是替代决策。我始终保留每周一次的人工抽检机制从线上随机抽 20 条 trace逐个看一遍 retrieval 得分和生成答案确认评估器没有漂移。LangSmith 的 UI 让这个抽检过程非常高效不需要额外写统计分析工具。这套体系跑了一段时间后我最大的感受是链路追踪解决的是看得见自动化评估解决的是守得住两者配合起来RAG 应用才有了持续交付的基本保障。如果你正在做的项目也遇到类似问题建议别急着调 prompt先把链路数据和评估闭环搭起来。有了数据你做的每个决策都会更有底气。