大型语言模型在回答问题方面表现得相当不错,但它们有一个很大的局限性:它们无法了解用户私人的文档内容。
如果你上传了像公司政策、研究论文或合同这样的PDF文件,那么模型就无法自动读取这些内容,除非你主动提供这些信息给模型。
这时,检索增强生成,即RAG就派上用场了。RAG可以将语言模型与用户自己的数据结合起来使用。与其让模型自行猜测,不如先从文档中提取出相关的信息,然后再利用这些数据来生成答案。
在这篇文章中,你将学习如何使用RAG与自己的PDF文件进行交互。你将使用LangChain来构建后端服务,同时创建一个简单的React用户界面,以便用户可以提出问题并获取答案。
你需要具备基本的Python和JavaScript技能,并且熟悉React和REST API。虽然对语言模型的了解以及向量搜索的知识不是必需的,但如果有这种知识的话会更有帮助。
我们解决的是什么问题?
想象一下,你有一份包含数百页内容的PDF文件。手动搜索效率很低,而把文本复制粘贴到ChatGPT中也并不实用。
你可以提出一些简单的问题,比如“公司的休假政策是什么?”或者“这份合同中关于终止合同的条款是什么?”
普通的语言模型无法正确回答这些问题,因为它们从未见过你的PDF文件。而RAG通过在生成答案之前先进行检索操作来解决这个问题。
系统首先找到PDF文件中相关的内容,然后将这些内容作为答案的上下文来辅助模型生成答案。
什么是检索增强生成?
首先,将文档分割成小块。每个小块都会被转换为向量嵌入形式,这些嵌入会被存储在向量数据库中。
当用户提问时,这个问题也会被转换成嵌入形式。系统会在向量数据库中进行搜索,找出最相似的片段。
然后,这些片段会与问题一起被发送给语言模型。模型仅使用这些上下文来生成答案。
这种方法使得答案更加基于文档内容,从而减少了幻觉的产生。
该系统由四个主要部分组成:
- PDF加载器负责读取文档。
- 文本分割器负责将文档分割成小块。
- 嵌入模型负责将文本转换为向量,并将其存储到向量库中。
- 语言模型则根据检索到的片段来生成答案。
前端是一个简单的React界面,它可以将用户的提问发送到后端API,并显示结果。
这种自定义的RAG开发方式可以帮助企业构建内部工具,直接使用自己的私有数据,而不需要将其发送到大语言模型中。
使用LangChain设置后端
我们将使用Python和LangChain来实现后端功能。后端会加载PDF文件,创建向量库,并提供一个API来回答问题。
安装依赖项
首先安装所需的库。
pip install langchain langchain-community langchain-openai faiss-cpu pypdf fastapi uvicorn
这个配置中使用了FAISS作为本地向量存储,而OpenAI则用于嵌入处理和聊天功能。之后,你可以更换其他模型来满足需求。
加载和分割PDF文件
第一步是加载PDF文件,并将其分割成足够小的块,以便于进行嵌入处理。
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = PyPDFLoader("document.pdf")
documents = loader.load()
text_splitter =RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = text_splitter.split_documents(documents)
分块处理非常重要。如果块太大,嵌入效果就会变差;如果块太小,则会导致上下文丢失。
创建嵌入和向量存储
接下来,将各个块转换为嵌入形式,并将其存储在FAISS中。
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from Documents(chunks, embeddings)
这一步通常只需要执行一次。在实际应用中,需要将向量存储持久化到磁盘上。
创建检索链
现在可以创建一个基于检索的问答链。
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
llm = ChatOpenAI(
temperature=0,
model="gpt-4o-mini"
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(
searchkwargs={
"k": 4
}
),
return_source_documents=False
)
retriever模块负责找到最匹配的片段,而语言模型则根据这些片段来生成答案。
使用FastAPI暴露API
现在,可以将这一逻辑封装成API,这样React应用程序就可以使用它了。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class QuestionRequest(BaseModel):
question: str
@app.post("/ask")
def ask_question(req: QuestionRequest):
result = qa_chain.run(req.question)
return {"answer": result}
可以使用以下命令运行服务器:
“`
uvicorn main:app –reload
“`
此时,你的后端已经准备好了。
构建简单的React聊天界面
接下来,可以构建一个简单的React界面,用于将问题发送到后端,并显示答案。
你可以使用任何版本的React来进行开发。简单的Vite或Create React App项目都可以满足需求。
在主组件中,可以管理问题的输入和答案的状态。
import { useState } from "react";
function App() {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
const [loading, setLoading] = useState(false);
async function askQuestion() {
setLoading(true);
const res = await fetch("http://localhost:8000/ask", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ question })
});
const data = await res.json();
setAnswer(data.answer);
setLoading(false);
}
return (
与您的PDF文件进行对话吧!
);
}
export default App;
这个界面很简单,但是非常实用。用户可以输入问题,然后将答案显示出来。建议使用最新版本的React,以避免潜在的漏洞。
整个流程是如何运作的?
当应用程序启动时,后端已经处理了PDF文件,并创建了向量存储。当用户输入问题时,React应用程序会将问题发送到后端。
后端会将问题转换为嵌入形式,然后在向量存储中查找类似的片段。这些片段会被传递给语言模型,模型仅基于这些片段来生成答案。
生成的答案会被返回给前端,并显示给用户。
为什么这种方法效果很好?
RAG之所以效果好,是因为它让答案基于真实的数据。模型并不是在猜测,而是直接从文档中获取信息。
此外,这种方式还具有良好的扩展性。你可以添加更多的PDF文件,重新索引它们,并使用相同的聊天界面。如果需要,还可以将FAISS替换为托管的向量数据库。
另一个好处是控制力更强。你可以决定模型能看到哪些数据,这对于处理私人或敏感的文件非常重要。
常见的改进方法
你可以以多种方式改进这个系统。例如,可以将向量存储持久化,避免每次重启时都需要重新构建。还可以在答案中添加文档引用。此外,还可以实现流式响应,以提供更好的聊天体验。
你还可以添加身份验证功能,允许用户通过UI上传新的PDF文件,或者支持多个用户共享一份文档。
总结
使用检索增强生成技术与PDF文件进行对话是现代语言模型的最实用应用之一。它可以将静态文档转化为互动式的知识来源。
借助LangChain来处理检索任务,再加上简单的React界面,你就可以用很少的代码来构建有用的系统。同样的模式也可以应用于人力资源政策、法律文件、技术手册或研究报告等领域。
一旦你理解了整个流程,就可以将其应用到许多现实世界的问题上,其中答案需要从可信的文档中获取,而不是仅仅来自模型的记忆。
