在人工智能领域,检索增强生成(RAG)技术正逐渐成为一种强大的工具,它允许大型语言模型(LLMs)访问外部知识库,从而实现更加精准和全面的信息检索与生成。本文将深入探讨如何利用RAG方法,结合谷歌的Gemini模型和Langchain框架,快速构建一个能够与PDF文件进行交互的智能机器人。选择Gemini模型的原因在于其API目前提供免费调用,这为广大的开发者提供了一个低成本的实验平台。RAG技术的核心在于其能够将检索模型与生成模型相结合,从而实现对私有或专有数据源信息的有效利用。一个典型的RAG检索过程包含多个关键步骤,例如,从用户问题出发,检索相关信息,并最终生成用户友好的答案。
一个典型的RAG系统由两个主要组件构成:检索器组件和生成器组件。检索器组件负责从外部数据源(如PDF、Word、Text等)检索与用户问题相关的信息,并将其提供给LLM,以便LLM能够更好地回答问题。生成器组件则利用检索到的相关信息,生成准确、完整且用户友好的答案。
本文将重点介绍如何利用Langchain框架的父文档检索器来构建检索器组件,以及如何利用谷歌的Gemini大模型来构建生成器组件。对于那些希望快速上手机器人应用程序开发的读者,可以参考之前撰写的关于使用Python快速开发各种聊天机器人应用的博客。此外,对于不熟悉谷歌Gemini模型的读者,可以查阅之前撰写的关于谷歌Gemini API应用的基础应用博客,以便快速了解Gemini API的使用方法。
在本文的最后,将分享完整的代码,供读者在此基础上进行完善,开发出符合自身需求的机器人。
环境配置
在开始之前,需要配置必要的开发环境。这里主要使用panel
和langchain
这两个Python包。可以使用pip命令进行安装:
pip install google-generativeai
pip install panel
pip install langchain
pip install chroma
如果在安装过程中遇到任何错误,请根据错误信息安装其他必要的Python包。
组件介绍
本文将使用谷歌Gemini模型组件以及基于Langchain的检索器组件,其中包括文档分割器组件、父文档检索器组件和向量数据库组件。此外,还需要使用Python的Web框架组件Panel。下面是这些组件的Python包导入:
import tempfile
import panel as pn
import param
from langchain.document_loaders import PyPDFLoader
from langchain.embeddings import HuggingFaceBgeEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.vectorstores import Chroma
from panel_chat_examples import EnvironmentWidgetBase
import google.generativeai as genai
这里需要强调的是,使用的是谷歌原生的Gemini模型组件,而非Langchain封装的Gemini组件。这是因为Langchain的Gemini组件存在一些Bug,例如无法设置谷歌模型的安全策略,导致机器人在回答问题时经常出现安全性问题。这可能是由于Langchain的Gemini组件仍处于早期版本,预计后续会逐步完善。
功能模块介绍
Embedding模型的选择
Embedding模型在RAG系统中扮演着至关重要的角色,它负责将文本数据转换为向量表示,从而使得计算机能够理解和处理文本信息。在选择Embedding模型时,需要根据具体的应用场景和数据特点进行权衡。
考虑到本次实验使用的是中文内容的文档,因此选择了BAAI的"bge-small-zh-v1.5"模型。如果使用的是英文PDF文档,则可以将Embedding模型切换为BAAI的"bge-small-en-v1.5"模型:
bge_embeddings = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-small-zh-v1.5", cache_folder="D:\models"
)
cache_folder
参数用于指定Embedding模型存储的文件夹。当首次执行机器人程序时,系统会自动从Hugging Face的网站下载Embedding模型,并将其存储到默认的cache_folder
文件夹下(通常是C盘)。为了避免C盘空间不足,可以指定cache_folder
参数的路径,将Embedding模型存储在其他磁盘上。
Gemini模型的安全策略
Gemini模型在回答用户问题时,采用了一套严格的内容安全审查机制,以防止出现违反道德伦理、色情暴力等内容。由于本次实验使用的PDF文档是《阿凡提故事大全.pdf》,它是一个中文文档,当机器人在回答问题时,有时会出现内容违规的提示。这可能是由于Gemini模型的默认安全审查策略的阈值设置过高导致的。因此,将安全审查策略的阈值调整为“无”后,就不再出现违规提示:
generation_config = {
"temperature": 0.0,
"top_p": 1,
"top_k": 1,
"max_output_tokens": 2048,
}
safety_settings = [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_NONE",
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_NONE",
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_NONE",
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_NONE",
},
]
这里设置了模型参数generation_config
和安全策略参数safety_settings
。在模型参数中,将temperature
设置为0。temperature
参数的取值范围为0到1,参数值越低,LLM给出的答案越精准,参数值越高,给出的答案变化性越大。为了让LLM不要产生多样化的结果,应尽量调低temperature
的值,可以设置为0或0.1。另外,将所有安全策略的阈值都设置为BLOCK_NONE
,即不做安全性审查。
父文档检索器
父文档检索器是Langchain框架中的一个重要组件,它能够将大型文档分割成更小的块,并建立父子关系,从而实现更精准的检索。
下面是在Panel中定义父文档检索器的一些组件:
@pn.cache(ttl=TTL)
def _get_docs(pdf):
# load documents
with tempfile.NamedTemporaryFile("wb", delete=False) as f:
f.write(pdf)
file_name = f.name
loader = PyPDFLoader(file_name)
docs = loader.load()
return docs
@pn.cache(ttl=TTL)
def _get_parent_splitter():
return RecursiveCharacterTextSplitter(chunk_size=1000)
@pn.cache(ttl=TTL)
def _get_child_splitter():
return RecursiveCharacterTextSplitter(chunk_size=400)
@pn.cache(ttl=TTL)
def _get_MemoryStore():
return InMemoryStore()
@pn.cache(ttl=TTL)
def _get_vector_db():
vectorstore = Chroma(
collection_name="split_parents", embedding_function=bge_embeddings
)
return vectorstore
@pn.cache(ttl=TTL)
def _get_retriever(pdf, number_of_chunks: int):
docs = _get_docs(pdf)
vectorstore = _get_vector_db()
store = _get_MemoryStore()
child_splitter = _get_child_splitter()
parent_splitter = _get_parent_splitter()
# 创建父文档检索器
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
search_kwargs={"k": number_of_chunks},
)
# 添加文档集
retriever.add_documents(docs)
return retriever
创建Gemini模型对象
下面是在Panel中定义Gemini模型对象和模型生成内容的方法:
@pn.cache(ttl=TTL)
def _get_model():
# 创建gemini model
model = genai.GenerativeModel(
model_name="gemini-pro",
generation_config=generation_config,
safety_settings=safety_settings,
)
return model
def _get_response(contents):
retriever = _get_retriever(state.pdf, state.number_of_chunks)
model = _get_model()
relevant_docs = retriever.get_relevant_documents(contents)
contexts = "\n\n".join([w.page_content for w in relevant_docs])
# prompt模板
template = f"""请根据下面给出的上下文来回答下面的问题,并给出完整的答案:
上下文:{contexts}
问题: {contents}
"""
response = model.generate_content(template)
chunks = []
for chunk in relevant_docs:
name = f"Chunk {chunk.metadata['page']}"
content = chunk.page_content
chunks.insert(0, (name, content))
return response.text, chunks
创建Panel页面组件
为了构建用户友好的交互界面,需要创建Panel的页面组件,并设置环境变量等全局变量,以便设置谷歌的API Key和回调函数。这里使用Panel的chat_interface
作为页面聊天组件:
class EnvironmentWidget(EnvironmentWidgetBase):
GOOGLE_API_KEY: str = param.String()
class State(param.Parameterized):
pdf: bytes = param.Bytes()
number_of_chunks: int = param.Integer(default=2, bounds=(1, 5), step=1)
environ = EnvironmentWidget()
state = State()
pdf_input = pn.widgets.FileInput.from_param(state.param.pdf, accept=".pdf", height=50)
text_input = pn.widgets.TextInput(placeholder="First, upload a PDF!")
def _get_validation_message():
pdf = state.pdf
google_api_key = environ.GOOGLE_API_KEY
if not pdf and not google_api_key:
return "请在左侧侧边栏中输入谷歌api key 然后上传PDF文件!"
if not pdf:
return "请先上传pdf 文件"
if not google_api_key:
return "请先输入谷歌api key"
genai.configure(api_key=google_api_key, transport='rest')
return ""
def _send_not_ready_message(chat_interface) -> bool:
message = _get_validation_message()
if message:
chat_interface.send({"user": "System", "object": message}, respond=False)
return bool(message)
async def respond(contents, user, chat_interface):
if _send_not_ready_message(chat_interface):
return
if chat_interface.active == 0:
chat_interface.active = 1
chat_interface.active_widget.placeholder = "在这里输入您的问题!"
yield {"user": "Gemini", "object": "现在可以开始和pdf对话了!"}
return
response, documents = _get_response(contents)
pages_layout = pn.Accordion(*documents, sizing_mode="stretch_width", max_width=800)
answers = pn.Column(response, pages_layout)
yield {"user": "Gemini", "object": answers}
chat_interface = pn.chat.ChatInterface(
callback=respond,
sizing_mode="stretch_width",
widgets=[pdf_input, text_input],
disabled=True,
)
@pn.depends(state.param.pdf, environ.param.GOOGLE_API_KEY, watch=True)
def _enable_chat_interface(pdf, google_api_key):
if pdf and google_api_key:
chat_interface.disabled = False
else:
chat_interface.disabled = True
_send_not_ready_message(chat_interface)
## Wrap the app in a nice template
template = pn.template.BootstrapTemplate(
title="PDF文档对话机器人",
sidebar=[environ, state.param.number_of_chunks],
main=[chat_interface],
)
template.servable()
机器人使用方法介绍
要在命令行窗口中执行机器人的源代码程序,可以使用以下命令:
panel serve gemini_pdf_bot.py
然后在浏览器中打开访问机器人的链接。完成输入谷歌API Key和上传PDF文件后,就可以开始和机器人聊天了:
总结
本文详细介绍了如何开发一个简单的RAG系统,即基于PDF文档问答的机器人应用。在这个过程中,应用了Langchain的父文档检索策略、Panel的页面聊天组件chat_interface
以及谷歌的Gemini大模型。希望本文的内容能够对大家学习RAG和聊天机器人程序有所帮助。
通过本文,读者可以了解到RAG技术的基本原理和应用方法,并能够利用Gemini模型和Langchain框架快速构建一个能够与PDF文件进行交互的智能机器人。这种机器人可以应用于各种场景,例如智能客服、文档分析、知识问答等,为人们提供更加便捷和高效的信息服务。