首页 > 教程攻略 > ai资讯 >使用RAG技术构建企业级文档问答系统之使用TruLens进行评估

使用RAG技术构建企业级文档问答系统之使用TruLens进行评估

来源:互联网 时间:2026-08-20 14:07:33

1 概述

RAG的评估,坦率地说,是个技术难点。目前业界的常见做法,大致有这么几种:

使用RAG技术构建企业级文档问答系统之使用TruLens进行评估

  • 人工评估:用多人盲审投票,准确率通常最高,但成本太高、效率低到几乎没有可操作性。

  • BLEU、ROUGE这类老牌指标:需要参考答案,而且不太考虑语义,现在RAG评估里用得很少了。

  • RAGAS评估:来自论文《RAGAS: Automated Evaluation of Retrieval Augmented Generation》,是一种不需要参考答案的自动化评测方法,目前基本是主流。

  • 用GPT-4打分:虽然争议不小,但实际中只要把Prompt调几轮,跟人工评估的一致性还挺高的,不过需要参考答案。

  • 语义相似度+关键词加权打分:常用于竞赛,企业级开发中很少用——因为得有正确答案,还得有关键词列表。

普遍的认识是,自动化评估的精度并不算高。吴恩达DeepLearning.AI平台上那门《Building and Evaluating Advanced RAG》课程里提到,当前自动化评估与人工评估的一致性只有80%多。这个数字供参考,但实际场景里通常不会这么低——一来那门课是2023年出的,当时大模型整体还没现在这么强;二来课程里介绍的方法(也就是本文要讲的方法)没有参考答案,而实际工作中一般会准备参考答案,在有参考答案的情况下,自动化评估与人工评估的一致性还是容易达到较高水平的。

本文要介绍的方法,借助TruLens库评估三个指标,TruLens称之为RAG三元组(RAG triad),由上下文相关性(Context Relevance)、依据性(Groundedness)和答案相关性(Answer Relevance)组成,如下图所示。这三个指标可以看作是RAGAS的子集,后面会有专门文章讲RAGAS评估。

(参考官方文档:https://www.trulens.org/trulens_eval/getting_started/core_concepts/rag_triad/

  • 上下文相关性(Context Relevance)

    :任何RAG应用的第一步就是信息检索。为了验证检索质量,需要确保每个上下文片段与输入查询高度相关。这一点至关重要,因为LLM将凭这些上下文来生成答案,不相关的信息会直接导致幻觉。TruLens通过序列化记录的结构来评估上下文的相关性。

  • 依据性(Groundedness)

    :检索到上下文之后,LLM会用它来形成答案。但LLM经常偏离提供的事实,夸大或延展成看似合理的回答。要验证依据性,可以把回答拆成独立陈述,然后在检索到的上下文里逐一找证据支撑。

  • 答案相关性(Answer Relevance)

    :最终回答还是得解决原始问题。通过评估最终答案与用户输入的相关性,就能验证这一点。

TruLens默认使用OpenAI的模型作为打分模型,但本文提供了更多选择——可以使用OpenAI兼容的LLM(比如千问),也可以用Ollama提供的模型。

本文代码已开源,地址在:https://github.com/Steven-Luo/MasteringRAG/blob/main/evaluation/01_trulens_evaluation.ipynb

2 准备环境

2.1 Ollama

先访问Ollama官网下载对应操作系统的版本并安装,然后拉取模型,这个模型会用作RAG中基于知识片段回答问题的LLM:

ollama pull qwen2:7b-instruct

2.2 Python

所有示例在安装Anaconda环境后,再安装下面的Python库即可。代码已在Google Colab测试过:

pip install -U langchain langchain_community pypdf sentence_transformers chromadb trulens_eval langchain_openai

本文使用的版本如下:

版本
langchain0.2.7
langchain_community0.2.7
langchain_openai0.1.7
pypdf4.2.0
sentence_transformers2.7.0
chromadb0.5.3
trulens_eval0.33.0

文中用到的问答对和原始数据,都可以从代码仓库里取用。

3 核心代码

import os
import pandas as pd
EMBEDDING_MODEL_PATH = dt = version = output_dir = os.path.join(os.path.pardir, , )
os.environ[] = os.environ[] =

加载问答对:

qa_df = pd.read_excel(os.path.join(output_dir, ))

3.1 文档处理

   PyPDFLoader
loader = PyPDFLoader(os.path.join(os.path.pardir, , ))
documents = loader.load()
   uuid4     RecursiveCharacterTextSplitter   Chroma 
(documents, filepath, chunk_size=, chunk_overlap=, seperators=[, ], force_split=):
    os.path.exists(filepath)   force_split:
        ()
        pickle.load((filepath, ))
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        separators=seperators
    )
    split_docs = splitter.split_documents(documents)
    chunk in split_docs:
        chunk.metadata[] = (uuid4())
    pickle.dump(split_docs, (filepath, ))
    split_docs
