RAG应用落地难在评估?这份指南帮你建立科学的评估体系,确保应用稳定可控
先说个事实:搭建一个RAG应用原型,借助LlamaIndex、LangChain这些框架,可能一两天就够了。但真正把它推到生产环境,让它稳定、可控、持续改进——这才是硬骨头。无数团队卡在“评估”这道坎上,要么根本不知道从何下手,要么评估方法过于粗糙,最后上线后问题频出。这篇指南就来拆解一下:如何科学地评估RAG应用,让它从“能用”变成“好用”。
核心内容:
1. RAG应用评估的必要性与四大挑战
2. 评估依据与关键指标解析
3. 评估流程设计与实施方法
一、为什么RAG应用需要评估
大模型技术已经足够成熟,我们有能力构建完整的RAG应用。但真正将它投入生产环境,远不只是“搭建起来”那么简单。下面这四个问题,值得在动手之前仔细想清楚。
1. 大模型输出的不确定性
大模型的回答天生带有不确定性——这是它的优点,也是痛点。这种特性意味着你没法像对待传统软件那样,“输入固定、输出固定”。如果上线前没有充分测试,一个出乎意料的回答可能直接引发业务风险。所以,部署之前必须通过科学方法量化并控制这种不确定性。2. 持续评估与改进机制
应用上线只是起点。后续迭代中,你需要一套科学、快速、可复用的评估机制,来衡量每次优化到底有没有带来价值。举个例子:某次修改后,回答的置信度升了10%还是降了5%?如果没有系统化的度量,所谓的“调优”很可能只是盲目试错。3. 知识库的动态变化
RAG应用依赖的知识库不是一成不变的。随着数据持续更新,可能引入新的噪声甚至错误信息。为了长期稳定运行,定期检测和重新评估知识库质量就变得至关重要。4. 底层模型的选择与升级
RAG应用高度依赖底层大模型。面对市面上那么多商业和开源模型,企业需要明确如何选型。同时模型升级是否带来性能提升,还是引入了新的不确定性?这些都必须提前纳入评估体系。
一句话总结:RAG应用快速开发不难,真正的挑战在于生产环境中的稳定性、可控性和持续改进能力。唯有建立科学的评估与运维体系,才能让RAG应用真正发挥价值。
二、RAG应用的评估依据与指标
传统应用输出具有确定性和可量化性——比如直接返回一个数字,评估标准清晰。而RAG应用以自然语言作为输入输出,结果是一段文本。相关性、准确性这些核心指标,很难通过简单的定量方式直接判断,往往需要借助更智能的评估工具和专业模型来完成。
RAG应用的评估依据,也就是评估模块的输入,一般包括以下要素:
- 输入问题(question):用户使用时的问题。
- 生成的答案(answer):RAG应用输出的答案。
- 上下文(context):检索阶段生成的参考上下文,用于增强回答。
- 参考答案(reference_answer):人工标注的真实正确答案(可选,用于某些指标)。
基于这些依据,常见的评估指标如下表所示。
三、RAG应用的评估流程与方法
- 确定评估的目的、维度与指标。
- 准备评估数据集(自行标注或利用大模型生成均可)。
- 根据所选指标,可能需要准备不同的输入问题与参考答案。
- 将评估数据集输入RAG应用,获得检索上下文和生成的答案。
- 将评估依据输入评估器,计算各类指标,分析整体性能。
- 组件级评估:重点关注检索与生成两个核心阶段。分别评估它们,能更细致地定位问题,从而有针对性地优化。
四、评估检索质量
利用`RetrieverEvaluator`组件可以对任何检索模块进行质量评估。主要指标包括:
- 命中率(hit_rate):检索出的上下文对期望上下文的命中比例。
- 平均倒数排名(mrr):衡量检索结果的排名质量。
- Cohere重排相关性(cohere-rerank-relevancy):用Cohere Rerank模型衡量排名质量(可选)。
评估检索质量的主要依据是输入问题与期望的上下文(检索出的Node)。
1. 生成检索评估数据集
评估检索过程有两种方式:用`evaluate`方法评估单次查询,或用`evaluate_dataset`方法批量评估。由于需要问题与参考上下文的数据对,我们可以借助大模型,从已有数据中自动生成检索评估数据集——当然,你也可以手动构造。
from pathlib import Path
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.evaluation import (
generate_question_context_pairs,
EmbeddingQAFinetuneDataset,
)
# 加载大模型的代码
from common.llm_model_helper import llm_settings
documents = SimpleDirectoryReader(
input_files=[r"D:muxuecommon_docsjianlai.txt"]).load_data()
node_parser = SentenceSplitter(chunk_size=1024)
nodes = node_parser.get_nodes_from_documents(documents)
for idx, node in enumerate(nodes):
node.id_ = f"node_{idx}"
# 准备一个检索器,后面使用
# vector_index = VectorStoreIndex(nodes)
# retriever = vector_index.as_retriever(similarity_top_k=2)
QA_GENERATE_PROMPT_TMPL = """
以下是上下文:
---------------------
{context_str}
---------------------
你是一位专业教授。你的任务是基于以上的上下文,为即将到来的考试设置{num_questions_per_chunk} 个问题。
这些问题必须基于提供的上下文生成,并确保上下文能够回答这些问题。确保每一行都只有一个独立的问题。不要有多余解释。不要给问题编号。
"""
print("Generating question-context pairs...")
qa_dataset = generate_question_context_pairs(
nodes,
# llm=llm_Ollama,
num_questions_per_chunk=1,
qa_generate_prompt_tmpl=QA_GENERATE_PROMPT_TMPL
)
print("Sa ving dataset...")
qa_dataset.sa ve_json("retriever_eval_dataset.json")
这段代码中,我们手工设置了一个中文Prompt,这样既能理解生成原理、便于调试,又能直接产出中文问题。最后将数据集保存到本地JSON,避免重复生成,后续直接加载即可。
2. 运行评估检索过程的程序
我们已经构造了检索器,并借助大模型生成了评估数据集。下面构造并运行检索评估器。基本流程:
- 加载检索评估数据集。
- 构造`RetrieverEvaluator`对象,设置评估指标。
- 调用`evaluate`方法,查看结果。
from pathlib import Path
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.evaluation import (
generate_question_context_pairs,
EmbeddingQAFinetuneDataset,
)
from llama_index.core.evaluation import RetrieverEvaluator
from evaluation_comm import get_retriever
print("Loading dataset...")
qa_dataset = EmbeddingQAFinetuneDataset.from_json("retriever_eval_dataset.json")
eval_querys = list(qa_dataset.queries.items())
# 构造一个检索评估器,设定两个评估指标
metrics = ["mrr", "hit_rate"]
retriever_evaluator = RetrieverEvaluator.from_metric_names(metrics, retriever=get_retriever())
# 简单评估前10个用例
for eval_id, eval_query in eval_querys[:10]:
expect_docs = qa_dataset.relevant_docs[eval_id]
print(f"Query: {eval_query}, Expected docs: {expect_docs}")
eval_result = retriever_evaluator.evaluate(query=eval_query, expected_ids=expect_docs)
print(eval_result)
# 对整个数据集进行评估(更简洁的方式)
# eval_results = retriever_evaluator.evaluate_dataset(qa_dataset)
从打印结果中可以看到每个用例的指标值。你可以汇总计算,得出检索评估器的整体质量。如果想要一次性评估整个数据集,直接调用`evaluate_dataset`方法即可——效果与逐个循环调用`evaluate`一致。
五、评估响应质量
响应质量是RAG应用评估的重点,因为它直接关系到最终用户体验。我们可以评估单个指标、单次响应,也可以基于批量数据集自动运行多个响应评估器,获取整体结果。
1. 生成响应评估数据集
和检索评估类似,我们可以借助大模型生成响应评估数据集(也可以按格式手动标注)。它与检索评估数据集不同:评估响应质量需要问题、检索上下文、大模型生成的答案,甚至参考答案。使用`generate_dataset_from_nodes`方法可以批量生成。
from pathlib import Path
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.llama_dataset.generator import RagDatasetGenerator
from llama_index.core.llama_dataset import LabelledRagDataset
from common.llm_model_helper import llm_settings
docs = SimpleDirectoryReader(input_files=[r'D:muxuecommon_docsjianlai.txt']).load_data()
dataset_generator = RagDatasetGenerator.from_documents(
documents=docs,
# llm=llm_ollama,
num_questions_per_chunk=1,
show_progress=True,
question_gen_query="您是一位老师。您的任务是为即将到来的考试设置{num_questions_per_chunk}个问题。这些问题必须基于提供的上下文生成,并确保上下文能够回答这些问题。确保每一行都只有一个独立的问题。不要有多余解释。不要给问题编号。"
)
sa ve_json_name='rag_eval_dataset_jianlai.json'
print('Generating questions from nodes...')
rag_dataset = dataset_generator.generate_dataset_from_nodes()
rag_dataset.sa ve_json(sa ve_json_name)
print('Loading dataset...')
rag_dataset = LabelledRagDataset.from_json(sa ve_json_name)
for example in rag_dataset.examples:
print(f'query: {example.query}')
print(f'answer: {example.reference_answer}')
运行后会在当前目录生成`rag_eval_dataset_jianlai.json`,每个评估用例包含:
query:生成的问题reference_contexts:检索出的参考上下文reference_answers:参考答案
这些数据将作为响应评估器的输入。
2. 单次响应评估
要对某一次查询的响应进行多维度评估,只需构造对应的评估器组件(Evaluator),输入必要数据即可。输入参数包括:
query:输入问题response:RAG应用的响应结果(如果使用evaluate_response方法可直接传入Response对象)reference:参考答案(用于正确性和相似度评估)
from llama_index.core import (
VectorStoreIndex,
SimpleDirectoryReader,
Response,
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.evaluation import (
FaithfulnessEvaluator, RelevancyEvaluator,
ContextRelevancyEvaluator, AnswerRelevancyEvaluator,
CorrectnessEvaluator, SemanticSimilarityEvaluator
)
from common.llm_model_helper import llm_settings
documents = SimpleDirectoryReader(input_files=[r"D:muxuecommon_docsjianlai.txt"]).load_data()
splitter = SentenceSplitter(chunk_size=512)
vector_index = VectorStoreIndex.from_documents(documents, transformations=[splitter])
query_engine = vector_index.as_query_engine()
query = "陈平安对中年光棍的尖酸刻薄言语有何反应?"
response = query_engine.query(query)
# 忠实度
evaluator = FaithfulnessEvaluator()
eval_result = evaluator.evaluate_response(query=query, response=response)
print(f'faithfulness score: {eval_result.score}')
# 相关性(综合上下文与答案相关性)
evaluator = RelevancyEvaluator()
eval_result = evaluator.evaluate_response(query=query, response=response)
print(f'relevancy score: {eval_result.score}')
# 上下文相关性
evaluator = ContextRelevancyEvaluator()
eval_result = evaluator.evaluate_response(query=query, response=response)
print(f'context relevancy score: {eval_result.score}')
# 答案相关性
evaluator = AnswerRelevancyEvaluator()
eval_result = evaluator.evaluate_response(query=query, response=response)
print(f'answer relevancy score: {eval_result.score}')
# 正确性(需要参考答案)
evaluator = CorrectnessEvaluator()
eval_result = evaluator.evaluate_response(
query=query, response=response,
reference='根据提供的上下文信息,陈平安对中年光棍的尖酸刻薄言语反应如下:他翻了个白眼,但并不以为意。原因有二:一是他生活在这座乡野地方,早已习惯此类言语,认为若因此恼火反而显得可笑;二是这中年光棍本身也是小镇百姓经常取笑的对象,陈平安对此人并无太多计较。'
)
print(f'correctness score: {eval_result.score}')
# 语义相似度(基于embedding)
evaluator = SemanticSimilarityEvaluator()
eval_result = evaluator.evaluate_response(
query=query, response=response,
reference='陈平安翻了个白眼,但并不以为意,对此人并无太多计较。'
)
print(f'semantic similarity score: {eval_result.score}')
评估过程非常简洁。注意:某些评估器(如正确性、语义相似度)必须提供参考答案,否则会报错。运行后可以看到类似下图的输出。
3. 批量响应评估
借助批量评估器`BatchEvalRunner`,可以在评估数据集上并行运行多个响应评估器,并通过统计获得综合性能结果。以下代码以第1节生成的响应评估数据集为基础,对构造的查询引擎进行综合评估:
from llama_index.core import (
VectorStoreIndex,
SimpleDirectoryReader,
Response,
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.llama_dataset import LabelledRagDataset
from llama_index.core.evaluation import (
FaithfulnessEvaluator, RelevancyEvaluator,
ContextRelevancyEvaluator, AnswerRelevancyEvaluator,
CorrectnessEvaluator, SemanticSimilarityEvaluator
)
from llama_index.core.evaluation import BatchEvalRunner
import asyncio
import pandas as pd
from common.llm_model_helper import llm_settings
documents = SimpleDirectoryReader(input_files=[r"D:muxuecommon_docsjianlai.txt"]).load_data()
splitter = SentenceSplitter(chunk_size=512)
vector_index = VectorStoreIndex.from_documents(documents, transformations=[splitter])
query_engine = vector_index.as_query_engine()
faithfulness_evaluator = FaithfulnessEvaluator()
relevancy_evaluator = RelevancyEvaluator()
correctness_evaluator = CorrectnessEvaluator()
similartiy_evaluator = SemanticSimilarityEvaluator()
rag_dataset = LabelledRagDataset.from_json('rag_eval_dataset_jianlai.json')
runner = BatchEvalRunner(
{"faithfulness": faithfulness_evaluator,
"relevancy": relevancy_evaluator,
"correctness": correctness_evaluator,
"similarity": similartiy_evaluator},
workers=4
)
async def evaluate_queries():
eval_results = await runner.aevaluate_queries(
query_engine,
queries=[example.query for example in rag_dataset.examples][:10],
reference=[example.reference_answer for example in rag_dataset.examples][:10],
)
return eval_results
eval_results = asyncio.run(evaluate_queries())
def display_results(eval_results):
data = {}
for key, results in eval_results.items():
scores = [result.score for result in results]
scores.append(sum(scores) / len(scores))
data[key] = scores
data["query"] = [result.query for result in eval_results["faithfulness"]]
data["query"].append("【A verage】")
df = pd.DataFrame(data)
print(df)
display_results(eval_results)
使用`BatchEvalRunner`的`aevaluate_queries`方法时,可以通过`workers`参数设置并行数,缩短评估时间。最后将结果以表格形式展示(也可以输出Excel),方便直观观察。输出结果如下。

