利用LangChain实现RAG

news2024/9/19 10:37:01

  检索增强生成(Retrieval-Augmented Generation, RAG)结合了搜寻检索生成能力和自然语言处理架构,透过这个架构,模型可以从外部知识库搜寻相关信息,然后使用这些信息来生成response。要完成检索增强生成主要包含四个步骤:

1.TextLoad:

2.TextSplit:

3.Generate and save Embedding:

4.Search relavant context and get final anwser.

DocumentLoader

  Langchain的document_loader目录下提供多种Load不同类型文档的方法,文档大类型总体可以分为结构化文档和非结构化文档。不同Loader的API信息如下图所示,更多Loader相关的API信息可查看这里。

下面是Load csv格式文件数据的代码和结果,import CSVLoader,print load的结果。可以看到,结果是一个List,List中每一个item是一个Document对象,包含page_content 和metadata数据,metadata包含source和row两个信息。

除了Load csv格式文件,还可以load pdf文件,import PyPDFLoader,load后的数据也是一个List,list中的每个value是Document对象,具体结果如下所示:

  在Load PDF格式文档的时候,可以选择多种Loader,例如UnstructuredPDFLoader,MathpixPDFLoader,OnlinePDFLoader,PyPDFium2Loader,PDFMinerLoader,PyMuPDFLoader,PyPDFDirectoryLoader,PDFPlumberLoader。另外,在load PDF文档时还可以通过参数控制是否需要解析图片上的文字信息等。总之,PDF的loader是非常成熟和丰富的。

除了load PDF格式文档,当然还支持txt格式,markdown格式,html格式等文档,在load的时候可以通过参数选择是否显示load进度,是否使用多线程进行load,多线程load可提升load效率。代码如下所示:

loader = DirectoryLoader('testdata', glob="**/*.txt",

show_progress=True, use_multithreading=True)

docs = loader.load()

print(docs)

TextSplitter

  langchain提供了多种TextSplitter,具体如下图所示,这些spliter中比较常用的有TextSplitter,CharacterTextSplitter,RecursiveCharacterTextSplitter等。

下面是调用CharacterTextSplitter对文本进行切割,需要指定separator,chunk_size,chunk_overlap等参数。chunk_size:定义文本应该被分割成的最大块的大小。在这个例子中,设置为 1000,所以每个块最多包含 1000 个字符。这只是一个最大字符数量控制,真正在分割的时候还要参考separator。chunk_overlap:块之间的最大重叠量。这里设置为 200,所以连续块之间最多可以有 200 个字符的重叠。length_function:用于计算块长度的函数。在这个例子中,使用内置的 len 函数,所以块的长度就是它的字符数.

from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
    separator = "\n\n",
    chunk_size = 1000,
    chunk_overlap  = 200,
    length_function = len,
    is_separator_regex = False,
)

  上面是最简单的文本切割方式,在切割分本时,我们需要尽量保证文本的语句含义不要丢失,即相关性很强的内容最好能放到一个chunk内,上面的切割方式虽然简单,但是容易把文本含义丢失。所以除了上面最简单的切割方式外,langchain还提供了RecursiveCharacterTextSplitter 切割方式。该切割方式的默认separator是["\n\n", "\n", " ", ""]。文本分割器首先尝试在每个双换行符 ("\n\n") 处拆分文本,这通常用于分隔文本中的段落。如果生成的块过大,它接着尝试在每个换行符 ("\n") 处拆分,这通常用于分隔句子。如果块仍然过大,它最后尝试在每个空格 (" ") 处拆分,这用于分隔单词。如果块仍然过大,它会在每个字符 ("") 处拆分,尽管在大多数情况下,这种细粒度的拆分是不必要的。这种方法的优点是它尽量保留了语义上下文,通过保持段落、句子和单词的完整性。这些文本单元往往具有强烈的语义关系,其中的单词在意义上通常密切相关,这对于许多自然语言处理任务是有益的。

   根据经验,采用RecursiveCharacterTextSplitter进行文本切割时,chunk_size设置在500-1000分为内效果较好,chunk_overlap设置在chunk_size大小的10%-20%左右。

  如果文档内容包含格式信息,例如html,markdown等,langchain还提供了HTMLHeaderTextSplitter和MarkdownHeaderTextSplitter切割方式。即在切割的时候按标题头切割,如果是在一个标题下的内容就放在一起。这样能最大程度保证相关性强的内容被切割在一个chunk里面。除了对文本进行切割,Langchain还可以对Code进行切割。下面的代码中就是加载代码目录地址,对该目录下的代码进行切割。