splitted_docs = split_docs(documents, os.path.join(output_dir, ), chunk_size=, chunk_overlap=)

向量化:

   HuggingFaceBgeEmbeddings 
device =   torch.cuda.is_a vailable()  ()
embeddings = HuggingFaceBgeEmbeddings(
    model_name=EMBEDDING_MODEL_PATH,
    model_kwargs={: device},
    encode_kwargs={: })
   tqdm 
(docs, store_path, force_rebuild=):
    os.path.exists(store_path):
        force_rebuild = 
    if force_rebuild:
        vector_db = Chroma.from_documents(
            docs,
            embedding=embeddings,
            persist_directory=store_path
        )
    else:
        vector_db = Chroma(
            persist_directory=store_path,
            embedding_function=embeddings
        )
    vector_db
vector_db = get_vector_db(splitted_docs, store_path=os.path.join(os.path.pardir, output_dir, , ))

3.2 问答全流程Pipeline

   Ollama   StrOutputParser   RunnablePassthrough   PromptTemplate 
(docs): .join(doc.page_content for doc in docs)
llm = Ollama(
    model=,
    base_url=)
prompt_tmpl = 
prompt = PromptTemplate.from_template(prompt_tmpl)
retriever = vector_db.as_retriever(search_kwargs={: })
rag_chain = (
    {: retriever | format_docs, : RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

3.3 评估

3.3.1 准备测试集

prediction_df = qa_df[qa_df[] == ][[, , , ]]

3.3.2 初始化Feedback函数

TruLens默认使用OpenAI的LLM,下面提供了自定义OpenAI兼容API以及Ollama的方式。

   ChatOpenAI
llm_chain = ChatOpenAI(
    api_key=os.environ[],
    base_url=os.environ[],
    model_name=)

也可以用Ollama提供的模型:

# from langchain.llms import Ollama
# llm_chain = Ollama(
#     model='qwen2:7b-instruct',
#     base_url="http://192.168.31.92:11434"
# )

使用方式:

llm_chain.invoke()
   App   Feedback      OpenAI   Langchain  LangchainProvider
provider = LangchainProvider(chain=llm_chain)
context = App.select_context(rag_chain)

f_groundedness = (
    Feedback(provider.groundedness_measure_with_cot_reasons, name = )
    .on(context.collect())
    .on_output()
)
f_answer_relevance = (
    Feedback(provider.relevance_with_cot_reasons, name = )
    .on_input_output()
)
f_context_relevance = (
    Feedback(provider.context_relevance_with_cot_reasons, name = )
    .on_input()
    .on(context)
    .aggregate(np.mean)
)
✅ In Groundedness, input source will be set to __record__.app.first.steps__.context.first.invoke.rets[:].page_content.collect() .
✅ In Groundedness, input statement will be set to __record__.main_output or `Select.RecordOutput` .
✅ In Answer Relevance, input prompt will be set to __record__.main_input or `Select.RecordInput` .
✅ In Answer Relevance, input response will be set to __record__.main_output or `Select.RecordOutput` .
✅ In Context Relevance, input question will be set to __record__.main_input or `Select.RecordInput` .
✅ In Context Relevance, input context will be set to __record__.app.first.steps__.context.first.invoke.rets[:].page_content .

3.3.3 创建TruLens记录器

   TruChain, Tru
tru = Tru()
tru.reset_database()

注意:

如果在同一个目录下有多个版本在迭代,并且希望TruLens把这些迭代的评估结果都记录下来,那么不要调用tru.reset_database(),否则之前的评估记录会被清除。评估记录默认保存在一个名为default.sqlite的SQLite数据库中。

? Tru initialized with db url sqlite:///default.sqlite .
? Secret keys may be written to the database. See the `database_redact_keys` option of `Tru` to prevent this.
tru_recorder = TruChain(
    rag_chain,
    app_id=,
    feedbacks=[f_answer_relevance, f_context_relevance, f_groundedness]
)
answer_dict = {}
for idx, row in tqdm(prediction_df.iterrows(), total=(prediction_df)):
    with tru_recorder as recording:
        uuid = row[]
        question = row[]
        answer = rag_chain.invoke(question)
        answer_dict[question] = {: uuid, : row[], : answer }

3.4 检查结果

tru.get_leaderboard()

total_cost为0是因为所用的模型不是OpenAI的,关联不上价格。

tru.run_dashboard()

启动后会出现类似下面的信息:

Starting dashboard ...
Config file already exists. Skipping writing process.
Credentials file already exists. Skipping writing process.
Dashboard already running at path: Network URL: http://192.168.31.92:48913 
Popen: returncode: None args: ['streamlit', 'run', '--server.headless=True'...

用浏览器访问上面代码运行后出现的地址即可。

在Leaderboard里可以直观地看到RAG triad三个评估指标。从这个指标能明显看出当前整个流程的薄弱环节。点击Evaluations可以看到每条测试数据的三个评估指标,再点击测试数据还能查看整个Trace的详情。