RAG Agent 评估与优化
1 评估的意义与作用
Agent(尤其是 RAG Agent)上线后的好用通常是多因素耦合:切分/索引/召回/重排/图谱/提示词/模型/工具调用等,仅靠主观体验很难定位瓶颈。评估的核心价值是把“感觉”变成“可量化、可回归、可定位”的工程闭环。
-
质量量化:用指标把输出质量数字化(例如忠实度、相关性、检索召回/精确)。
-
回归保障:改了切分、TopK、Rerank、Prompt 或模型后,能快速判断整体是否退化。
-
发现短板:通过细分指标(如检索召回率 vs 生成忠实度),精准定位是检索模块的问题还是大模型生成的问题。
-
自动化监控:建立自动化测试流,减少人工验收的成本和主观偏差。
2 目前主流评估 Agent 的方法
2.1 基于数据集的离线评估(Golden Set)
-
方式:准备 question + ground_truth,用当前系统跑出 answer + contexts,再人工对比打分。
-
优点:可复现、可回归、易对比多版本系统。
-
难点:黄金集建设成本,一般是通过人工结合真实业务总结出来的; 人工打分成本较大。
2.2 LLM-as-a-Judge(大模型当裁判)
-
方式:让更强的模型对“答案是否正确/是否基于上下文/是否跑题”做判分或打标签。
-
代表:RAGAS 、DeepEval、TruLens 等。
-
优点:更贴近语义质量;无需把所有问题都写成“唯一标准答案”(RAGAS 可同时评估检索与生成)。
-
风险:裁判模型本身有偏差, 生成问题集未必贴近实际业务。
3 什么是 RAGAS
3.1 概念
- RAGAS(Retrieval-Augmented Generation Assessment)是一套面向 RAG(检索增强生成)系统的评测框架/方法论,目标是在缺少人工标注答案或难以大规模做人工评审的情况下,用相对自动化的方式衡量 RAG 的关键质量:检索是否找对、答案是否忠于证据、是否覆盖问题要点、是否引入幻觉等。
3.2 RAGAS 评测的核心思路
RAGAS通常把一次问答拆成四要素来评估:
-
Question(问题)
-
Contexts(检索到的上下文证据):通常是 top-k 文档片段
-
Answer (RAG流程生成的答案)
-
Ground_truth(参考答案)
然后用一组指标从“检索侧 + 生成侧 + 证据一致性”三个方向打分。
**常见指标 **
- Faithfulness(忠实性/证据一致性)
衡量答案中的陈述是否能被给定 contexts 支撑;越高表示越不容易“编”。
- Answer Relevancy(回答相关性)
衡量答案是否真正回答了问题、是否跑题或废话。
- Context Precision(上下文精确率)
检索到的 contexts 里,有用内容占比是否高;越高表示“拿到的证据更干净”。
- Context Recall(上下文召回率)
contexts 是否覆盖了回答问题所需的关键信息;越高表示“证据拿全了”。
- Answer Correctness / Similarity(正确性/相似度)
当你有参考答案(ground truth)时可用;没有参考答案时通常以 Faithfulness/Context 相关指标为主。
4 评估优化步骤
-
准备评测集:问题列表(可来自真实日志抽样/人工构造/AI合成)
-
编写RAGAS的评估程序
-
运行评估生成评估结果
-
定位问题:
Context Precision 低:检索噪声大(需要重排序、过滤、改 embedding、改 chunk)
Context Recall 低:检索没找全(top-k、召回策略、chunk 粒度、query 改写)
Faithfulness 低:生成在编(提示词约束、引用证据、拒答策略、降低温度等)
-
bad case分析
-
优化程序
5 准备评测集
准备已经从pdf转为md的文本文件,交给任何一个ai生成即可。生产环境推荐由对业务较熟悉的人员手工编写,或者从常见业务问题中收集。

将ai生成的内容放在eval/eval_doc目录下的qa.csv文件中。

