最新LangChain+GLM4开发AI应用程序系列(三):RAG检索增强生成篇

最新LangChain+GLM4开发AI应用程序系列(三):RAG检索增强生成篇

一、前言

上两篇文章分别介绍了LangChain的快速入门和Agent智能体开发。在LLM的实际应用场景中,经常会需要用到特定领域用户的数据,但这些数据不属于模型训练集的一部分,要实现这一需求,最好的方法是通过检索增强生成(RAG)。在用户提问时,先检索特定的外部数据,把检索结果作为上下文传递给LLM,以便大模型返回更精准的结果。今天我们就带大家了解下在LangChain里RAG的使用,结合智谱AI GLM4大模型开发一个基于特定知识库的智能问答应用。

二、RAG介绍

LangChain中提供了RAG应用程序的所有构建模块。结构图如下:

1、文档加载器

文档加载器用来加载各种不同类型的文档。LangChain提供了100 多种不同的文档加载器,可以加载各种类型文档,包括:CSV、HTML、JSON、Markdown、PDF、DOC、XLS、图片、视频、音频等等。具体的文档加载器参考:https://python.langchain.com/docs/integrations/document_loaders/

2、文本分割器

加载文档后,对于长文档,需要分割成更小的块,以适合LLM的上下文窗口。LangChain 有许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。

LangChain 提供了多种不同类型的文本分割器。下表列出了所有这些以及一些特征:

类型:文本分割器的类型

分割依据:此文本拆分器如何拆分文本

添加元数据:此文本拆分器是否添加有关每个块来自何处的元数据。

描述:分离器的描述,包括有关何时使用它的建议。

类型 分割依据 添加元数据 描述
Recursive 用户定义的字符列表 递归地分割文本。递归地分割文本的目的是尝试使相关的文本片段彼此相邻。这是开始分割文本的推荐方法。
HTML HTML 特定字符 根据 HTML 特定字符分割文本。值得注意的是,这添加了有关该块来自何处的相关信息(基于 HTML)
Markdown Markdown 特定字符 根据 Markdown 特定字符分割文本。值得注意的是,这添加了有关该块来自何处的相关信息(基于 Markdown)
Code 代码(Python、JS)特定字符 根据特定于编码语言的字符分割文本。有 15 种不同的语言可供选择。
Token Tokens 基于tokens分割文本。有几种不同的方法来衡量tokens.
Character 用户定义的字符 根据用户定义的字符分割文本。比较简单的方法之一。
[Experimental] Semantic Chunker 句子 首先对句子进行分割。然后,如果它们在语义上足够相似,则将它们相邻地组合起来。

3、嵌入模型

嵌入(Embedding)是一种将单词、短语或整个文档转换为密集向量的技术。每个单词或短语被转换成一组数字,这组数字捕捉了该文本的某些语义特征。

嵌入模型通过将文本转换为计算机可以处理的数值形式(即向量),使得计算机能够理解和处理自然语言。通过嵌入捕获文本的语义,可以快速有效地找到文本的其他相似部分。

LangChain提供了超过25种不同嵌入提供商和方法的集成,可以参考:https://python.langchain.com/docs/integrations/text_embedding

抱脸上有个中文、英文嵌入模型排行榜,可以作为参考,具体还要选适合自己的,比如开源的,对中文支持比较好的等等:https://huggingface.co/spaces/mteb/leaderboard

4、向量数据库

有了嵌入模型对数据的向量化,就需要数据库来支持这些嵌入数据的高效存储和搜索。LangChain提供了50多种不同向量数据库的集成,可以参考:https://python.langchain.com/docs/integrations/vectorstores

目前Milvus评分最高。

三、RAG开发案例

接下来我们实战开发一个基于特定知识库的智能问答应用。还是在Jupyter Notebook环境中执行。

1、创建智谱GLM4大模型对象

首先定义LangChain里智谱大模型的包装类,参考第一篇文章里有,或者从github上下载:https://github.com/taoxibj/docs/blob/main/zhipuai.py

创建大模型对象

python 复制代码
# 填写您自己的APIKey
ZHIPUAI_API_KEY = "..."
llm = ChatZhipuAI(
    temperature=0.1,
    api_key=ZHIPUAI_API_KEY,
    model_name="glm-4",
)

