
01闭环五步全景一览先看完整链路再逐步拆图1评测闭环五步流程图Step吃什么输入吐什么输出Ragas扮演什么角色1文档目录Document列表无LangChain加载2Document列表KnowledgeGraph节点实体关系切卡片 → 划重点NER→ 拉红线Jaccard3KG Synthesizer 数量testset.json问题参考答案题型标签TestsetGenerator 3种QuerySynthesizer4testset中的问题每条的response retrieved_contexts无你的RAG系统5(question, response, contexts, reference)6指标×N条的得分矩阵6个Metric scorer下面逐步展开。· · ·02Step 1文档加载最简单的一步但它的输出格式决定后面所有环节怎么跑。输入一个文件夹里面放 .md/ .txt/ .pdf文档。输出一个 Document列表。每个Document两部分——正文page_content和来源信息metadata。from langchain_community.document_loaders import DirectoryLoader, TextLoader loader DirectoryLoader(./my_docs, glob**/*.md, loader_clsTextLoader) documents loader.load() # [Document(page_content爱因斯坦的相对论..., metadata{source: physics.md}), ...]这步没Ragas什么事。但输出要存盘——后面可能换不同参数重建知识图谱不想每次都重新扫文件夹。· · ·03Step 2建证据墙知识图谱这是上一篇讲的证据墙在代码层面的落地。回忆一下切卡片→划重点→拉红线。在Ragas里这三步对应三个组件证据墙的步骤Ragas组件做了什么切卡片DocumentSplitter把Document切成chunk每个chunk是图上的一个Node划重点NERExtractor / KeyphraseExtractor从每个Node提取实体、关键词挂到Node的properties上拉红线JaccardSimilarityBuilder两个Node共享实体拉一条边连起来输入Document列表输出KnowledgeGraph对象——节点文档chunk 边实体关系在代码里你不需要手动分别调这三步。TestsetGenerator.generate_with_langchain_docs内部会自动走完整个建图流程from ragas.testset import TestsetGenerator from ragas.llms import llm_factory from ragas.embeddings.base import embedding_factory from openai import AsyncOpenAI # 所有模型调用统一走OpenAI协议 client AsyncOpenAI(base_urlhttp://your-api/v1, api_keysk-xxx) llm llm_factory(gpt-4o-mini, clientclient) embeddings embedding_factory(openai, modeltext-embedding-3-small, clientclient) generator TestsetGenerator(llmllm, embedding_modelembeddings)调 generate_with_langchain_docs时Ragas先建图再出题是一体完成的。但有个问题**建图非常贵。**一批文档几十上百个chunk每个都要调LLM做NER提实体再两两算Jaccard相似度建边。跑一次可能几分钟几千次LLM调用。所以实践中必须把KG持久化。建好一次存成pickle文件后面换分布重新出题、或者换RAG系统重新评测都直接加载这个pkl不重复花钱。· · ·04Step 3批量造题知识图谱建好了接下来从图上长出测试题。这对应上一篇讲的换不同证人来问——同一张证据墙按不同题型策略遍历节点生成不同难度的问题。输入KnowledgeGraph QuerySynthesizer列表 生成数量输出testset.json每条样本包含四个字段{ user_input: 停在仓库门口的车主和谁有过法律纠纷, reference: 李强曾因合同纠纷起诉仓库老板张伟。, reference_contexts: [监控录像显示..., 报案记录显示...], query_type: multi_hop_specific }Ragas里有三种现成的QuerySynthesizer对应三种出题策略from ragas.testset.synthesizers import ( SingleHopSpecificQuerySynthesizer, # 单张卡片出事实题 MultiHopSpecificQuerySynthesizer, # 多张卡片出事实题 MultiHopAbstractQuerySynthesizer, # 多张卡片出推理题 )它们的区别在于怎么从图上选节点Synthesizer选节点方式对应上一篇的SingleHopSpecificQuerySynthesizer取单个Node单跳具体最简单一张卡片一句话能答MultiHopSpecificQuerySynthesizer取一条边连接的两个Node多跳具体得关联两张卡片MultiHopAbstractQuerySynthesizer取一条边连接的两个Node多跳抽象关联推理最难调用时通过 query_distribution参数控制比例。这个参数的类型是 List[Tuple[Synthesizer, float]]——一个synthesizer配一个权重query_distribution [ (SingleHopSpecificQuerySynthesizer(llmllm), 0.35), (MultiHopSpecificQuerySynthesizer(llmllm), 0.35), (MultiHopAbstractQuerySynthesizer(llmllm), 0.30), ] testset generator.generate_with_langchain_docs( documentsdocuments, testset_size20, query_distributionquery_distribution, )testset.samples就是生成的样本列表。每个sample有 user_input问题、reference参考答案、reference_contexts出处原文。怎么给每条题打上类型标签上面的调用方式有个问题——Ragas不会告诉你哪条样本是哪个Synthesizer生成的。解决办法是分批生成每次只传一种SynthesizerQUERY_TYPE_MAP { single_hop_specific: SingleHopSpecificQuerySynthesizer, multi_hop_specific: MultiHopSpecificQuerySynthesizer, multi_hop_abstract: MultiHopAbstractQuerySynthesizer, } all_samples [] for query_type, synth_cls in QUERY_TYPE_MAP.items(): synthesizer synth_cls(llmllm) testset generator.generate_with_langchain_docs( documentsdocuments, testset_sizetype_size, query_distribution[(synthesizer, 1.0)], # 一次只用一种 ) for sample in testset.samples: all_samples.append({ user_input: sample.user_input, reference: sample.reference, reference_contexts: sample.reference_contexts, query_type: query_type, # 打标签 })这样每条测试题都带了 query_type标签后面评分时就能按题型分组统计。这是整条流水线最值钱的中间产物testset.json生成一次可以拿去测N个不同的RAG系统——切片策略不同、Embedding模型不同、检索参数不同、甚至完全不同的技术栈——让它们在同一组考题上打分对比才有意义。· · ·05Step 4RAG系统答题有了考卷接下来让考生上场答题。输入testset.json里的每一条 user_input输出每条问题对应两样东西——responseRAG系统给出的答案文本retrieved_contextsRAG系统检索到的原文片段列表这一步和Ragas无关——它完全取决于你的RAG系统长什么样。你的系统可能是QdrantOpenAI可能是ElasticsearchQwen可能是一个现成的平台API。为了让评测工具不绑定特定实现我们定义了一个抽象接口class RAGInterface(ABC): abstractmethod async def query(self, question: str, top_k: int 5) - RAGResponse: ... dataclass class RAGResponse: answer: str # LLM生成的回答 retrieved_contexts: list[str] # 检索到的原文片段不管你的RAG系统底层用什么只要能实现这个接口——给一个问题返回答案和检索上下文——就能接入评测。配套项目里实现了一个 QdrantRAGInterface向量搜索Qdrant LLM生成答案作为参考实现。· · ·06Step 56指标打分现在手里有了每条问题的完整四元组用Ragas的6个Metric逐一打分。回忆第一篇讲的前三个评检索中间两个评生成最后一个评抗压。输入四元组输出6个分数Ragas评分统一模式所有指标的调用方式一致初始化scorer → 调 .ascore()传入所需字段 → 拿 result.valuefrom ragas.llms import llm_factory from ragas.embeddings.base import embedding_factory from ragas.metrics.collections import ( Faithfulness, AnswerRelevancy, ContextPrecision, ContextRecall, ContextEntityRecall, NoiseSensitivity, ) client AsyncOpenAI(base_urlhttp://your-api/v1, api_keysk-xxx) llm llm_factory(gpt-4o-mini, clientclient) embeddings embedding_factory(openai, modeltext-embedding-3-small, clientclient)每个指标吃什么字段这里是关键——不是每个指标都需要四元组的全部字段。哪个指标需要什么严格对应它考察的维度检索环节的三个指标# 上下文精确率检索到的内容相关的排前面了吗 # 需要question reference contexts scorer ContextPrecision(llmllm) result await scorer.ascore( user_inputquestion, referencereference, # 拿标准答案判断哪个context是相关的 retrieved_contextscontexts, ) # 上下文召回率标准答案里的信息点检索都覆盖了吗 # 需要question reference contexts scorer ContextRecall(llmllm) result await scorer.ascore( user_inputquestion, retrieved_contextscontexts, referencereference, # 把reference拆成claim逐条检查context能否支撑 ) # 上下文实体召回关键实体漏没漏 # 需要reference contexts scorer ContextEntityRecall(llmllm) result await scorer.ascore( referencereference, # 从reference提取实体集合 retrieved_contextscontexts, # 从contexts提取实体集合算交集比例 )生成环节的两个指标# 忠实度回答有没有瞎编 # 需要question response contexts不需要reference scorer Faithfulness(llmllm) result await scorer.ascore( user_inputquestion, responseresponse, # 把response拆成claim retrieved_contextscontexts, # 逐条检查claim能否被context支撑 ) # 回答相关性回答跑题了吗 # 需要question response只比对意图不需要context也不需要reference scorer AnswerRelevancy(llmllm, embeddingsembeddings) result await scorer.ascore( user_inputquestion, responseresponse, # 从response反推问题算和原问题的相似度 )抗压测试# 噪声敏感度被无关内容带偏了吗越低越好 # 需要全部四元组 scorer NoiseSensitivity(llmllm) result await scorer.ascore( user_inputquestion, responseresponse, referencereference, # 判断哪些claim是错的 retrieved_contextscontexts, # 判断错误是否源于噪声context )总结成一张表指标questionresponsecontextsreference考察Context Precision✓✓✓检索排序Context Recall✓✓✓检索覆盖Context Entity Recall✓✓实体覆盖Faithfulness✓✓✓不瞎编Answer Relevancy✓✓不跑题Noise Sensitivity✓✓✓✓抗干扰按题型聚合逐条打完分后按 query_type分组取均值就能看到每种题型下各指标的表现# 分组 by_type {} for sample in results: by_type.setdefault(sample.query_type, []).append(sample) # 每组每指标取均值 for query_type, samples in by_type.items(): for metric in metrics: scores [s.scores[metric] for s in samples if s.scores[metric] 0] avg sum(scores) / len(scores)这才是有诊断价值的结果。比如你可能看到Single-Hop Specific忠实度 0.92召回率 0.85 →基本功OKMulti-Hop Specific忠实度 0.88召回率 0.55→跨文档检索覆盖不够Multi-Hop Abstract忠实度 0.72噪声敏感度 0.35 →复杂场景下容易被带偏对应第一篇讲的诊断思路召回低修检索扩top-k、调chunk忠实低修生成调prompt噪声高加过滤。现在你知道在哪种题型上修。· · ·07测试结果长什么样跑完 rag-evaluator run-all后evaluation_results.json是结构化数据rag-evaluator report把它渲染成一份带图表的Markdown报告。下面是一份示例报告的样子数据为模拟总览表MetricScoreAnswer Relevancy0.7800Context Precision0.9200Context Recall0.7100Entity Recall0.6500Faithfulness0.8500Noise Sensitivity0.2300总览图图2总览指标柱状图按题型对比表MetricMulti-Hop AbstractMulti-Hop SpecificSingle-Hop SpecificAnswer Relevancy0.68000.80000.8500Context Precision0.85000.93000.9500Context Recall0.55000.75000.8200Entity Recall0.50000.68000.7500Faithfulness0.72000.88000.9200Noise Sensitivity0.35000.20000.1500各题型独立图Single-Hop Specific7 samples——基本功表现图3Single-Hop Specific 指标柱状图Multi-Hop Specific7 samples——跨文档关联能力图4Multi-Hop Specific 指标柱状图Multi-Hop Abstract6 samples——复杂推理能力图5Multi-Hop Abstract 指标柱状图怎么读这份报告三张图并排看规律一目了然从左到右难度递增分数逐步下降——符合直觉多跳抽象题本就是最难的。Context Recall 下降最明显82% → 75% → 55%——说明检索器在需要跨多个文档覆盖时力不从心这是要优先修的环节。Noise Sensitivity 上升最明显15% → 20% → 35%——复杂场景下检索到的无关内容更多系统被带偏的概率更高。Context Precision 一直很高95%/93%/85%——检索排序还行排在前面的基本是相关的问题不在排序而在覆盖。对应的优化方向扩大top-k提升召回覆盖加rerank把多跳场景下更准确的上下文顶上去在prompt里强调只依据上下文回答降低噪声敏感度。· · ·08中间产物哪些可以复用整条流水线最贵的是Step 2建KG大量NER调用和Step 3合成问题大量LLM调用。所以每一步都要持久化output/ ├── testset.json # Step 3 产物最重要 └── artifacts/ ├── documents.json # Step 1 产物 ├── knowledge_graph.pkl # Step 2 产物 └── knowledge_graph_summary.json # Step 2 摘要复用逻辑很简单你想做什么复用哪个文件省掉哪些步骤同一份测试集测多个RAG系统testset.jsonStep 1-2-3全省同一批文档换比例重新出题knowledge_graph.pklStep 1-2省同一批文档什么都不复用无全部重跑这也是为什么要分步设计——Step 3生成的测试集是出题人的产出和考生RAG系统完全解耦。你可以让10个不同的RAG系统做同一份卷子然后横向对比。· · ·09跑通闭环完整命令配套项目有两个工具分工明确doc-indexer文档切片 → Embedding → 写入Qdrant造一个RAG系统当考生rag-evaluator出题 → 让考生答题 → 打分 → 出报告# 1. 先造一个考生——文档入库到Qdrant cd doc-indexer uv run doc-indexer index -d ./my_docs -c my_collection \ --openai-base-url http://localhost:11434/v1 \ --embedding-model nomic-embed-text # 2. 一键跑完出题 答题 打分 cd ../rag-evaluator uv run rag-evaluator run-all \ -d ./my_docs \ -c my_collection \ -o ./eval_output \ --openai-base-url http://localhost:11434/v1 \ --llm-model qwen2.5 \ -n 30 # 3. 生成可视化报告 uv run rag-evaluator report -r ./eval_output/evaluation_results.json用已有测试集测另一个RAG系统# 直接用上次的testset.json换一个RAG系统 uv run rag-evaluator run-all \ -d ./my_docs \ -c another_collection \ -o ./eval_output_B \ --reuse-testset ./eval_output/testset.json· · ·10总结三篇文章一条完整的线核心思路一句话让LLM既当出题人Step 2-3的知识图谱问题合成又当评委Step 5的6指标打分用文档本身作为ground truth来源实现无需人工标注的自动化评测闭环。闭环跑通之后你的RAG做得怎么样不再是一个主观感受而是一组可量化、可对比、可追踪的数字。每次改了切片策略、换了Embedding模型、调了prompt——重跑一次闭环看数字涨了还是跌了就知道改对了没有。代码在 github.com/quick-sort/hello_world/rag-eval-exampleuv sync即可运行。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】