python实战(七)——基于LangChain的RAG实践

一、任务目标

基于之前的RAG实战,相信大家对RAG的实现已经有了一定的了解了。这篇文章将使用LangChain作为辅助,实现一个高效、便于维护的RAG程序。

二、什么是LangChain

LangChain是一个用于构建大模型应用程序的开源框架,它内置了多个模块化组件。通过这些组件,我们能够快速且便捷地搭建一个强大的大模型应用程序。首先,我们来看一下如何通过LangChain来调用大语言模型:

python 复制代码
from langchain_community.chat_models.openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
import os


API_SECRET_KEY = "your api key"
BASE_URL = "your api base"
os.environ["OPENAI_API_KEY"] = API_SECRET_KEY
os.environ["OPENAI_API_BASE"] = BASE_URL


# 这里的temperature参数控制答案生成的随机性,0表示按照概率最大的结果生成,也就是最稳定
# 如果温度设置为1则表示生成极富随机性的结果
# 由于我们没有指定调用哪个openai模型,默认会是gpt-3.5-turbo
chat = ChatOpenAI(temperature=0.0)

# 定义template字符串,这里不需要使用f字符串来赋值
template = """请将下面的中文文本翻译为{language}。\
文本:'''{text}'''
"""

# 将template字符串转换为langchain模板,这时候会自动识别prompt模板需要的参数,即{}中的内容
prompt_template = ChatPromptTemplate.from_template(template)

customer_language = '英文'
customer_text = '你好,我来自中国。'

# 传入相应字符串生成符合大模型输入要求的prompt
customer_messages = prompt_template.format_messages(language=customer_language, text=customer_text)
print(customer_messages[0])

# 调用大语言模型
customer_response = chat.invoke(customer_messages, temperature=0.0)
print(customer_response.content)

相比起我们直接使用openai库调用大模型的方式,LangChain库调用大语言模型要更加结构化和模块化,尤其是prompt模板的构建部分。这样做的好处是,我们可以很方便地进行程序的复用。另外,LangChain内置了多个常用场景的Prompt模板,可以拿来即用,省去了我们重新设计和构造prompt的时间。

三、RAG流程构建

1、文档加载

LangChain支持多种文档格式的加载,比如doc、pdf、markdown甚至html等。这里,我们加载几个pdf文档(文档中的内容是调用大模型生成的关于各个西游记主角的人物特征):

python 复制代码
from langchain_community.document_loaders.pdf import PyPDFLoader

# 这里会逐页加载文档
loaders = [PyPDFLoader('孙悟空.pdf'), PyPDFLoader('猪八戒.pdf'), PyPDFLoader('沙和尚.pdf'), PyPDFLoader('唐僧.pdf')]
docs = []
for loader in loaders:
    pages = loader.load()
    # 打印pages的元素个数
    print(len(pages))
    # 打印第一个元素,也就是第一页的一部分文字看看
    print(pages[0].page_content[:10])
    # 打印第一个元素的元数据
    print(pages[0].metadata)
    docs.extend(pages)

2、文档分割

加载后的文档可能很大,我们需要将文档分割成一个一个的小块进行存储。在进行检索的时候,也能够直接返回相关的文档块,而不需要整个文档都传给大模型。LangChain提供字符级别的文档分隔、token级别的文档分隔等多个工具。这里我们使用字符分割:

python 复制代码
from langchain.text_splitter import RecursiveCharacterTextSplitter

# chunk_size用于设置每个文本块的大小,chunk_overlap用于设置每个文本块直接的重叠部分大小
text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=10)
blocks = text_splitter.split_documents(docs)

3、文本向量化存储

为了便于后续使用,我们把文本转化成向量并存储到本地。向量化的意义在于可以很方便地比较文本相似度,从而检索出与query相关的信息返回给大模型。

python 复制代码
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 定义向量化工具
embedding_model = OpenAIEmbeddings(model='text-embedding-3-small')

directory = './save_folder'
# Chroma是一个轻量级的向量存储库,langchain提供了数十个向量存储库,这里我们使用高效的Chroma
vectordb = Chroma.from_documents(
    documents=blocks,
    embedding=embedding_model,
    persist_directory=directory
)
# 永久化存储
vectordb.persist()

# 打印文本向量数
print('文本向量数:', vectordb._collection.count())

# 可以进行相似性检索,k指定返回结果数量
query = '请介绍一下唐僧的性格特点'
search_results = vectordb.similarity_search(query, k=3)
print('相似文本示例:', search_results[0])

这里,我们使用了similarity_search方法检索相关的文本向量,这是纯相似度比较的方式,返回结果将高度相关,但也会减少多样性。如果希望在查询的相关性和多样性之间保持平衡,也可以使用max_marginal_relevance_search方法进行向量检索

4、问答

这里,我们构造一个检索式问答链,并让程序返回它所用到的检索结果:

python 复制代码
from langchain_community.chat_models import ChatOpenAI
from langchain.chains.retrieval_qa.base import RetrievalQA
from langchain.prompts import PromptTemplate

llm = ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.0)
template = '请根据上下文回答以下问题。上下文:{context},问题:{question}'
qa_prompt = PromptTemplate.from_template(template)

qa_chain = RetrievalQA.from_chain_type(
    llm,
    retriever=vectordb.as_retriever(),
    return_source_documents=True,
    chain_type_kwargs={'prompt':qa_prompt}
)

question = '请介绍一下唐僧的性格特点'
response = qa_chain({'query':question})
# 打印模型响应
print(response['result'])
# 打印第一个相关的检索结果
print(response['source_documents'][0])

上面的代码中,{context}是检索结果,{question}是指我们的提问,这两个变量不可缺少,变量命名和相对位置也应当与上面一致,至于放置的绝对位置则无特别要求

四、完整代码

代码运行过程中如果存在报错是因为一些依赖库没有安装,pip install就可以了。如果是无报错退出且状态码是00005结尾的,那么应该是Chroma读取内存数据的时候报错了,persist存储到本地之后,使用Chroma(persist_directory='你的存储地址', embedding_function='初始化之后的embedding模型')即可正常运行。

python 复制代码
from langchain_community.document_loaders.pdf import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.chat_models import ChatOpenAI
from langchain.chains.retrieval_qa.base import RetrievalQA
from langchain.prompts import PromptTemplate
import os


API_SECRET_KEY = "your api key"
BASE_URL = "your api base"
os.environ["OPENAI_API_KEY"] = API_SECRET_KEY
os.environ["OPENAI_API_BASE"] = BASE_URL


# 这里会逐页加载文档
loaders = [PyPDFLoader('孙悟空.pdf'), PyPDFLoader('猪八戒.pdf'), PyPDFLoader('沙和尚.pdf'), PyPDFLoader('唐僧.pdf')]
docs = []
for loader in loaders:
    pages = loader.load()
    docs.extend(pages)

# chunk_size用于设置每个文本块的大小,chunk_overlap用于设置每个文本块直接的重叠部分大小
text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=10)
blocks = text_splitter.split_documents(docs)

# 定义向量化工具
embedding_model = OpenAIEmbeddings(model='text-embedding-3-small')
directory = 'save_folder'
# Chroma是一个轻量级的向量存储库,langchain提供了数十个向量存储库,这里我们使用高效的Chroma
vectordb = Chroma.from_documents(
    documents=blocks,
    embedding=embedding_model,
    persist_directory=directory
)
# 永久化存储
vectordb.persist()

# 指定模型
llm = ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.0)
# 构建模板
template = '请根据上下文回答以下问题。上下文:{context},问题:{question}'
qa_prompt = PromptTemplate.from_template(template)

# 构建检索式问答链
qa_chain = RetrievalQA.from_chain_type(
    llm,
    retriever=vectordb.as_retriever(),
    return_source_documents=True,
    chain_type_kwargs={'prompt':qa_prompt}
)

quest = '请介绍一下唐僧的性格特点'
response = qa_chain({'query':quest})
# 打印模型响应
print('模型响应:', response['result'])
# 打印第一个相关的检索结果
print('第一个相关的检索结果:', response['source_documents'][0])

五、总结

本文实现了基于LangChain的RAG问答助手程序,但是其中仍然有许多值得打磨的细节,例如目前的问答助手只能支持一问一答的互动形式,如果需要基于历史聊天记录进行问答,则需要使用LangChain中其他的模块化工具进行聊天记录的存储和调用,这些内容将在后续的博文中讨论。

相关推荐
JianminZheng1 小时前
灰狼优化算法
算法
初晴~2 小时前
【动态规划】打家劫舍类问题
java·数据结构·c++·python·算法·leetcode·动态规划
自信人间三百年2 小时前
数据结构与算法-前缀和数组
java·数据结构·算法·leetcode
说私域3 小时前
私域流量圈层在新消费时代的机遇与挑战:兼论开源 AI 智能名片、2 + 1 链动模式、S2B2C 商城小程序的应用
人工智能·小程序
小森( ﹡ˆoˆ﹡ )3 小时前
词嵌入方法(Word Embedding)
人工智能·机器学习·自然语言处理·nlp·word·embedding
将心ONE3 小时前
RandomWords随机生成单词
python
古城小栈4 小时前
Spring Security 认证流程,长话简说
java·python·spring
用一个不重复的昵称5 小时前
python数据写入excel文件
python·excel·pandas
阿牛牛阿5 小时前
多模态大模型(1)--CLIP
算法·机器学习·ai·aigc
想成为高手4995 小时前
成功男人背后的女人--解析AIGC幕后的算法原理
算法·aigc