repo_path = "/Users/taoli/study/Python/langchain/langchain"
loader = GenericLoader.from_filesystem(
    repo_path,
    glob="**/*",
    suffixes=[".py"],
    parser=LanguageParser(language=Language.PYTHON, parser_threshold=500),
)
documents = loader.load()
python_splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=1000, chunk_overlap=100
)
texts = python_splitter.split_documents(documents)

Embedding model

  介绍完了Splitter,接着来看看Embedding。langchain支持多种Embedding model的载入。部分Embedding model如下图所示,更多Embedding信息可查看这里。比较常用的有OpenAIEmbeddings和HuggingFaceEmbeddings。

  如下面代码所示,在调用向量数据库的classmethod from_document,就需要指定调用的Embedding方法,可以使用HuggingFace中某个特定的Embedding模型,也可以直接使用OpenAIEmbedding。设置Embedding后,调用from_document方法,就会把texts中的内容转换成向量并存放在向量数据库中,用于后面的检索获取。

db = Chroma.from_documents(
    texts, HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2"))
db = Chroma.from_documents(texts,OpenAIEmbeddings)

在调用from_document方法时,除了指定embedding function,texts内容外,还可以指定persist_directory,即持久化向量信息的目录等多个参数信息。

Search relavant context and get final anwser

  介绍完Embedding后,再来看看相关性检索和得到最终结果的部分。如下面代码所示,在获取到向量库对象后,需要转换成retriever。Retriever类可以根据输入的查询问题,从文档中检索相关性高的内容并返回,检索器不需要存储文档,只需要返回(或检索)文档。向量存储可以用作检索器的支撑,但还有其他类型的检索器。

retriever = db.as_retriever(search_type="mmr",
                            search_kwargs={"k": 8})

llm = ChatOpenAI(model_name="gpt-3.5-turbo")
qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever, chain_type_kwargs={"prompt": rag_prompt})
result = qa_chain({"query": "What classes are derived from the Chain class?"})
print(result)

memory = ConversationSummaryMemory(
    llm=llm, memory_key="chat_history", return_messages=True
)
qa = ConversationalRetrievalChain.from_llm(
    llm, retriever=retriever, memory=memory)
questions = [
    "What is the class hierarchy?",
    "What classes are derived from the Chain class?",
    "What one improvement do you propose in code in relation to the class hierarchy for the Chain class?",
]
for question in questions:
    result = qa(question)
    print(f"-> **Question**: {question} \n")
    print(f"**Answer**: {result['answer']} \n")

在调用as_retriever()方法中可以指定search_type,search_type主要包括两种:query- Similarity Search and Max Marginal Relevance Search (MMR Search),其中MMR Search旨在提高搜索结果的多样性,常用于推荐系统。如果想检索更准确则推荐选择query- Similarity Search,默认的search_type也是query- Similarity Search。

search_kwargs中可以传入多个参数,常见的参数包括:

k 定义返回多少个文档;默认为 4。
如果search_type是 "similarity_score_threshold" ,可以设置score_threshold,即减少相关性的分数阀值。
fetch_k 确定传递给MMR算法的文档数量;默认为 20。
lambda_mult 控制MMR算法返回结果的多样性,1表示最小多样性,0表示最大多样性。默认为 0.5。
filter 允许您根据文档的元数据定义文档检索的筛选条件。如果Vectorstore不存储任何元数据,则此选项无效。

  获取到retriver后,在检索文档生成答案时,有多种方式,既可以通过RetrievalQA这个chain, 还可以通过ConversationalRetrievalChain来获取最终的答案。在调用chain的时候还可以指定chain_type,langchain提供了四种类型的chain_type.Stuff,Refine,Map Reduce和Map re-rank.默认是使用Stuff类型。

Stuff:stuff文件链("stuff "是指 "to stuff "或 "to fill")是最直接的文件链。它接收一个文件列表,将它们全部插入一个提示,并将该提示传递给一个LLM。这条链非常适合于文件数量较少且大多数调用只传递几个文件的应用。

