1. 为什么链路追踪在 RAG 项目里成了刚需做过 RAG 项目的人大概都有过这种体验用户问了一个问题系统答得驴唇不对马嘴你打开日志一看只有一行“请求完成耗时 2.3 秒”。至于检索召回了哪些文档、重排之后的顺序是什么、最终塞进大模型的提示词长什么样、模型是哪一步开始跑偏的——全是黑盒。这种状态下调优基本靠猜。我最早做检索增强生成的时候用的就是最朴素的办法在每个环节手动打print把召回文档的标题、分数、最终拼装的 prompt 全部打到控制台。项目小的时候还能忍一旦链路变成“查询改写 → 多路召回 → 重排 → 上下文压缩 → 生成 → 后处理”这种六七个节点的流水线控制台输出直接爆炸而且你根本没法把某一次请求的完整链路串起来看。更麻烦的是当你想对比“换了重排模型之后效果到底有没有变好”你连一个稳定的评估基线都没有。这就是链路追踪和评估工具切入的价值点。LangSmith 这类平台解决的核心问题就两个第一把一次完整调用拆成可观测的树状结构每个节点的输入输出、耗时、token 消耗全部留痕第二把“效果好不好”这件事从主观感受变成可量化、可回归的自动化评估。前者让你看得见后者让你改得动。这篇文章适合两类人看。一类是已经在做 RAG 或者 Agent 应用但调试还停留在打印日志阶段的开发者你会在这里找到一套完整的可观测性搭建思路另一类是想引入自动化评估但不知道从哪下手、不知道评估集怎么造、不知道指标怎么选的人。我会把链路追踪的接入细节、评估数据集的构造方法、评估器的选型逻辑以及我自己踩过的坑全部摊开讲。2. 链路追踪的接入设计与核心思路拆解2.1 追踪模型选型为什么是树状 Span 而不是平铺日志先说清楚链路追踪的底层数据模型这决定了你后面怎么组织代码。LangSmith 采用的是 Span 树的结构一次顶层调用是一个 Run下面可以嵌套任意层级的子 Run每个子 Run 有自己的类型LLM、Chain、Tool、Retriever、Parser 等。这个设计和分布式追踪里的 Trace/Span 概念是一脉相承的。为什么一定要树状而不是平铺因为 RAG 的调用天然是嵌套的。一次用户请求进来最外层是一个 ChainChain 内部先调一次 LLM 做查询改写然后并发调三个 Retriever每个 Retriever 内部可能还有一次向量化调用召回结果汇总后进 Reranker再进一次 LLM 做生成。如果你用平铺日志你看到的是十几个孤立的记录根本还原不出父子关系用树状 Span你在界面上一眼就能看出“哦原来是第二个 Retriever 召回了不相关的文档导致后面生成跑偏”。我实测下来树状结构最大的好处是耗时归因。一次请求总共 3 秒到底是 LLM 生成慢还是检索慢还是重排慢树状视图里每个节点的耗时是累加的你顺着最耗时的分支往下钻很快就能定位瓶颈。平铺日志做不到这一点因为你看不出谁包含谁。2.2 自动追踪与手动埋点的取舍LangSmith 提供了两种接入方式自动追踪和手动埋点。自动追踪主要靠框架集成比如你用的是主流的那几个编排框架只要配好环境变量链路会自动上报几乎零代码改动。手动埋点则是用装饰器或者上下文管理器自己控制哪些函数要追踪、Span 的边界划在哪里。我的建议是混合使用。框架层面的调用Chain、LLM、Retriever交给自动追踪省事且不容易漏但你自己写的业务逻辑比如“查询意图分类”“上下文去重”“答案后处理”这些自定义步骤一定要手动埋点。原因很简单自动追踪只能看到框架认识的那些组件你写在普通函数里的逻辑它看不见。而这些自定义步骤往往才是问题高发区。手动埋点的粒度也要控制。我见过有人把每个小函数都包一层结果一次请求产生上百个 Span界面卡得打不开排查时反而被噪声淹没。合理的粒度是一个语义完整的步骤一个 Span。比如“查询改写”是一个 Span“向量检索”是一个 Span“重排”是一个 Span而不是把“拼接字符串”“计算余弦相似度”这种细碎操作也单独埋点。2.3 元数据设计让追踪数据真正可检索很多人接入追踪之后只填了默认字段结果数据攒了几万条想筛选“所有失败请求”或者“所有耗时超过 5 秒的请求”都做不到。问题出在元数据设计上。我的做法是给每个顶层 Run 固定挂几个维度的元数据环境标识开发/预发/生产、版本号提示词版本或模型版本、用户分群如果有、会话 ID用于多轮对话串联。这几个字段看起来简单但后面做评估对比的时候你会发现它们决定了你能不能把数据切片分析。提示元数据的 key 一旦定下来就尽量别改因为历史数据不会追溯更新。我早期把版本字段命名成ver后来改成version结果新旧数据在筛选时对不上白白浪费了一批历史追踪记录。另外敏感信息一定要在埋点阶段就过滤掉。用户输入里可能包含手机号、邮箱、身份证号这类内容直接上报到追踪平台是有合规风险的。LangSmith 支持在客户端做脱敏或者你也可以在埋点前自己写一层清洗函数。这一步别偷懒等出事再补就晚了。3. 核心细节解析与实操要点3.1 环境变量配置与项目隔离接入的第一步是配置连接信息。通常需要设置几个环境变量API 密钥、项目名称、追踪开关。项目名称这个字段特别重要它相当于一个命名空间不同项目的数据互相隔离。我一般会按“应用名-环境”的格式来命名比如rag-assistant-dev、rag-assistant-prod这样在界面上切换项目就能快速区分环境。这里有个容易踩的坑开发环境一定要把追踪开关做成可配置的。我早期图省事把追踪硬编码成始终开启结果本地跑单元测试的时候几百个测试用例全部上报把配额瞬间打满而且测试数据污染了正式项目的统计。后来改成读环境变量本地默认关闭只在需要调试时手动打开清爽多了。配置好之后先跑一个最小验证发一次请求去平台上确认能看到这条记录并且 Span 树的结构符合预期。这一步别跳过我见过有人配了半天发现密钥权限不对或者项目名拼错数据全跑到别的项目里去了。3.2 Span 边界划分的三个原则Span 怎么划直接决定了追踪数据的可读性。我总结了三条原则。第一按语义边界划不按函数边界划。一个 Span 应该对应一个“有业务含义的步骤”而不是一个技术函数。比如“检索”这个步骤内部可能调了 embedding 接口、查了向量库、做了过滤这三件事在业务上是一体的就应该合成一个 Span而不是拆成三个。第二输入输出要能自解释。每个 Span 的输入输出字段要让人不看代码就能明白这一步干了什么。比如检索 Span 的输出不要只存一个文档 ID 列表要把文档的标题、分数、片段都存进去。这样你在界面上点开这个 Span一眼就能判断召回质量。第三错误要显式标记。任何一步抛异常都要把 Span 标记为失败状态并把异常信息写进去。不要用 try-catch 吞掉异常然后继续跑那样追踪数据里看起来一切正常实际上结果早就错了。我吃过这个亏一个检索接口偶发超时被 catch 之后返回空列表下游生成模型基于空上下文硬编了一个答案追踪记录里全是绿色的成功状态查了两天才发现问题。3.3 追踪数据的采样策略生产环境全量上报追踪数据成本和存储压力都不小。这时候需要采样。但采样不能简单粗暴地随机丢否则你会丢掉最需要关注的失败案例。我的策略是分层采样正常请求按 10% 到 20% 采样失败请求 100% 上报耗时超过阈值的请求 100% 上报特定用户分群的请求提高采样率。这样既控制了总量又保证了异常样本不丢失。LangSmith 支持在客户端根据条件决定是否上报实现起来不复杂就是在埋点前加一层判断。注意采样率一旦调整评估数据的分布会跟着变。如果你在做 A/B 对比评估两个版本的采样策略必须一致否则对比结果没有意义。4. 从零搭建 RAG 自动化评估体系4.1 评估数据集没有标准答案怎么办自动化评估最大的拦路虎是“没有标注数据”。RAG 场景下一个问题的“好答案”往往不唯一你很难像分类任务那样给每个样本打一个确定的标签。这时候有几条路可以走。第一条路是人工构造小规模黄金集。找业务方或者自己动手针对高频问题写 50 到 100 条“问题 参考答案 相关文档 ID”。规模不用大但质量要高这是你评估体系的锚点。我一般会覆盖三类问题事实型答案在文档里能直接找到、推理型需要综合多篇文档、拒答型知识库里根本没有模型应该承认不知道。第二条路是用大模型辅助生成评估集。把知识库里的文档片段喂给一个能力较强的模型让它基于片段生成问题和参考答案。这样能快速扩充规模但生成的问题质量参差不齐需要人工过一遍筛掉明显不合理的。我实测下来生成 200 条能用的比例大概在六成左右剩下的要么问题太简单要么答案和片段对不上。第三条路是拿线上真实请求做评估集。从追踪数据里捞真实用户问题人工补上参考答案。这条路最贴近实际分布但成本最高适合产品上线一段时间、有一定数据积累之后做。4.2 评估器选型规则、模型、人工怎么配比评估器决定了你用什么标准给答案打分。常见的有三类。规则类评估器适合有明确对错标准的场景。比如“答案是否包含指定关键词”“检索是否召回了目标文档”“输出是否是合法 JSON”。这类评估器快、便宜、稳定但只能覆盖有限维度。模型类评估器用大模型来打分适合主观性强的维度。比如“答案是否忠实于检索到的上下文”忠实度、“答案是否切题”相关性、“答案是否完整”覆盖度。这类评估器灵活但有两个坑一是模型打分有波动同一个答案跑两次可能分数不一样二是模型可能被“忽悠”如果答案写得很流畅但内容是错的模型有时会给高分。人工评估是最后的兜底用于校准模型评估器的准确性。我的做法是定期抽一批样本人工打分然后和模型打分做对比如果偏差超过阈值就调整评估器的提示词或者换模型。实际配置上我一般用“规则打底 模型补充”的组合。检索环节用规则评估器命中率、召回率生成环节用模型评估器忠实度、相关性关键业务场景再加人工抽检。4.3 评估指标的计算与解读指标不是越多越好选几个核心的盯住就行。我常用的有这几个。指标含义计算方式关注阈值检索命中率目标文档是否被召回命中数 / 总数低于 0.8 要查检索上下文精确率召回文档中相关占比相关文档数 / 召回总数低于 0.5 说明噪声大忠实度答案是否基于上下文模型打分 0-1低于 0.7 有幻觉风险答案相关性答案是否切题模型打分 0-1低于 0.6 要查提示词拒答准确率该拒答时是否拒答正确拒答数 / 应拒答数低于 0.9 要加约束解读指标的时候要注意联动分析。比如忠实度低但检索命中率高说明问题出在生成环节模型没有好好利用上下文如果检索命中率本身就低那生成环节再优化也是白搭得先修检索。我见过有人一上来就调生成提示词调了半天没效果最后发现是检索根本没召回对的文档。5. 实操过程与核心环节实现5.1 追踪接入的完整代码路径假设你已经有一个跑得通的 RAG 流水线接入追踪的步骤大致如下。先装依赖配置环境变量然后在入口处初始化客户端。接着给核心函数加装饰器把每个语义步骤标记出来。import os from langsmith import traceable os.environ[LANGCHAIN_TRACING_V2] true os.environ[LANGCHAIN_PROJECT] rag-assistant-dev traceable(run_typeretriever, namevector_search) def retrieve(query, top_k5): # 向量化 检索 过滤 docs vector_store.search(query, top_k) return docs traceable(run_typellm, namegenerate_answer) def generate(query, contexts): prompt build_prompt(query, contexts) return llm.invoke(prompt)这里的关键是run_type参数它决定了这个 Span 在界面上显示成什么类型也影响后续的统计聚合。检索类用retriever生成类用llm工具调用用tool自定义逻辑用chain。跑一次请求之后去平台上确认 Span 树的结构。如果发现某个步骤没被追踪到大概率是装饰器没加对位置或者函数被别的装饰器包裹导致追踪失效。这种情况我遇到过解决办法是把追踪装饰器放在最外层。5.2 评估数据集的构造流程构造评估集我一般分四步走。第一步从知识库里随机抽一批文档片段覆盖不同主题。第二步用能力较强的模型基于每个片段生成 2 到 3 个问题同时生成参考答案。第三步人工审核删掉问题表述不清、答案与片段不符的条目。第四步把审核通过的条目整理成结构化格式每条包含问题、参考答案、相关文档 ID。dataset [ { question: 某产品的保修期是多久, answer: 根据文档标准保修期为 12 个月, relevant_doc_ids: [doc_1024, doc_2048] }, # ... ]这个数据集要版本化管理。每次调整知识库或者提示词评估结果都要和上一版对比。我习惯把数据集存成 JSON 文件配合版本号一起提交到代码仓库这样任何一次评估都能追溯到当时用的是哪版数据。5.3 评估任务的批量执行与结果对比评估不是跑一次就完事而是要能批量跑、能对比。我的做法是写一个评估脚本输入是评估数据集和待测的 RAG 配置输出是各项指标的分数。脚本内部对每条样本跑一遍完整流水线然后用评估器打分最后聚合。def run_evaluation(dataset, rag_pipeline, evaluators): results [] for item in dataset: output rag_pipeline(item[question]) scores {} for name, evaluator in evaluators.items(): scores[name] evaluator(item, output) results.append({**item, **output, **scores}) return aggregate(results)对比两个版本的时候不要只看平均分。平均分一样可能分布完全不同。我一般会看分位数和失败样本列表。比如 v2 版本平均忠实度比 v1 高了 0.05但仔细看失败样本发现 v2 在某一类问题上集体翻车只是被其他类别的提升掩盖了。这种问题只看平均分是发现不了的。提示评估任务尽量在固定环境下跑模型版本、温度参数、检索配置全部锁定。我有一次对比评估忘了锁温度结果两次跑出来的分数差异比版本差异还大白折腾一下午。6. 常见问题与排查技巧实录6.1 追踪数据不完整或丢失最常见的现象是明明跑了请求平台上却看不到记录或者只看到部分 Span。排查顺序是这样的。先确认环境变量是否生效尤其是追踪开关和项目名。再看网络是否通有些内网环境需要配置出口规则。然后检查装饰器是否加在了正确的函数上有没有被其他装饰器覆盖。最后看采样策略是不是被采样逻辑过滤掉了。我遇到过一次诡异的情况追踪数据时有时无。查了半天发现是并发调用时上下文管理器没有正确传递导致部分子 Span 丢失。解决办法是在并发入口处显式传递追踪上下文别依赖隐式继承。6.2 评估分数波动大同一个配置跑两次分数差很多通常有三个原因。一是模型评估器本身不稳定温度没设成 0或者提示词有歧义。二是评估数据集里有“边界样本”就是那种模棱两可、人工都难判断的条目这种样本会放大波动。三是流水线里有随机性比如检索时的随机采样、生成时的随机种子。解决办法模型评估器温度锁 0提示词里把评分标准写死边界样本单独标记评估时排除或者单独统计流水线里的随机源全部固定种子。6.3 检索和生成的问题难以区分有时候答案不好你分不清是检索没召回对还是生成没用好。这时候追踪数据就派上用场了。打开那条记录的 Span 树先看检索 Span 的输出目标文档在不在里面如果不在问题在检索如果在但排名很靠后问题在重排如果在且排名靠前但生成答案还是错的问题在生成环节的提示词或者模型能力。我一般会做一个“检索命中但生成失败”的专项分析把这类样本单独捞出来看。这类样本最有价值因为它说明检索没问题纯粹是生成环节的锅优化方向非常明确。6.4 常见问题速查表现象可能原因排查动作平台无数据环境变量未生效检查密钥、项目名、开关Span 树断裂并发上下文丢失显式传递追踪上下文评估分数波动评估器温度非 0锁定温度与随机种子忠实度低生成未用上下文检查提示词约束检索命中率低向量化或分块问题检查分块策略与模型拒答率异常提示词过于宽松增加拒答约束条件7. 我在实际项目中的几点体会链路追踪和自动化评估这两件事单独做哪一个都不难难的是把它们串起来形成闭环。追踪数据告诉你“哪里出了问题”评估数据告诉你“改完有没有变好”两者缺一不可。我早期只做追踪不做评估结果每次改完提示词只能靠感觉判断好坏改来改去反而越改越差。后来补上评估每次改动都有量化对比优化方向才清晰起来。另一个体会是评估集的质量比评估器的花哨程度重要得多。我见过有人花大力气调评估器提示词却用了一批质量很差的评估样本最后分数好看但线上效果没提升。评估集是地基地基不牢上面盖什么都是空中楼阁。最后分享一个小技巧把追踪和评估的元数据打通。也就是说评估时用的样本能关联到线上真实的追踪记录。这样当评估发现某个样本失败时你能直接跳到对应的线上请求看完整的链路。这个关联做起来不难就是在评估样本里存一个追踪 ID但带来的排查效率提升是巨大的。
阅读完成 · 觉得有帮助?