6 评估程序代码
6.1 代码流程
-
初始化评估组件(LLM、Embedding、Metrics)
-
加载测试数据(qa.csv)
-
运行 RAG pipeline
-
构建评估数据集
-
执行ragas评估
-
导出结果并购提取bad case
6.2 主程序代码
6.3 Step 1:初始化评估组件(LLM、Embedding、Metrics)
对应代码:
6.3.1 相关Ragas API
LangchainEmbeddingsWrapper:把 LangChain Embeddings 接口包装成 Ragas 可用的 embedding 计算器。
- 你们自定义了 ProjectBGEEmbeddings,把项目里的 generate_embeddings()(BGE-M3)适配成 LangChain 的 embed_documents/embed_query。
注意:generate_embeddings() 同时产出 dense+sparse,但这里 只把 dense 交给 Ragas。
为什么只需要稠密索引(dense):
稀疏向量(sparse)主要是为了检索阶段的 hybrid search(Milvus 稠密+稀疏双路);而 Ragas 评测阶段并不负责检索,它只拿到最终的 contexts 文本来评“相关不相关/覆盖不覆盖/是否忠实”。所以在这里只把 dense 交给 Ragas,不会让评测失真到不可用,它评的是“给定这些 contexts 后,答案的质量/证据一致性/语义相关性”。
6.3.2 Ragas 指标的工作原理(结合本文件)
本文件选了 4 个典型 RAG 指标:
- faithfulness(忠实度):通常由 LLM 参与判定
原理:把 answer 拆成若干“声明/事实”,让 LLM 判断每个声明是否能在 contexts 中找到充分依据;汇总得到分数。依赖字段:answer + contexts(依赖 Step 3 提取的 reranked 文本质量)。
- answer_relevancy(答案相关性):通常依赖 embedding(可能辅以 LLM)
原理:衡量 answer 是否在语义上真正回答了 question(常见做法是 embedding 相似度/问答一致性判断)。依赖字段:question + answer。
- context_precision(上下文精确度):衡量“返回的 contexts 里有多少是真相关/有用的”,通常需要 LLM/embedding
原理:逐条判断 contexts 是否与问题相关,并结合排序位置计算精确度倾向(越靠前越重要)。依赖字段: ground_truth(参考答案) + contexts 。
- context_recall(上下文召回率):衡量“contexts 是否覆盖了应有的关键事实”,通常需要 LLM
原理:对比 ground_truth(或 reference)中关键点,看 contexts 是否包含支撑这些关键点的信息。依赖字段:ground_truth(参考答案) + contexts(强依赖你的测试集答案质量与完整性)。
- Answer Correctness 就是用来衡量生成答案与参考答案(ground truth / reference)的对齐程度,范围 0~1
原理:用 embeddings 计算 answer 与 ground_truth 的语义相似度
6.4 Step 2: 从 CSV 加载测试样本(question + ground_truth)
对应代码:
-
输入文件默认是 kb/eval/eval_doc/qa.csv(两列:question,answer)。
-
代码里把 CSV 的 answer 列映射为 ground_truth,后续供 Ragas 的“需要参考答案”的指标使用(尤其是 context_recall)。
你们当前的 qa.csv 结构示例:
question,answer
"HAK180:为什么不要将HAK180烫金机放在化学品附近或可能泼溅到化学品的位置?","化学品接触设备可能引发火灾或触电;尤其有机溶剂/液体..."
6.5 Step 3:运行现有 RAG Pipeline,采集 answer 与 contexts
- 对应代码:
调用之前的项目查询流程query_process的langgraph的主流程,提取state中rerank重排之后的文档作为context=
6.6 Step 4:构建 Ragas 数据集
-
对应代码:
6.9 Ragas 的 schema
在你们代码里,一条样本最终进入 Ragas 的字段是:
-
question: 用户问题
-
answer: 你们 RAG pipeline 的实际输出
-
contexts: rerank 后上下文列表(list[str])
-
ground_truth: CSV 中 的参考答案
6.10 Step 5:调用 ragas.evaluate() 计算指标(Ragas 核心 API)
- 对应代码
6.11 evaluate() 的原理
可以把 ragas.evaluate() 理解为一个“指标调度器”:
-
输入:
-
带标准字段的数据集(每行一条样本)
-
指标列表
-
可选的 LLM/Embedding 计算器
-
过程:对数据集逐行计算每个 metric 的 score
遇到 LLM 型 metric(如 faithfulness/context相关):会用你传入的 llm 对“答案/上下文/参考答案”生成判断或抽取,再汇总成分数
遇到 embedding 型 metric(answer_relevancy 相关):会用你传入的 embeddings 计算向量相似度或相关性
- 输出:一个结果对象,包含:
每行样本的各指标分数列(列名来自 metric.name)
通常也会包含原始字段(question/answer/contexts/ground_truth 或其兼容命名)
5.2 raise_exceptions=False 的意义
你们显式设置了:
raise_exceptions=False:表示某条样本计算某个指标失败(比如上下文为空、模型调用异常、解析失败)时,不要让整次评测中断,通常会把该格分数记为 NaN/None。
这也正好与 Step 6 的 bad case 筛选逻辑配套(把 NaN 当 bad case)。
- 注意
evaluate()这个非常消耗内存如果出现卡死或内存不足的时候,先把问题及调小一些。
6.12 Step 6:导出结果、筛 Bad Cases、生成文本报告
- 对应代码:
6.13 Bad Case 定义
-
只要 answer_correctness < 阈值(默认 0.5),或者 任意指标为 NaN,该样本就会进入 bad case。
-
阈值是参数 bad_case_threshold(主程序里传 0.5)。
7 Bad Case 分析
7.1 案例一:错失切片
7.1.1 问题描述
- 从bad case 清单中分析
7.1.2 分析
faithfulness 较低,context_precision和context_recall 为0 。 说明提供的上下文并没有给出符合参考答案信息,llm被迫编造了不忠于资料的回复。
但是查询上下文中,并没有引用到这部分切片。
说明相关切片没有最终进入到最终提示词的参考中。
切片可能出现三个问题:
-
切片在embeddingg中没有被匹配到
-
切片在rrf的融合倒排中被过滤掉了
-
切片在rerank 的重排序中被过滤掉了
7.1.3 为什么有明确答案的分片会被打低分?
主要原因是rerank的模型的导致的,reranker模型为了提高评判的效率默认只会查看靠前的token, 所以在当信息处于整个切片靠后的位置的时候reranker会给出较低的评分。
7.1.4 如何解决
目前该rerank模型不支持修改这个参数,只能更换模型,而增加模型的评判token数会影响效率和准确性。
- 改动一:比较合适的处理方式是调整分片的大小,而且确实越大的切片对于评分的准确度就会下降因为关键词句会被稀释。
在knowledge/processor/import_process/config.py 中修改参数
把原来的切片最大长度2000改为500 ,切片的最小长度500改为100
- 改动二:由于 切片变小,那么可以考虑更多的切片,纳入到最终的提示词中所以提高最小排名数和GAP的参数
在knowledge/processor/query_process/config.py 中修改参数
- 重新测试该案例:
7.1.5验证
通过本次优化:可以看整个评估的评价值结果
优化前:
优化后:
可以看出尤其是上下文召回和忠实度都有不错的提高。
7.2 案例二:提示词问题
7.2.1 问题描述
整体的faithfulness、answer_relevancy、context_precision、context_recall都不低,但是answer_correctness不高。
主要原因是一般是提示词对于修饰答案的表达风格和参考答案差距较大。
7.2.2 分析问题:
通过系数可以看出faithfulness、answer_relevancy、context_precision、context_recall 这几个参数都不低,说明查询的分片与问题和答案都没问题。问题出在最终输出的回答与参考答案准确度不高。主要的原因就是,回答太罗嗦,太散,观点不够集中,所以回答与参考答案的匹配不高。
7.2.3 解决思路
在prompt.py中的要求部分改为:
7.3 案例三: 缺失逻辑
7.3.1 问题描述
7.3.2 分析问题:
通过系数可以看出这几个参数都不低,说明查询的分片与问题和答案都没问题。
但是仔细查看答案,其中关于放电理由存在实时性错误。
这种情况一方面可能是因为切片保留的内容较多,大量的噪声干扰了最终的答案输出。那么可以减少噪音,比如减少最终保留的切片数。但是这么做会有其他问题,比如在其他的搜索中合理的切片被遗弃了,这是一个两难的选择。
那么换一个思路,如果不动切片的话还有什么地方可以尽量保留关键点呢? 图谱信息。
图谱信息比起切片最重要的就是可以把内容提纯,减少干扰,提取核心的关键点。
7.3.3 解决思路:用知识图谱提纯关键事实
普通向量检索返回的是一段完整切片。切片虽然保留了上下文,但其中可能同时包含背景说明、其他操作、注意事项等信息,模型容易把不同语境下的内容组合在一起。知识图谱采用“实体—关系—实体”的结构,只保留文档中明确出现的对象、动作、条件、步骤和警告,可以把长文本中的关键逻辑压缩为短而明确的事实。
例如,原文中如果明确说明“测量电阻前应断开被测电路电源并将电容放电,以防触电”,图谱可以抽取为:
这样,系统既能通过关系直接保留“做什么、在什么条件下做、为什么要注意”,又能根据实体反查原始切片,让最终答案仍有原文作为证据。知识图谱在本项目中不是替代向量检索,而是作为第三路检索,与普通向量检索、HyDE 检索共同工作。
7.3.4 图谱提取流程
知识图谱在文档导入阶段生成。当前导入流程位于 knowledge/processor/import_process/main_graph.py,核心顺序如下:
具体处理步骤如下:
- 文档切片:先把 PDF 或 Markdown 转换、清洗并切分为多个 Chunk。每个 Chunk 都带有
chunk_id、content和item_name。 - 识别商品名称:
ItemNameRecognitionNode从文档标题和前几个切片中提取商品或设备名称,并将其写回所有切片。item_name用于隔离不同产品下的同名实体,避免把不同设备的“电源键”“电阻测量”等节点混在一起。 - 逐切片抽取图谱:
KnowLedgeGraphNode将每个切片交给 LLM,要求只输出包含entities和relations的 JSON。调用失败时最多重试 3 次。 - 清洗抽取结果:程序校验 JSON 结构、实体名称长度、实体类型和关系类型,过滤空值、重复项、越界类型以及头尾实体不存在的关系,减少模型自由生成造成的脏数据。
- 双库存储:实体名称经过 BGE-M3 生成稠密向量和稀疏向量后写入 Milvus;实体、实体关系以及实体到原始切片的映射写入 Neo4j。
- 幂等更新:同一
item_name的文档重新导入前,先清理其旧的 Milvus 实体记录和 Neo4j 节点、关系,再写入新结果,避免图谱重复或新旧版本混用。
当前项目允许抽取的实体类型如下:
核心关系包括:
Neo4j 中的实体节点使用 (name, item_name) 作为业务归属,Chunk 节点使用 (id, item_name) 标识。实体除了短名称外,还可以保存 description,用于保留步骤或警告的原文细节。因此,图谱中的短名称负责检索和关联,description 与原始 Chunk 负责提供完整证据。
7.3.5 图谱使用流程
查询侧的图谱检索由 knowledge/processor/query_process/nodes/kg_search_node.py 完成,并作为 LangGraph 多路检索的一条并行分支运行。
- 确认商品范围:系统先从当前问题和历史会话中识别商品名,再通过商品名称向量库进行对齐。如果无法确认具体商品,会要求用户补充或选择,防止跨商品串图。
- 抽取查询实体:从改写后的独立问题中移除商品名,降低噪声,再由 LLM 提取部件、操作、步骤、条件或警告等实体关键词。
- 实体对齐:用户的表达不一定与入库实体完全一致,例如“测欧姆”可能对应图谱中的“电阻测量”。系统使用 BGE-M3 的稠密、稀疏混合检索,在当前
item_name范围内把查询词对齐到已入库的标准实体名。 - 图查询:对齐后的实体作为种子节点,在 Neo4j 中先精确定位,并在需要时使用模糊匹配兜底;随后双向查询种子节点的一跳关系,保留关系的真实方向和实体描述。
- 关系转文本:查询结果被转换为模型容易理解的关系描述,例如
【RS-12数字万用表】电阻测量 --REQUIRES--> 断开电源,写入kg_triples。 - 生成答案:原始切片负责提供完整证据,
kg_triples负责突出对象间的逻辑关系,二者互相补充。这样既保留上下文,又减少长切片中噪声对模型判断的影响。
7.3.6 本案例中图谱如何补足逻辑
对于“测完电压后能否直接测电阻或二极管”这个问题,单靠相似度检索容易召回多个包含“电压”“电阻”“二极管”的切片。模型可能知道需要放电,却把放电目的错误概括为“防止仪表损坏或读数不准”。
图谱检索会优先围绕“电阻测量”“二极管测量”“功能转盘”“断开电源”“电容放电”“防触电”等实体展开。如果原文已经建立 REQUIRES、HAS_STEP 和 HAS_WARNING 关系,系统可以同时获得:
- 操作限制:不能在电压档直接测量电阻或二极管;
- 前置动作:断开电源、给电容放电、拔掉相关连接;
- 档位要求:将功能转盘切换到对应测量档位;
- 警告原因:原文明确给出的“防触电”。
这些关系会把分散在切片中的关键点集中起来,并把关联原文切片推到更靠前的位置。最终提示词要求模型只使用参考内容和图谱关系中明确出现的事实,从而降低错误归因和自行补充原因的概率。
这里必须强调:知识图谱只能提纯原文中已经明确表达的逻辑,不能自动把错误或缺失的原文变成正确知识。如果源文档没有说明“放电是为了防触电”,抽取阶段就不应臆造该关系,需要补充权威资料或人工校正图谱。