Refine:完善文档链通过循环输入文档和迭代更新其答案来构建一个响应。对于每个文档,它将所有非文档输入、当前文档和最新的中间答案传递给LLM链,以获得一个新的答案。由于Refine链每次只向LLM传递一个文档,所以它很适合需要分析更多文档的任务,这条链会比Stuff文档链等调用更多的LLM。当然,也有一些任务是很难迭代完成的。例如,当文档之间经常相互参照,或者当一个任务需要许多文档的详细信息时,Refine链的表现就很差。下图是Refine的过程源代码中Refine背后的Prompt信息。

Map_Reduce: map reduce文档链首先对每个文档单独应用LLM链(map步骤),将链的输出视为一个新的文档。然后,它将所有的新文档传递给一个单独的组合文档链,以获得一个单一的输出(Reduce步骤)。

Map re_rank:map re-rank文件链对每个文件进行初始提示,不仅试图完成一项任务,而且对其答案的确定程度进行评分。得分最高的回答会被返回。

  在使用chain生成最终答案时,如果调用ConversationalRetrievalChain,可以在from_llm中传入如下的参数,包括chain_type,自定义的prompt等。例如要传入自定义的prompt,那么可以通过condense_question_prompt传入,或者通过combine_docs_chain_kwargs参数传入:

  combine_docs_chain_kwargs={"prompt": TEST_PROMPT},

  在使用chain生成最终答案时,如果调用RetrievalQA的from_chain_type方法,可以通过chain_type_kwargs传入自定义prompt等参数。

   以上就是RAG的过程,总结而言包含Document Loader,Document splitter,Embedding,Search and get final answer四个步骤。步骤示意图如下所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1203169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2、工厂模式的实现

工厂模式概念 工厂模式是一种常用的设计模式,它主要用于实例化对象。这种模式的主要思想是在不暴露具体的实现细节的情况下,让客户端能够创建具有特定接口的对象。它可以让我们在运行时决定实例化哪个类。 在C语言中,实例化对象意味着创建一…

Leetcode—70.爬楼梯【简单】

2023每日刷题(二十七) Leetcode—70.爬楼梯 动态规划思想 动态规划算法的本质是使用空间换时间,通过计算和记录状态来得到最优解。 在分析动态规划类题目时,我们可以通过3个问题对题目进行基本的拆解。 1.问题是否分阶段&…

Java编程--定时器/线程池/工厂模式/ ThreadPoolExecutor

前言 逆水行舟,不进则退!!! 目录 什么是定时器 实现一个定时器 自己实现一个定时器 什么是线程池 线程池的使用: 什么是工厂模式? 自己实现一个线程池: ThreadPoolExecutor 类…

卓越进行时 | 信息安全测试公益培训班报名!先到先得

为配合推进江苏省网络安全工作的部署实施,培训网络安全技术人才,针对全省网络技术骨干人员,近期,由江苏省网络空间安全学会主办、南京赛宁信息技术有限公司承办的“网络安全技能课堂-信息安全测试培训班”在网络安全卓越中心正式举…

完全免费!超好用的IDEA插件推荐:Apipost-Helper

Idea 是一款功能强大的集成开发环境(IDE),它可以帮助开发人员更加高效地编写、调试和部署软件应用程序,Idea 还具有许多插件和扩展,可以根据开发人员的需要进行定制和扩展,从而提高开发效率,今天我们就来介绍一款国产的…

ceph的体系结构

文章目录 CephCeph的体系结构对象存储RADOSOSDOSD的状态osd状态检测 数据寻址file--->Object映射Object--->pg映射pg--->osd思考:为什么要在Object和osd之间增加一层pg的映射呢? 存储池monitormonitor与客户端的通信Monitor与osd的通信 数据操作…

7-爬虫-中间件和下载中间件(加代理,加请求头,加cookie)、scrapy集成selenium、源码去重规则(布隆过滤器)、分布式爬虫

0 持久化(pipelines.py)使用步骤 1 爬虫中间件和下载中间件 1.1 爬虫中间件(一般不用) 1.2 下载中间件(代理,加请求头,加cookie) 1.2.1 加请求头(加到请求对象中) 1.2.2 加cookie 1.2.3 加代理 2 scrapy集成selenium 3 源码去重…