2、加载文档

这里特定领域用户的数据来源于一个示例的ordersample.csv文件,这个文件可以从我的github上获取:https://github.com/taoxibj/docs/blob/main/ordersample.csv

文件具体内容如下:

把orersample.csv下载到jupyter notebook当前ipynb文件目录,使用CSV文档加载器,加载文档内容:

python 复制代码
from langchain_community.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(file_path='ordersample.csv')
data = loader.load()

打印加载后的文档内容:

python 复制代码
from pprint import pprint
pprint(data)

按一行一个文档,分成了6个文档。

[Document(page_content='\ufeff订单ID: 123456\n订单金额: 100.18\n收货人姓名: 李四同\n收货人电话: 1234567890\n收货地址: 北京市海淀区\n图片: https://qnssl.niaogebiji.com/152701334564123796dd0f43.47234870.png', metadata={'source': 'ordersample.csv', 'row': 0}),
 Document(page_content='\ufeff订单ID: 111222\n订单金额: 367.58\n收货人姓名: 王五一\n收货人电话: 1314567890\n收货地址: 北京市朝阳区\n图片: ', metadata={'source': 'ordersample.csv', 'row': 1}),
 Document(page_content='\ufeff订单ID: 333444\n订单金额: 1234.5\n收货人姓名: 张三丰\n收货人电话: 1331112390\n收货地址: 北京市西城区\n图片: ', metadata={'source': 'ordersample.csv', 'row': 2}),
 Document(page_content='\ufeff订单ID: 123458\n订单金额: 120.18\n收货人姓名: 李四同\n收货人电话: 1234567890\n收货地址: 北京市海淀区\n图片: https://qnssl.niaogebiji.com/152701334564123796dd0f43.47234870.png', metadata={'source': 'ordersample.csv', 'row': 3}),
 Document(page_content='\ufeff订单ID: 119822\n订单金额: 267.58\n收货人姓名: 王五一\n收货人电话: 1314567890\n收货地址: 北京市朝阳区\n图片: ', metadata={'source': 'ordersample.csv', 'row': 4}),
 Document(page_content='\ufeff订单ID: 329444\n订单金额: 234.5\n收货人姓名: 张三丰\n收货人电话: 1331112390\n收货地址: 北京市西城区\n图片: ', metadata={'source': 'ordersample.csv', 'row': 5})]

3、文本分割

通常可以使用递归字符文本分割器对文档对象进行分割,可以设置分割块字符的大小,和重合字符大小。因为我的文档对象比较小,所以每个文档基本上没有达到要切分的大小阈值,可以尝试把文档内容变多,试下效果。

python 复制代码
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200, add_start_index=True
)
all_splits = text_splitter.split_documents(data)

可以打印一下分割后块的数量。

python 复制代码
len(all_splits)

6

4、向量化存储

接下来我们首先使用嵌入模型对分割后的文档进行向量化,然后存储到向量数据库中。

嵌入模型使用的是bge-large-zh-v1.5,这是BAAI北京智源人工智能研究院开源的,支持中、英文的嵌入模型,在开源嵌入模型中算是效果比较好的一个。

向量数据库使用的是FAISS,是FAIR(Meta人工智能实验室)开源的向量数据库。可以在本地使用,相对轻量些,没有Milvus那么强, 但在本地环境使用也够用了。

python 复制代码
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

model_name = "BAAI/bge-large-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}
bgeEmbeddings = HuggingFaceBgeEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)
from langchain_community.vectorstores import FAISS
vector = FAISS.from_documents(all_splits, bgeEmbeddings)

5、向量库检索

接下来尝试下使用向量库进行检索。

python 复制代码
retriever = vector.as_retriever(search_type="similarity", search_kwargs={"k": 3})
retriever.invoke("收货人姓名是张三丰的,有几个订单?金额分别是多少,总共是多少?")

检索结果如下。

