上一篇简单概括性给大家介绍了RAG的全过程
AI菜鸟向前飞 — LangChain系列之十 - RAG(上篇)
这篇着重给大家介绍 从文档到向量存储的核心步骤...
开篇一张图
下面一步步介绍,在这之前,先准备一段文字,存储在“test.txt”,后面程序演示需要
#将文件加载到内存中,赋值给txt_docs
txt = TextLoader("test.txt")
txt_docs = txt.load()
# 无需加载,存储字符串列表中
texts = ["""南方将迎长达10天密集降雨热,\n
苹果在中国1年收400亿苹果税,\n
欢迎晚宴上他们都来了,\n
湖北一村上百村民莫名有了营业执照,\n
三组数据看实体经济发展质效提升,\n
南方即将迎来密集降雨热天气,长达10多天,\n
台湾地震把福州乌塔震歪?假的,\n
苹果在中国1年收400亿苹果税,\n
上海外滩特警回应因为太帅走红,"""]
文档加载Loader
简介:
将文档转换成LangChain/向量存储可以识别的Document对象,形如:
Document(page_content='(文档中的内容)', metadata={key: value, key……})
page_content的值为文档中的实际内容,metadata为文档元数据,存储的是一些属性值,例如:"source" (key)内容源自“文档源文件”(value)
元数据的用处很多,例如:可以做文档记录,也可作为组合查询使用,如:self-query、Multi-Vector Retriever……
文档拆分Splitters
简介
为什么要做文档切分?一个原因:在embedding向量化时,每种embedding限制的字符长度都不一样,如果文档太长的话,在做向量相似度查询的时候会非常不准确。
例如,对于阿里的通义千问embedding模型而言,
支持的语种非常重要,这跟你要向量化的原数据密切相关(必须一一对应)
再介绍几个重要术语:
-
-
Chunk Size:块大小
-
Chunk Overlap:重叠的块
-
对于Documents对象的page_content拆分
程序
text_split = CharacterTextSplitter(separator="\n", chunk_size=9, chunk_overlap=2)
chunks = text_split.split_documents(txt_docs)
结果
[Document(page_content='南方将迎长达10天密集降雨热,', metadata={'source': 'test.txt'}),
Document(page_content='苹果在中国1年收400亿苹果税,', metadata={'source': 'test.txt'}),
Document(page_content='欢迎晚宴上他们都来了,', metadata={'source': 'test.txt'}),
Document(page_content='湖北一村上百村民莫名有了营业执照,' …………
程序
text_split = RecursiveCharacterTextSplitter(separators=["\n"], chunk_size=9, chunk_overlap=2)
chunks = text_split.split_documents(docs)
结果
[Document(page_content='南方将迎长达10天密集降雨热,', metadata={'source': 'test.txt'}),
Document(page_content='\n苹果在中国1年收400亿苹果税,', metadata={'source': 'test.txt'}),
Document(page_content='\n欢迎晚宴上他们都来了,', metadata={'source': 'test.txt'}),
Document(page_content='\n湖北一村上百村民莫名有了营业执照,' …………
发现了什么?
-
如果separators切割后文本的长度大于chunk_size的长度,还是按照前者split,chunk_size和chunk_overlap的设置将“失效”
-
使用RecursiveCharacterTextSplitter切割之后,"\n"会存在于切割后的文本段之中
-
…… (等待你去发现更多)
对字符串列表进行拆分
程序
text_split = RecursiveCharacterTextSplitter(separators=["\n"], chunk_size=15, chunk_overlap=2)
texts_chunks = text_split.split_text(texts[0])
结果
['南方将迎长达10天密集降雨热,',
'\n苹果在中国1年收400亿苹果税,',
'欢迎晚宴上他们都来了,',
'\n湖北一村上百村民莫名有了营业执照,'
……
]
看出区别:)
向量化
程序
em_txt_docs = embeddings.embed_documents([each.page_content for each in txt_docs_chunks if each])
print(len(em_txt_docs[0]))
print(em_txt_docs[0][:8])
em_text = embeddings.embed_documents(texts_chunks)
print(len(em_text[0]))
print(em_text[0][:8])
结果
1536
[1.7074159383773804, 2.5431907176971436, 1.5308246612548828, -0.9348058700561523, -1.5376235246658325, -0.3806244730949402, 4.476251602172852, -0.23280033469200134]
1536
[1.7074159383773804, 2.5431907176971436, 1.5308246612548828, -0.9348058700561523, -1.5376235246658325, -0.3806244730949402, 4.476251602172852, -0.23280033469200134]
"1536" 有没有感觉很熟悉, 它就是“text-embedding-v2”所支持的向量维度,不同的embedding支持的维度各不相同哈,请大家自行查阅。
存储
先准备好所使用的向量数据库,这次先用Chroma
collections = Chroma(embedding_function=DashScopeEmbeddings(model="text-embedding-v2"))
接下来,我们就可以存进去,并可以查询到效果
程序
# 新增txtId元数据,存储在metadata中,这次是为了方便“查询”
for i in range(len(txt_docs_chunks)):
txt_docs_chunks[i].metadata.update({"txtId": "txt_docs"})
# 存储
collections.add_documents(txt_docs_chunks)
# 查询
collections.get(where={"txtId": {"$eq": "txt_docs"}})
结果
{'ids': ['00102149-b6a0-4993-95ad-dddba69e9fdc', '46217ba5-ba2c-41f8-ad13-052331c616e3', '5ed0e7a8-c3cb-49cd-92a3-1a3f878da3f3', '7f2a97af-a565-4b68-8dfe-3c8f742a841a', '83578251-b288-4bbf-bd6a-94b3c63498d1', '90592713-dc63-4524-b9f6-549812a73e8e', 'd66590a0-d254-4ec5-9d45-b08f55c843cc', 'ddfe710c-3ae9-4e00-991f-10f02bb6c3fa', 'fa6caccf-d173-430c-a316-c02538e01f92'], 'embeddings': None, 'metadatas': [{'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}, {'source': 'test.txt', 'txtId': 'txt_docs'}],
'documents': ['\n苹果在中国1年收400亿苹果税,', '南方将迎长达10天密集降雨热,', '\n南方即将迎来密集降雨热天气,长达10多天,', '\n三组数据看实体经济发展质效提升,', '\n台湾地震把福州乌塔震歪?假的,', '欢迎晚宴上他们都来了,', '\n上海外滩特警回应因为太帅走红,', '\n苹果在中国1年收400亿苹果税,', '\n湖北一村上百村民莫名有了营业执照,'], 'uris': None, 'data': None}
程序
# 存储
collections.add_texts(texts=texts_chunks,
# 在这里,有几段文本需要要加几个metadatas,否则无法用metadata查
metadatas=[
{"txtId": "texts"},
{"txtId": "texts"},
{"txtId": "texts"},
…………
])
# 查询
collections.get(where={"txtId": {"$eq": "texts"}}
结果
{'ids': ['0a9e72b9-15e4-4a09-8aa0-00c6c7b0a269', '2f86e26a-c7da-47f7-8c71-523e7fb371d8', '7c4acb7d-4682-4dc1-9923-cdb72af17041', '93df9c47-e35c-4009-8950-28f56fd4750b', 'c3993815-f9ba-408d-a539-9dbca3dfbe38', 'daf5f77b-38a0-4dcc-93e7-ae3f9d235405', 'e83d758b-aad9-4534-8cd1-830338713869', 'ed603f61-5120-4d73-af67-70a320db1841', 'efe92668-1ab6-458c-89f3-a6ab01df7a9e'], 'embeddings': None, 'metadatas': [{'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}, {'txtId': 'texts'}], 'documents': ['\n苹果在中国1年收400亿苹果税,', '欢迎晚宴上他们都来了,', '\n苹果在中国1年收400亿苹果税,', '\n上海外滩特警回应因为太帅走红,', '\n台湾地震把福州乌塔震歪?假的,', '\n湖北一村上百村民莫名有了营业执照,', '\n南方即将迎来密集降雨热天气,长达10多天,', '南方将迎长达10天密集降雨热,', '\n三组数据看实体经济发展质效提升,'], 'uris': None, 'data': None}
总结
回头再看开篇那张图是不是更清晰了:)
下一篇 将给大家介绍一个特别重要的Index(即:索引),敬请期待。