外观
RAG
RAG,全称Retrieval-Augmented Generation,即检索增强生成。
过去的模型都是基于已有的数据训练的,没有实时性,无法回答最新的问题,也没有能力回答企业私有的知识。
为了处理私有知识,我们可以利用它们,通过开源大模型进行二次微调,也可以通过大模型搭建 RAG 系统。
流程

有两个主要步骤:语义搜索和生成输出。在语义搜索步骤中,从知识库中找到与要回答的查询最相关的部分内容。然后,在生成步骤中,将使用这些内容来生成响应。
完整的RAG应用流程主要包含两个阶段:
数据准备阶段:数据提取——>文本分割——>向量化(embedding)——>数据入库 应用阶段:用户提问——>数据检索(召回)——>注入Prompt——>LLM生成答案
代码
将pdf转换为向量数据库
from langchain_community.document_loaders import PyMuPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import FAISS # 向量数据库
def get_vector():
# 第一步:加载文档
loader = PyMuPDFLoader("物流信息.pdf")
# 将文本转成 Document 对象
data = loader.load()
print(f'data-->{data}')
print(f'len(data):{len(data)}')
# # 第二步:切分文本
text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=20)
# # 切割加载的 document
split_docs = text_splitter.split_documents(data)
print("split_docs size:", len(split_docs))
print(split_docs)
#
# # 第三步:初始化 hugginFace 的 embeddings 对象
embeddings = OllamaEmbeddings(model="mxbai-embed-large")
#
#
# # 第四步:将 document通过embeddings对象计算得到向量信息并永久存入FAISS向量数据库,用于后续匹配查询
db = FAISS.from_documents(split_docs, embeddings)
db.save_local("./faiss/wuliu")
if __name__ == '__main__':
result = get_vector()构建RAG本地问答系统
# coding:utf-8
# 导入必备的工具包
import time
from local_db import *
from langchain import PromptTemplate
from langchain_community.llms import Ollama
# 加载FAISS向量库
embeddings = OllamaEmbeddings(model="mxbai-embed-large", temperature=0)
db = FAISS.load_local("faiss/wuliu", embeddings, allow_dangerous_deserialization=True)
# db = FAISS.load_local("faiss/wuliu", embeddings)
start_time = time.time()
def get_related_content(related_docs):
# print(f'related_docs--》{related_docs}')
related_content = []
for doc in related_docs:
# print(f'doc.page_content--》{doc.page_content}')
related_content.append(doc.page_content.replace("\n\n", "\n"))
# print(f'related_content列表状态--》{related_content}')
return "\n".join(related_content)
def define_prompt():
question = '我的快递出发地是哪?预计几天的时间到达?'
docs = db.similarity_search(question, k=2)
related_content = get_related_content(docs)
# print('*' * 80)
# print(f'related_content字符串状态-->{related_content}')
# print('*'*80)
PROMPT_TEMPLATE = """
基于以下已知信息,简洁和专业的来回答用户的问题。不允许在答案中添加编造成分。
已知内容:
{context}
问题:
{question}"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=PROMPT_TEMPLATE,)
my_pmt = prompt.format(context=related_content,
question=question)
return my_pmt
def qa():
model = Ollama(model="qwen2.5:7b")
# print(f'model-->{model}')
my_pmt = define_prompt()
print(f'my_pmt--》{my_pmt}')
# result = model.invoke(my_pmt)
# return result
if __name__ == '__main__':
result = qa()
print(result)
end_time = time.time()
print(end_time-start_time)RAG 的 Query 改写
在RAG(检索增强生成)流程中,第一步通常是对用户的提问(query)进行改写。这是因为用户提问的方式与他们期望的答案之间可能存在差距。由于每个用户的提问方式可能千差万别,因此对问题进行改写可以帮助系统更好地理解问题并返回更相关的答案,从而提升RAG系统的鲁棒性和扩展性。
用户提出的问题通常存在两类问题:
- 信息不完整:用户的提问没有表达清楚所有的关键信息。
- 噪声问题:提问中可能包含了与答案无关的内容。
信息不完整
历史信息扩写
在对话中,前后文是相互关联的。如果仅凭当前的query进行检索,可能会导致召回精度大幅下降,因为query中往往缺少重要的上下文信息。以下是一个具体的例子:
用户: 华为meta70手机的性能怎么样?
系统: 华为meta70手机搭载了强大的处理器和先进的摄像系统,性能表现非常优秀。
用户: 与上一代相比,它有哪些改进?
系统: 华为meta70相较于meta60在处理器性能、摄像头优化和电池续航方面都有显著提升。
用户: 摄像头方面具体改进了什么?
--改写前: 摄像头方面具体改进了什么?
--改写后: 华为meta70手机的摄像头相比meta60有哪些具体改进?
关键词扩写
用户在搜索时常常输入的关键词较为简短,并且缺乏足够的上下文信息,这会影响语义检索(向量检索)的效果,导致召回的相关性较低。因此,需要对用户的原始关键词进行扩展和丰富。
用户输入: “机器学习 实践”
改写后的 Query: “机器学习在实际应用中的案例有哪些?哪些工具和方法适用于机器学习实践?
伪答案改写
伪答案改写通过在原始查询中加入一种假设性答案,来增强查询的语义丰富性,从而提高检索或回应的精准度。伪答案并非真实的答案,而是一个设想的内容,用于帮助系统更好地理解并检索相关信息。
用户输入: “如何提高企业的市场竞争力?”
改写后的 Query: “如何提高企业的市场竞争力?比如通过创新产品、优化营销策略或提升客户服务等手段。”
伪答案目的:通过提供假设性的提升方式,丰富查询的语义信息,从而增强系统在应对复杂问题时的检索能力。
缩写词改写
用户在查询时常常使用缩写,而许多相关文档通常会使用完整的术语,因此需要对缩写进行扩展,以便更好地匹配相关内容。
用户输入: “VR 技术在教育中的应用”
改写后的 Query: “虚拟现实(Virtual Reality)技术在教育中的应用有哪些?可以举一些实际的应用案例吗?”
噪声问题
一般去噪改写
通过去除查询中的无关成分(如多余的修饰语、模糊表达或不相关的背景信息),简化并优化查询,使其更加精确和可操作。这种方法有助于提高检索的准确性和效率。
用户输入: “我最近在准备面试,但对于算法的理解还不太够,能推荐一些有效的学习资源吗?”
改写后的 Query: “有哪些有效的学习资源可以帮助提高算法理解?”
分析:去除与问题无关的背景信息 “我最近在准备面试,但对于算法的理解还不太够”。 直接提取核心意图 “帮助提高算法 理解”。
关键词改写
这是一种专注于提取核心关键词并去除噪声的查询重写方法。通过识别查询中的关键内容,并排除冗余信息(如停用词、语气词和多余的描述),使查询更加简洁明了,从而提高检索效率和准确性。该方法特别适用于关键词检索召回,如 BM25 检索算法。
用户输入:“关于 Java 中的线程池,常见的实现方式有哪些?”
改写后:“Java 线程池 常见实现方式”
子查询改写
当查询涉及对比多个实体时,可能会产生相互干扰的情况。对比类查询通常包含多个元素,这些元素如果直接放在一个查询中,可能会导致信息重叠,影响检索的准确性。为了避免干扰,可以将对比类查询拆分成多个独立的查询,每个查询聚焦于其中一个实体,这样可以减少信息混淆,获得更准确的结果。
用户输入:“C++ 和 Go 哪个更适合做系统编程?”
拆分后的查询:“C++ 适合做系统编程的优点有哪些?”;“Go 适合做系统编程的优点有哪些?”
拆分原因:直接对比 C++ 和 Go 的优劣,可能使得系统无法有效地提取每种语言的特点。拆分后,系统可以分别检索 C++ 和 Go 在系统编程中的优点,避免信息混乱。
Prompt 示例
以下是一个开源rag系统,问题改写的示例,仅供参考:
您是查询扩展方面的专家,能够生成问题的释义。
我无法直接使用用户的问题从知识库中检索相关信息。
您需要通过多种方式扩展或释义用户的问题,例如使用同义词/短语、完整地写出缩写、添加一些额外的描述或解释、改变表达方式、将原始问题翻译成另一种语言(英语/中文)等。
并返回 5 个版本的问题,其中一个来自翻译。
只需列出问题。不需要其他单词。
评估
当我们为某个真实线上系统开发了检索增强生成 (RAG) 应用,那么在此应用正式上线提供服务前,我们需要评估RAG 的表现到底是怎样的。如果发现现有的 RAG 效果不够理想,可能需要一些新的 RAG 算法流程来改进。在这之前,就需要对 RAG 流程进行评估,得到评估指标,然后才能进行自动化对比,观察改进的流程是否真的有效。
RAGAS (Retrieval Augmented Generation Assessment) 我们一般称为 Automated Evaluation of Retrieval Augmented Generation,即检索增强生成的自动评估。Ragas是一个大模型评测框架,可以评估检索增强生成(RAG)的效果,帮助分析模型的输出,了解模型在给定任务上的表现。Github地址: https://github.com/explodinggradients/ragas。
RAGAS的评估主要基于两个方向:检索部分和生成部分,那么针对不同的部分评估的指标也会有所区分。接下来,我们将分别介绍RAGAS评估框架所需的数据,评估指标,实际用例等。
数据说明
最开始的 RAGAs 在评估数据集时,不必依赖人工标注的标准答案,而是通过底层的大语言模型 (LLM) 来进行评估。所以只需要一个带有问题-答案对的评估数据集(QA 对),如:https://huggingface.co/datasets/m-ric/huggingface_doc的具体字段:
- question:作为 RAG 管道输入的用户查询,输入。
- answer:从 RAG 管道生成的答案,输出。
- contexts:从用于回答question外部知识源中检索的上下文。
- ground_truths:question的基本事实答案。这是唯一人工注释的信息。
评估指标

评估检索(context)的指标:提供了上下文相关性(context_relevancy)和上下文召回率(context_recall),这些可以衡量你的检索系统的性能,即检索的段落是否相关。
评估生成(answer)的指标:提供了忠实度(faithfulness),用以衡量生成的信息是否准确无误;以及答案相关性(answer_relevancy),用以衡量答案对问题的切题程度,即模型生成的答案是否恰当。
上下文相关性(context relevance)
作用:指的是检索到的上下文应该只包含回答问题所需的信息,这个指标旨在惩罚包含冗余信息的情况。比率越高,表示检索到的上下文与问题的相关性越强。
为了估算上下文的相关性,我们用 LLM 从上下文 C(q) 中抽取对回答问题 q 至关重要的句子 S。提取的prompt如下:
请从提供的{上下文}中提取可能有助于回答以下{问题}的相关句子。
如果没有找到相关句子,或者你认为问题无法从给定上下文中得到回答,则返回短语“信息不足”。
在提取候选句子时,你不得更改给定上下文中的句子。
然后,在RAGAS中,通过下面的公式计算相关性:
CR=total number of sentences in c(q)number of extracted sentences
上下文召回率(context recall)
作用:衡量检索到的上下文(contexts)与真实答案(ground_truths)的匹配程度。
该指标通过问题、标注答案和检索到的上下文计算,分数范围在0到1之间,得分越高表示性能更好。
要从真实答案中估计上下文召回率,需要分析真实答案中的每个声明(claim),以确定它是否可以归因于检索到的上下文。理想情况下,真实答案中的所有声明都应可归因于检索到的上下文。
假设真实答案(Reference Answer)为:
“2010年世界杯的冠军是西班牙。”
“西班牙在决赛中以1-0击败了荷兰。”
RAG检索到的上下文(Retrieved Context)为:
“西班牙在2010年世界杯的决赛中击败了荷兰。”
“2010年世界杯的冠军是西班牙,西班牙队首次赢得世界杯。”
步骤具体实施:
声明1:"2010年世界杯的冠军是西班牙。"
输入给GPT-3.5:检索到的上下文 + 声明1。
GPT-3.5检查上下文是否包含“2010年世界杯的冠军是西班牙”。
结果:GPT-3.5发现上下文包含该信息,因此该声明“召回”。
声明2:"西班牙在决赛中以1-0击败了荷兰。"
输入给GPT-3.5:检索到的上下文 + 声明2。
GPT-3.5检查上下文是否包含“西班牙在决赛中以1-0击败了荷兰”。
结果:GPT-3.5确认上下文中提到西班牙击败了荷兰,但未提到具体的比分1-0,因此该声明“未召回”。
计算召回率:总声明数:2(声明1和声明2)。
被召回的声明数:1(声明1被召回,声明2未召回)。
Context Recall=0.5
公式:
context recall=∣Number of claims in GT∣∣GT claims that can be attributed to context∣
分子:GT claims that can be attributed to context,表示在真实答案(GT)中的论断中,有多少是可以归因于检索到的上下文的。换句话说,这些论断在检索到的上下文中找到了支持或依据。
分母:Number of claims in GT 表示真实答案中论断的总数量。
忠实度(faithfulness)
作用:指答案确实是根据给定的上下文得到的。这对于避免错觉并确保检索到的上下文可以用作生成答案的依据非常重要。
如果分数低,它表明 LLM 的回应没有遵循检索到的知识,提供幻觉式答案的可能性增加。
为了估计忠实度,我们首先使用 LLM 提取一组陈述,S(a(q))。方法是使用以下提示:
给定一个问题和答案,从给定答案的每个句子中创建一个或多个陈述。
问题:[问题]
答案:[答案]
生成 S(a(q)) 后,LLM 判断每个陈述 si 是否可以从 c(q) 中推断出来。这个验证步骤使用以下提示进行:
考虑给定的上下文和以下陈述,然后确定它们是否由上下文中的信息支持。在得出结论(是/否)之前,为每个陈述提供简要解释。在最后以给定格式为每个陈述提供最终结论。不要偏离指定的格式。
陈述:[陈述 1]
...
陈述:[陈述 n]
最终的忠实度分数,F,计算为 F=|V|/|S|,其中 |V| 表示 LLM 支持的陈述数量,|S| 表示陈述的总数。
答案相关性(answer relevancy)
作用:生成的答案与查询之间的相关性。分数越高,相似性越好。
为了估计答案的相关性,我们提示 LLM 根据给定的答案生成 n 个潜在问题 qi,如下所示:
为给定答案生成一个问题。
答案:[答案]
具体步骤
- 对给定答案,提示大型语言模型(LLM)生成基于该答案可能的 n个问题 qi。
- 使用embedding模型获取所有问题的嵌入表示。
- 对于每个生成的问题 qi,计算它与原始问题 q 之间的相似度 sim(q,qi)。这里的相似度是通过计算对应嵌入之间的余弦相似度来得到的。
- 答案相关性得分(平均相似度) AR 通过以下公式计算:
公式:
AR=n1i=1∑nsim(q,qi)