[Document(page_content='\ufeff订单ID: 329444\n订单金额: 234.5\n收货人姓名: 张三丰\n收货人电话: 1331112390\n收货地址: 北京市西城区\n图片:', metadata={'source': 'ordersample.csv', 'row': 5, 'start_index': 0}),
 Document(page_content='\ufeff订单ID: 333444\n订单金额: 1234.5\n收货人姓名: 张三丰\n收货人电话: 1331112390\n收货地址: 北京市西城区\n图片:', metadata={'source': 'ordersample.csv', 'row': 2, 'start_index': 0}),
 Document(page_content='\ufeff订单ID: 123456\n订单金额: 100.18\n收货人姓名: 李四同\n收货人电话: 1234567890\n收货地址: 北京市海淀区\n图片: https://qnssl.niaogebiji.com/152701334564123796dd0f43.47234870.png', metadata={'source': 'ordersample.csv', 'row': 0, 'start_index': 0})]

6、生成问答结果

使用检索链,串联向量库检索和大模型,根据用户的提问,生成问答结果。

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("""仅根据所提供的上下文回答以下问题:

<context>
{context}
</context>

问题: {question}""")

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
retriever_chain = (
    {"context": retriever , "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

可以开始进行提问。

提问一:

python 复制代码
retriever_chain.invoke("订单ID是123456的收货人是谁,电话是多少?")

回答:

'根据所提供的上下文,订单ID为123456的收货人是李四同,电话是1234567890。'

提问二:

python 复制代码
retriever_chain.invoke("收货人张三丰有几个订单?金额分别是多少,总共是多少?")

回答:

'收货人张三丰有两个订单。第一个订单的订单ID是329444,金额是234.5元;第二个订单的订单ID是333444,金额是1234.5元。这两个订单的总金额是1469元。'

提问三:

python 复制代码
retriever_chain.invoke("收货地址是朝阳区的有哪些订单?")

回答:

'收货地址是朝阳区的订单有:\n\n1. 订单ID: 119822,订单金额: 267.58,收货人姓名: 王五一,收货人电话: 1314567890,收货地址: 北京市朝阳区。\n2. 订单ID: 111222,订单金额: 367.58,收货人姓名: 王五一,收货人电话: 1314567890,收货地址: 北京市朝阳区。\n\n以上两个订单的收货地址均为北京市朝阳区。'

整体的回答结果还算准确。但是也有一些情况,回答的不对的,基本上是向量库检索出的答案有出入造成的问题,所以语料知识的向量化和结构化,还是很重要的。

四、总结

这篇文章首先介绍了在LangChain中使用RAG的重要组成部分,包括文档加载器、文本分割器、嵌入模型、向量数据库,然后通过一个示例演示了LLM如何通过RAG检索增强生成的方式,借助特定领域用户数据,更准确的回答用户的提问。

对于RAG来说,语料的结构化是很重要的,后面也会深入分析一些文档材料如DOC、XLS、PDF的结构化以及如何更好的进行切分来获取更精准的答案。敬请期待。

相关推荐
Luke Ewin几秒前
基于3D-Speaker进行区分说话人项目搭建过程报错记录 | 通话录音说话人区分以及语音识别 | 声纹识别以及语音识别 | pyannote-audio
人工智能·语音识别·声纹识别·通话录音区分说话人
DashVector15 分钟前
如何通过HTTP API检索Doc
数据库·人工智能·http·阿里云·数据库开发·向量检索
说私域19 分钟前
无人零售及开源 AI 智能名片 S2B2C 商城小程序的深度剖析
人工智能·小程序·零售
蹉跎x21 分钟前
力扣1358. 包含所有三种字符的子字符串数目
数据结构·算法·leetcode·职场和发展
Calvin88082827 分钟前
Android Studio 的革命性更新:Project Quartz 和 Gemini,开启 AI 开发新时代!
android·人工智能·android studio
Jamence1 小时前
【深度学习数学知识】-贝叶斯公式
人工智能·深度学习·概率论
feifeikon1 小时前
机器学习DAY4续:梯度提升与 XGBoost (完)
人工智能·深度学习·机器学习
凡人的AI工具箱1 小时前
每天40分玩转Django:实操多语言博客
人工智能·后端·python·django·sqlite
Jackilina_Stone1 小时前
【自动驾驶】3 激光雷达③
人工智能·自动驾驶
巫师不要去魔法部乱说1 小时前
PyCharm专项训练4 最小生成树算法
算法·pycharm