AI菜鸟向前飞 — LangChain系列之十一 - RAG(中篇)

上一篇简单概括性给大家介绍了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(即:索引),敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值