Hi,我是 Hyde,今天的话题是 RAG(Retrieval-Augmented Generation),一种用于优化大模型效果的方法,翻译成中文就是检索增强生成。
在之前的文章《最优化大模型效果的方向和思考》中,我们提到当前的大模型仍然存在诸多不足,例如受限于预训练数据,模型无法获知私有数据或训练节点以后的新知识,因此对于这类场景下的推理效果往往不佳,也存在着幻觉问题。面对此类问题,我们就可以选择使用 RAG 技术来弥补甚至解决。
RAG 的核心思想可以认为是将信息显式地提示给大模型以增强模型的生成能力和准确性,也就是说我们可以通过检索相关文档并将其作为上下文提供给模型,让模型从上下文中学习到模型不知道的知识,从而生成更准确的回答。
那么要构建一个 RAG 系统要做些什么呢?RAG 中有哪些组件?我们又可以从哪些方面来优化 RAG 的效果?这些就是我希望与大家探讨的内容,就让我们从最基本的 RAG 系统 —— Naive RAG 开始吧。
Naive RAG
Naive RAG 是最早也是最简单的 RAG 系统,只做了三件事情:
第一,Indexing:将知识存储起来。
第二,Retrieval:如何从大量的知识中找到我们需要的内容。
第三,Generation:如何结合用户的提问与检索到的知识,让模型生成有价值的答案。
下图是 Naive RAG 过程的一个示例:
Indexing 索引
建立 RAG 系统的第一步是建立索引,也就是将知识存储起来的过程。在这一步中,我们需要将文档划分为多个块(chunk),使用 embedding 模型计算每个段落的嵌入向量,并存储在向量数据库中。
这里有很多关键词,例如分块、embedding模型、向量数据库,这些都是索引过程中用到的一些关键的技术。
分块的原因在于一个文档中往往包含大量的信息,而生成回答时可能只需要用到其中的一部分内容。通过合理的分块,我们就可以将有用的片段返回,提高检索精度。同时,分块也能够确保输入提示词的长度符合上下文窗口限制。
向量数据库是一种特殊的数据存储方式。有别于关系型数据库和图数据库,向量数据库存储的往往是非结构化的数据(文本、图片等),并且使用向量间的相似程度来进行查询。
举一个直观的例子,我们往向量数据库中存入三个名词:“苹果”、“西瓜”和“汽车”,同时存储的还有这三个名词的嵌入向量。根据小学二年级就学过的线性代数的知识,向量可以用高维空间中的点坐标来表示。
例如,下图是一个三维的向量空间,因为“苹果”和“西瓜”同属于水果,因此相较于“汽车”,“苹果”和“西瓜”的坐标更加接近,嵌入向量也更相似。如果我们使用“水果”这个词进行查询,那么根据语义间的接近程度,我们查询到的就会是“苹果”或者“西瓜”,而不是“汽车”。
Embedding 模型就是一种专门为语句创建嵌入向量的模型,我们输入一个词语或段落,就会生成一个多维向量,例如 bge-m3 模型就会将词语转化为一个 1024 维的嵌入向量。
Retrieval 检索
明白什么是向量数据库以后,理解检索过程就非常了简单了。用户的提问经过 embedding 模型转换为嵌入向量后,再根据相似度从向量数据库中检索出最相关的 K 个文本块。这些文本块将作为参考信息合并到生成阶段的提示词中,来增强模型的生成。
Generation 生成
生成阶段的工作非常简单,通过将包含参考信息的上下文输入到模型,然后由模型基于提供的信息回答问题。如果有历史对话,也可以将其合并到提示词中,用于多轮对话。
生成阶段的提示词例如:
你是一个知识问答助手,你需要根据参考文档和历史对话,生成有用的回答。
## 历史对话
Question:xxxxx,
Answer:xxxxx,
Question:xxxxx,
Answer:xxxxx
...
## 参考内容
chunk1,
chunk2,
...
通过以上三件事情,大模型就具有了获取外部知识的能力,因此在包含私有数据和近期知识的场景下,模型的推理能力得到了飞跃性的提升。但 Naive RAG 毕竟还是非常 Naive 的,虽然简单明了,但也存在着许多问题。
- 检索质量低: 使用长文本作为索引时,无法有效地突出主题,核心知识被大量无关信息淹没。而直接用用户的原始查询进行检索,无法准确抓住核心需求,导致用户查询和知识索引之间的匹配度不高,从而降低了检索的整体质量。
- 生成质量差: 当未能检索到相关知识或检索到的知识质量不佳时,大模型在回答私域问题时容易产生幻觉,或给出空洞无用的回答。这使得知识库无法发挥其应有的价值。
- 增强过程难: 将检索到的信息整合到不同任务中可能具有挑战性,有时会导致输出不连贯或不一致。此外,生成模型可能过度依赖增强信息,只是简单复述检索内容,而没有加入有洞察力或综合的信息。
这就需要我们针对 RAG 的各个环节进行优化,逐步克服 Naive RAG 的局限性,这就我们以后要讨论的主题——Advanced RAG。
构建 Naive RAG 系统
现在已经有很多成熟的框架可以快速搭建 Naive RAG 应用,例如 LangChain、LlamaIndex 等。甚至使用一些基于大模型的 bot 平台和工作流平台(例如Coze、Dify)也可以快速构建出基于 RAG 的应用。
我们这里以 LangChain 文档中的 Build a Retrieval Augmented Generation (RAG) App
为例,介绍如何使用 LangChain 构建一个简单的 Naive RAG 应用。
在开始前,我们需要一个 python 环境,并使用安装相应的 python 包,这里不再赘述。
案例简介
案例获取了 OpenAI 研究员 Lilian Weng 博客中的一篇文章,并建立了文章的向量数据库,用户可以通过询问大模型来获取文章内容的详细信息。
1. 文档加载
由于我们的目标文档是一篇网页博客,因此使用 WebBaseLoader
加载博客页面的 HTML,并使用
BeautifulSoup
解析博客的正文。
import bs4
from langchain_community.document_loaders import WebBaseLoader
# 使用 WebBaseLoader 加载网页的 HTML
# 使用 BeautifulSoup 将网页解析为文本
bs4_strainer = bs4.SoupStrainer(class_=("post-title", "post-header", "post-content"))
loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs={"parse_only": bs4_strainer},
)
# 加载网页的文本内容到 docs 变量中
docs = loader.load()
2. 文档分块
使用 RecursiveCharacterTextSplitter
进行文本分割,它将使用常见的分隔符(如换行符)递归地分割文档,直到每个块的大小合适。
在本例中,文章将被分割为最大 1000 个字符的文档块,块与块之间有 200 个字符的重叠。
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 构建文本分割器,将文档分割为 1000 个字符的块,块与块之间有 200 个字符的重叠
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=200, add_start_index=True
)
all_splits = text_splitter.split_documents(docs)
- 构建索引
索引这一步需要引入 embedding 模型和向量数据库,案例中使用了 OpenAI 的 embedding 模型及 Chroma 向量数据库.
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
vectorstore = Chroma.from_documents(documents=all_splits, embedding=OpenAIEmbeddings())
4. 构建检索器
案例中使用了 VectorStoreRetriever
检索器,即通过适量存储来检索文档。案例将检索模式设置为 similarity,k 值设为 6,这意味着检索器将返回向量存储中的相似度最高的前 6 个文本块。
# 构建检索器,并将检索模式设置为 similarity,每次返回前 6 个结果
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 6})
5. 构建 RAG 链
为了实现生成这一步骤,我们需要构建 RAG 链。所谓的 RAG 链就是一个序贯执行的过程,将检索、构建提示词和生成答案等步骤通过串接起来,实现一个完成的 RAG 过程。
RAG 链条上有许多组件,有一些我们已经创建好了,例如检索器。而另一些则需要在接下来的过程中创建,比如大模型实例、提示词以及一些输出格式的解析器。
5.1 创建大模型实例
案例中使用了 OpenAI 的 “gpt-3.5-turbo-0125” 模型,因为 LangChain 做了许多集成方面的工作,我们可以非常方便的将其切换为国内的一些模型,例如阿里的 Qwen 系列模型、百度的文心系列模型等.
import getpass
import os
os.environ["OPENAI_API_KEY"] = getpass.getpass()
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
5.2 构建提示词
LangChain 框架提供了大模型应用开发相关完整工具链条和活跃度社区生态,在此案例中调用了 LangChain 社区中的提示词 rlm/rag-prompt
,并将检索器的检索结果拼接至提示词中。
from langchain import hub
prompt = hub.pull("rlm/rag-prompt")
example_messages = prompt.invoke(
{"context": "filler context", "question": "filler question"}
).to_messages()
该提示词看起来是这个样子:
[HumanMessage(content=
"""
You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Question: {filler question}
Context: {filler context}
Answer:
"""
)]
5.3 将组件拼接成 RAG 链条
组件都准备妥当,就要可以开始拼装 RAG 链条的时候了,这里我们使用了 LangChain 中的 LECL 表达式来完成这项工作。
在大模型组件后,案例还添加了一个 StrOutputParser
解析器,用来将大模型的结果转化为 Json 格式。
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 为文档块之间添加空行
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 通过 LECL 表达式组装 RAG 链条
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser() # 输出解析器,负责将大模型的输出结果解析为 json 格式
)
6. 万事俱备,来试试效果吧
我们可以参照如下的方式调用 RAG 链,并获得回答的结果,一个简单的 Naive RAG 应用就搭建完成了。
# 调用 RAG 链条,生成回答结果
for chunk in rag_chain.stream("What is Task Decomposition?"):
print(chunk, end="", flush=True)
7. 更进一步的工作
现在我们有一个非常简单和朴素的 Naive RAG APP,虽然可以运行,但是还存在许多问题。
第一,这个 APP 只能读取固定的一篇网页博客文章,如果我们想加载自定义的 PDF 文档就无法实现了。
第二,只支持单轮对话,大家可以尝试更改代码和提示词,来增加多轮对话的功能。
最后,这个 APP 没有前端界面,或许我们可以将它封装成 API ,然后做一些前端的工作来搭建页面。或者使用 Gridio、Streamlit、Chainlit 等工具来构建前端页面。
OK,非常感谢你的观看,我是 Hyde,我们下次见。
如何学习AI大模型?
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