【Transformer从零开始代码实现 pytoch版】(五)总架构类的实现

Transformer总架构 在实现完输入部分、编码器、解码器和输出部分之后,就可以封装各个部件为一个完整的实体类了。 【Transformer从零开始代码实现 pytoch版】(一)输入部件:embeddingpositionalEncoding 【Transformer从零开始代…

Power Automate-变量和excel表数据的应用

前提表格 Power Automate连接excel请参考:SharePoint-连接Excel-CSDN博客 需求1:计算表格中某列的和 添加操作,搜索变量,选择初始化变量 添加变量的名称、类型和初始值 再新增操作,搜索Excel,点击查看更多…

Kubernetes介绍和环境部署

文章目录 Kubernetes一、Kubernetes介绍1.Kubernetes简介2.Kubernetes概念3.Kubernetes功能4.Kubernetes工作原理5.kubernetes组件6.Kubernetes优缺点 二、Kubernetes环境部署环境基本配置1.所有节点安装docker2.所有节点安装kubeadm、kubelet、kubectl添加yum源containerd配置…

查询数据表格中的数据

1.创建这个表至少20个 1)创建数据库:create database 四川信息职业技术; 2)创建数据表 3)插入数据(第一条代码修改了一下手机号码的字段类型) 2.统计表中的人数 如果你想根据某个特定的列来统计人数&…

Jenkins在Linux环境下的安装与配置

Jenkins是一个开源软件项目,是基于Java开发的一种持续集成(CI)工具,用于解决持续重复的部署、监控工作;它一个开放易用的软件平台,大大简化软件的持续集成。 安装Jenkins 1.使用docker安装 2.本地下载je…

Python数据结构:元组(Tuple)详解

1.介绍和基础操作 Python中的元组(Tuple)是不可变有序序列,可以容纳任意数据类型(包括数字、字符串、布尔型、列表、字典等)的元素,通常用圆括号() 包裹。与列表(List)类似&#xff…

[IJKPLAYER]基于DEMO分析IJKPLAYER(整理版本)

背景 博主主要是从事C语言开发,因此本文着重强调FFMPEG部分,关于JAVA应用和框架层只是一笔带过。IJKPLAYER的实质是对FFMPEG项目中的ffplayer程序进行的二次封装,通过JNI方式完成对外提供JAVA接口。 1.目录结构 activities:包含了demo的所有…

react函数式组件props形式父向子传参

父组件中定义 子组件中触发回调传值 import { useState } from "react"; function Son(params) {const [count, setCount] useState(0);function handleClick() {console.log(params, paramsparamsparamsparamsparamsparams);params.onClick(111)setCount(count 1…

多个微信快速同步发圈

做营销最重要的任务是什么? 毋庸置疑,就是发布朋友圈。 为什么要发圈呢? 现在社交媒体中,微信不管在生活上、工作上都是不可或缺的工具,而朋友圈是微信中社交场景之一,也是很多企业作为推广产品和服务的重…

腾讯云服务器多少钱一年?2023年腾讯云优惠云服务器推荐

作为一名程序员,技术的突飞猛进是从拥有第一台云服务器开始的。那时,我开始尝试使用Linux系统,并成功上线了自己的第一个小程序。自此之后,我和我的同事们都开始拥有自己的云服务器,用来搭建各种小项目或者好玩的东西。…

OpenAtom OpenHarmony三方库创建发布及安全隐私检测

OpenAtom OpenHarmony三方库(以下简称“三方库”或“包”),是经过验证可在OpenHarmony系统上可重复使用的软件组件,可帮助开发者快速开发OpenHarmony应用。三方库根据其开发语言分为2种,一种是使用JavaScript和TypeScr…

wpf devexpress设置行和编辑器

如下教程示范如何计算行布局,特定的表格单元编辑器,和格式化显示值。这个教程基于前一个文章 选择行显示 GridControl为所有字段生成行和绑定数据源,如果AutoGenerateColumns 属性选择AddNew。添加行到GridControl精确显示为特别的几行设置。…

Containerd接入Harbor仓库

在使用容器时,避免不了会使用到私有仓库,一般都是采用 harbor 作为私有仓库,docker 对接 harbor 仓库非常简单,哪 containerd 如何对接 harbor 呢? 在内网使用 harbor 根据个人习惯,一般都是非 http 并且是…