一、简介
我们在实现了一系列功能之后,终于来到了rag的部分,下面我们将基于langchain来实现一个rag检索。
关于rag方面的知识,可以查看这两篇文章:
大模型应用之RAG详解
什么是 RAG(检索增强生成)
或者是去油管上IBM Technology专栏,他们在这方面的简介令人印象深刻。
好了,我们还是简单来描述一下rag文本处理的一个流程。
- 数据加载
- 文本分块
- 文本嵌入
- 创建索引
其实简单理解就是你要把一个外部的文档(各种格式)进行拆分,然后做嵌入(向量化),存储进向量库,作为检索的语料。
这就是我们今天主要要做的,我们要基于langchain来实现一下,然后后面不断完善他。
二、代码实现
1、启动es
我们需要使用es进行向量结果的存储,所以我们简单启动一个es,我们选择8.17.2
配置文件我们简单配置一下,不启动太多的安全检测。如果你想启动生产级别的单点配置,可以参考使用docker搭建ELK环境
cluster.name: my-application
node.name: node-1
http.port: 9200
xpack.security.http.ssl.enabled: false
xpack.security.enabled: false
xpack.security.transport.ssl.enabled: false
xpack.ml.enabled: false
kibana我们就不配置了,启动之后我们来编写python代码。
2、代码实现
我们准备两个pdf,我准备的是山东省公布的对台湾同胞的政策和潍坊的美食介绍。分别是lutai.pdf,weifangfood.pdf。
我们来看一下langchain对于文档加载的支持。这部分内容位于langchain的文档加载
你能看到他支持多种文件格式的加载。
Webpages:网络页面
PDFs:pdf文件
…
我们这里使用的就是pdf加载器。我们就用这个,PyPDF文档
你按照他的那个步骤安装这个包就好了:
pip install -qU pypdf
我们在pycharm中的虚拟环境中加载这个就可以了,前面我们弄过了,可以直接去看前面的文章就行。
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_elasticsearch import ElasticsearchStore
from uuid import uuid4from langchain_core.documents import Document# 构建一个文件数组,后期用来解析
pdf1 = './weifangfood.pdf'
pdf2 = './lutai.pdf'
pdfs = [pdf1,pdf2]
docs = []# 解析pdf拆分文档
for pdf in pdfs:# 构建pdf loaderloader = PyPDFLoader(pdf)# 添加到文档数组中,后面就处理这个docs.extend(loader.load())# 对文档结果做切分,每一块切1000个字符,重叠大约200个,并且设置索引(做编号,标记这个切分结果来自于文档切分的哪一块)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000,chunk_overlap=200,add_start_index=True)
split_text_list = text_splitter.split_documents(docs)# 构建向量化组件
embed = OllamaEmbeddings(model="llama3.2:latest")# 构建es向量存储器
elastic_vector_search = ElasticsearchStore(es_url="http://localhost:9200",index_name="langchain_index",embedding=embed,
)documents = []
for text in split_text_list:# 向量化,输出的就是向量结果# vector = embed.embed_query(text.page_content)# 对拆分的文本构建一个文档结构document = Document(page_content = text.page_content,metadata = text.metadata,)documents.append(document)uuids = [str(uuid4()) for _ in range(len(documents))]
# 添加到es向量存储中
elastic_vector_search.add_documents(documents=documents, ids=uuids)
以上代码均可参考以下文档来实现。
文本向量化
向量数据库选择
es向量化存储
运行之后他会自动在es中创建一个索引langchain_index,然后把你的文档向量化的结果存储进es中。
3、向量化结果
我们运行之后,查看一下这个索引。
{"langchain_index": {"aliases": {},"mappings": {"properties": {"metadata": {"properties": {"author": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"comments": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"company": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"creationdate": {"type": "date"},"creator": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"keywords": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"moddate": {"type": "date"},"page": {"type": "long"},"page_label": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"producer": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"source": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"sourcemodified": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"start_index": {"type": "long"},"subject": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"title": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"total_pages": {"type": "long"},"trapped": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}},"text": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"vector": {"type": "dense_vector","dims": 3072,"index": true,"similarity": "cosine","index_options": {"type": "int8_hnsw","m": 16,"ef_construction": 100}}}},"settings": {"index": {"routing": {"allocation": {"include": {"_tier_preference": "data_content"}}},"number_of_shards": "1","provided_name": "langchain_index","creation_date": "1744981253591","number_of_replicas": "1","uuid": "JdwR_WlTQzWtzFRlVCv-rw","version": {"created": "8521000"}}}}
}
我们来主要分析一下他的这些字段,或者你可以在代码中输出结果来看下。我们在es中直接查看即可。
- text:你在把文本拆分之后然后对每一段进行向量化存储,每一段就是一条es doc。text存储了这一段的原文。
- metadata:元数据,这里是一个层级结构,下层还存储着当前段来自哪个文档,属于第几段。这个文档一共拆分了几段,当前这一段属于第几段,等等元信息。
- vector:double类型的数组,其实就是当前段向量化之后的结果。
我们来看其中一个段的结果:
{"_index": "langchain_index","_id": "e5bb54ff-8269-4e0b-8cb3-3952dc4d57b9","_score": 1,"_ignored": ["text.keyword"],"_source": {"text": """中国台湾网 8 月 15 日济南讯 7 月 31 日,山东省正式发布实施《关
于促进鲁台经济文化交流合作的若干措施》,该措施共 56 条,其中
促进鲁台经济合作 23 条措施、促进鲁台文教交流 14 条措施,支持
台湾同胞在鲁学习创业就业生活 19 条。措施涵盖产业合作、就业、
人才引进、知识产权保护、文教合作及职业资格考试、证件办理等
方面,综合运用了财税、金融、用地等政策手段,并明确规定今后
山东省各级政府在制定发展规划、设立扶持资金和出台支持政策时,
都将保障符合条件的台资企业和台湾同胞享有同等待遇。
山东是大陆国有经济大省、基础设施建设不断发展,该措施支
持台湾民间资本与山东国有资本共同设立股权投资基金、产业投资
基金,支持台资参与山东高速公路、轨道交通等多方面基础设施建
设,让台商台企率先享受山东发展红利,实现两岸资本合作共赢。
山东也是儒家文化发源地和教育人力资源大省,措施扶持台湾同胞、
台湾高校研究机构和民间社团参与承接山东文化产业工程项目,联
合开展教学改革和共建研究生培养基地,大力拓展两岸文化融合的
深度广度,特别是将大力吸引台湾儒学研究高端人才到山东开展儒
学研究和传播工作,符合条件的给予优厚待遇。同时,该措施也针
对台商反映的“退城进园”、农业土地流转合同到期优先续租、子
女就学、台胞行医等做出了统筹管理和制度化安排。
据悉,山东省人大正加紧制定保护促进台胞在山东投资权益的
地方立法条例,条例将吸收本次出台的 56 条措施,在法律层面上对
台胞应享有权益予以明确和保障。""","metadata": {"producer": "","creator": "WPS 文字","creationdate": "2024-07-30T17:08:05+09:08","author": "","comments": "","company": "","keywords": "","moddate": "2024-07-30T17:08:05+09:08","sourcemodified": "D:20240730170805+09'08'","subject": "","title": "","trapped": "/False","source": "./lutai.pdf","total_pages": 14,"page": 0,"page_label": "1","start_index": 0},"vector": [0.017683318,0.010620082,0.02021882,-0.0032659627,......0.006196561]}}
4、相似性检索
我们把文本向量化之后,我们就可以执行一些向量检索,这个是es中一个比较重要的概念,文档位于ai检索
我们使用langchain中的一些封装就可以。文档同样位于langchain相似性检索
from langchain_ollama import OllamaEmbeddings
from langchain_elasticsearch import ElasticsearchStore# 向量化
embed = OllamaEmbeddings(model="llama3.2:latest")# 存储es
elastic_vector_search = ElasticsearchStore(es_url="http://localhost:9200",index_name="langchain_index",embedding=embed,
)
# 使用相似性检索
results = elastic_vector_search.similarity_search_with_score(query="进鲁台经济合作方面",k=1,
)# 输出检索结果
for doc, score in results:print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
这个检索结果有很大提升空间,我们后面整合到llm中再说。
5、Retrievers 检索器
我们的langchain中有很多向量检索的api,es的 pgsql的很多。这种都有各自的使用方式,于是langchain封装了一个上层实现Retrievers 检索器
众多实现都实现了这个接口,我们可以直接使用这个上层接口来屏蔽底层的实现。我们来简单实现一个。
from langchain_ollama import OllamaEmbeddings
from langchain_elasticsearch import ElasticsearchStore# 向量化
embed = OllamaEmbeddings(model="llama3.2:latest")# 存储es
elastic_vector_search = ElasticsearchStore(es_url="http://localhost:9200",index_name="langchain_index",embedding=embed,
)
# 构建一个Retrievers 检索器,使用similarity相似性检索。k:1表示每一个问题都只返回一个回答
retriever = elastic_vector_search.as_retriever(search_type="similarity",search_kwargs={"k":1}
)# 批量执行两个问题
resp = retriever.batch(["台湾同胞在山东就业期间有什么政策","潍坊有啥好吃的"]
)
# 返回输出
for result in resp:print(result)
这就是检索器的简单使用,我们后面再来探究其深入使用。
三、总结
我们到此完成了文本的rag存储,后面我们将会使用rag增强检索来增强大模型的问答效果。