文章目录
- 一、概述
- 二、什么是 RAG
- 三、概念
- 1、Indexing
- 2、Retrieval and generation
- 四、设置
- 1、下载安装 langchain
- 2、LangSmith
- 五、一个简单的RAG后端
- 1、Load
- 2、Split
- 3、Embedding and Store
- 4、Retrieval
- 5、Generate
一、概述
LLM 支持的最强大的应用程序之一是 复杂的问答 (Q&A) 聊天机器人。这些应用程序可以回答有关特定源信息的问题。这些应用程序使用一种称为 检索增强生成 (RAG) 的技术。
这里将展示如何 基于文本数据源 构建一个简单的问答应用程序。在此过程中,我们将介绍典型的问答架构,并重点介绍更多高级问答技术的资源。
我们还将了解 LangSmith 如何帮助我们跟踪和理解我们的应用程序。随着我们的应用程序变得越来越复杂,LangSmith 将变得越来越有用。
二、什么是 RAG
RAG 是一种利用附加数据增强 LLM 知识的技术。
LLM 可以推理广泛的主题,但他们的知识 仅限于 他们接受训练的 特定时间点 的 公共数据。如果想构建能够推理 私有数据 或 模型截止日期后引入的数据 的 AI 应用程序,则需要使用模型所需的特定信息来增强模型的知识。将适当的信息引入模型提示的过程称为 检索增强生成 (RAG)。
LangChain 有许多组件,旨在帮助构建问答应用程序以及更广泛的 RAG 应用程序。
注意:这里重点介绍非结构化数据的问答。如果您对结构化数据的 RAG 感兴趣,请查看 关于通过 SQL 数据进行问答的教程。
三、概念
一个典型的 RAG 应用包含两个主要的部分:
- Indexing (索引):从源中提取数据并对其进行索引的管道。这通常是离线进行的。
- Retrieval 和 generation (检索和生成):实际的 RAG 链,它在运行时接受用户查询并从索引中检索相关数据,然后将其传递给模型。
从原始数据到答案的最常见完整序列如下所示:
1、Indexing
- Load (加载):首先我们需要加载数据。这可以通过 DocumentLoaders 完成。
- Split (拆分):Text splitters 文本拆分器 将大型文档拆分成较小的块。这对于索引数据和将其传递给模型都很有用,因为大块数据更难搜索,并且不适合模型的有限上下文窗口。
- Store (存储):我们需要一个地方来存储和索引我们的拆分,以便以后可以搜索它们。这通常使用 VectorStore 和 Embeddings 模型来完成。
2、Retrieval and generation
- Retrieval (检索):给定用户输入,使用检索器从存储中检索相关分割。
- Generation (生成):ChatModel/LLM 使用 包含问题和检索到的数据的 提示 生成 答案。
四、设置
1、下载安装 langchain
要安装 LangChain,请运行:
pip install langchain # 在python 中
2、LangSmith
点击跳转至 LangSmith 的使用教程
五、一个简单的RAG后端
1、Load
我们首先用 LangChain 中的 document_loaders 来加载各种格式的文本文件。这些文件 (也就是我们的私有知识库) 我放在了 documents 这个目录中,如果你创建自己的文件夹,就要调整一下代码中的目录。
在这一步中,我们从 pdf、word 和 txt 文件中加载文本,然后将这些文本存储在一个列表中。(注意:可能需要安装 PyPDF、Docx2txt 等库)
import os
os.environ["OPENAI_API_KEY"] = '你的Open AI API Key'
# 1.Load 导入Document Loaders
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader
# 加载Documents
base_dir = '.\documents' # 文档的存放目录
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.docx'):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.txt'):
loader = TextLoader(file_path)
documents.extend(loader.load())
2、Split
接下来需要将加载的文本分割成更小的块,以便进行嵌入和向量存储。这个步骤中,我们使用 LangChain 中的 RecursiveCharacterTextSplitter 来分割文本。
# 2.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
3、Embedding and Store
紧接着,我们将这些分割后的文本转换成嵌入的形式,并将其存储在一个向量数据库中。在这个例子中,我们使用了 OpenAIEmbeddings 来生成嵌入,然后使用 Qdrant 这个向量数据库来存储嵌入 (这里需要 pip install qdrant-client)。
向量数据库有很多种,比如 Pinecone、Chroma 和 Qdrant,有些是收费的,有些则是开源的。如果要使用其他的 向量数据库,最好去其官网看看文档中关于参数的说明。
# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain.vectorstores import Qdrant
from langchain.embeddings import OpenAIEmbeddings
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=OpenAIEmbeddings(), # 用OpenAI的Embedding Model做嵌入
location=":memory:", # in-memory 存储
collection_name="my_documents", # 指定collection_name
)
4、Retrieval
当内部文档存储到向量数据库之后,我们需要根据问题和任务来提取最相关的信息。此时,信息提取的基本方式就是 把问题也转换为向量,然后去和向量数据库中的各个向量进行比较,提取最接近的信息。
向量之间的比较通常基于向量的距离或者相似度。在高维空间中,常用的向量距离或相似度计算方法有 欧氏距离 和 余弦相似度。
- 欧氏距离,这是最直接的距离度量方式,就像在二维平面上测量两点之间的直线距离那样。在高维空间中,两个向量的欧氏距离就是 各个对应维度差的平方和的平方根。
- 余弦相似度,在很多情况下,我们更关心向量的方向而不是它的大小。例如在文本处理中,一个词的向量可能会因为文本长度的不同,而在大小上有很大的差距,但方向更能反映其语义。余弦相似度就是度量 向量之间方向的相似性,它的值范围在 -1 到 1 之间,值越接近 1,表示两个向量的方向越相似。
这两种方法都被广泛应用于各种机器学习和人工智能任务中,选择哪一种方法取决于具体的应用场景。
在这一步的代码部分,我们会创建一个聊天模型。然后需要创建一个 RetrievalQA 链,它是一个检索式问答模型,用于生成问题的答案。在 RetrievalQA 链中有下面两大重要组成部分:
- LLM 是大语言模型,负责回答问题。
- retriever (vectorstore.as_retriever()), 负责根据问题检索相关的文档,找到具体的“嵌入片”。这些“嵌入片”对应的“文档块”就会作为知识信息,和问题一起传递进入大模型。本地文档中检索而得的知识很重要,因为从互联网信息中训练而来的大模型不可能知道某个组织的私有知识库。
# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain.chat_models import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import MultiQueryRetriever # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链
# 设置Logging
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)
# 实例化一个大模型工具 - OpenAI的GPT-3.5
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
# 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(), llm=llm)
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)
上面代码中,MultiQueryRetriever 是一个高级检索器,它使用多查询策略来增强信息检索的效果。一般的 retriever 是不需要 llm 的,但是可以看到 MultiQueryRetriever 的实例化过程中不但用到了 retriever 还用到了 llm。
实际上,MultiQueryRetriever 使用语言模型生成多种查询,以提高信息检索的覆盖范围和准确性。
5、Generate
这一步是问答系统应用的主要 UI 交互部分,这里会创建一个 Flask 应用来接收用户的问题,并生成相应的答案,最后通过 index.html 对答案进行渲染和呈现。
在这个步骤中,我们使用了之前创建的 RetrievalQA 链来获取相关的文档和生成答案。然后,将这些信息返回给用户,显示在网页上。
# 5. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
# 接收用户输入作为问题
question = request.form.get('question')
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template('index.html', result=result)
return render_template('index.html')
if __name__ == "__main__":
app.run(host='0.0.0.0',debug=True,port=5000)
<body>
<div class="container">
<div class="header">
<h1>一个简单的 RAG</h1>
<img src="{{ url_for('static', filename='flower.png') }}" alt="flower logo" width="200">
</div>
<form method="POST">
<label for="question">Enter your question:</label><br>
<input type="text" id="question" name="question"><br>
<input type="submit" value="Submit">
</form>
{% if result is defined %}
<h2>Answer</h2>
<p>{{ result.result }}</p>
{% endif %}
</div>
</body>
最后跑起一个网页 http://127.0.0.1:5000/ 即可。最后的最后,给出一下项目的目录结构: