当前位置: 首页 > article >正文

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

一、任务目标

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

二、什么是LangChain

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

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文档(文档中的内容是调用大模型生成的关于各个西游记主角的人物特征):

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级别的文档分隔等多个工具。这里我们使用字符分割:

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相关的信息返回给大模型。

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、问答

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

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模型')即可正常运行。

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中其他的模块化工具进行聊天记录的存储和调用,这些内容将在后续的博文中讨论。


http://www.kler.cn/a/390357.html

相关文章:

  • flink sql + kafka + mysql 如何构建实时数仓
  • 使用pdfjs加载多页pdf并实现打印
  • 《情商》提升:增强自我意识,学会与情绪共处
  • 机器情绪及抑郁症算法
  • 算法——二分查找(leetcode704)
  • Kafka参数了解
  • Simulink对仿真数据进行FFT频谱分析
  • Unity中IK动画与布偶死亡动画切换的实现
  • 【学习记录丨UVM】2.1uvm_component 与uvm_object
  • 人到一定年纪,要学会远离多巴胺
  • 群控系统服务端开发模式-应用开发-前端框架
  • 必应 Bing 国内广告开户及代运营服务的优势有哪些?
  • UE5.3 CineCameraRigRail组件实测
  • 实现3D热力图
  • VPN相关学习笔记
  • 企业级工位管理:Spring Boot实践
  • wget命令之Tomcat(三)
  • JVM垃圾回收详解二(重点)
  • 【数据结构】线性表——链表
  • 区块链技术在电子政务中的应用
  • 5 分钟内最多允许用户尝试登录3次,如果错误次数超过限制,需要对该用户进行锁定。如何实现?
  • 《Django 5 By Example》阅读笔记:p1-p16
  • Spark 的容错机制:保障数据处理的稳定性与高效性
  • 「IDE」集成开发环境专栏目录大纲
  • 【c++篇】:掌握list--实用的基本使用方法与模拟实现思路
  • 练习LabVIEW第四十二题