langchain教程-8.VectorStore/向量数据库存储和检索
前言
该系列教程的代码: https://github.com/shar-pen/Langchain-MiniTutorial
我主要参考 langchain 官方教程, 有选择性的记录了一下学习内容
这是教程清单
- 1.初试langchain
- 2.prompt
- 3.OutputParser/输出解析
- 4.model/vllm模型部署和langchain调用
- 5.DocumentLoader/多种文档加载器
- 6.TextSplitter/文档切分
- 7.Embedding/文本向量化
- 8.VectorStore/向量数据库存储和检索
- 9.Retriever/检索器
- 10.Reranker/文档重排序
- 11.RAG管道/多轮对话RAG
- 12.Agent/工具定义/Agent调用工具/Agentic RAG
向量存储的概念指南
向量存储 是经过精心设计的一种数据库系统,在其基础架构上整合了向量表示法(即嵌入技术),并提供了一系列高效的数据处理功能。该系统的核心目标是实现对数据内容的有效索引与快速搜索能力。
这类工具一般使用语义相似性识别而非仅依赖于精确的关键词匹配的方式来进行处理,并能够高效地处理包括文本、图像和音频在内的非结构化数据
为什么向量存储至关重要
- 快速高效的搜索
通过科学地组织和索引嵌入向量空间中的内容,在实际应用中可以实现对信息的有效提取与快速检索;即使面对海量数据的挑战,在系统设计得当的情况下也能保证良好的性能表现
- 应对数据增长的可扩展性
伴随数据持续增长,向量存储必须具备良好扩展能力。一个架构合理的向量存储能够保证系统在处理大规模数据时能够维持高性能状态,并实现无阻的规模增长。
- 促进语义搜索
与传统的基于关键词的检索方式不同,在意涵检索中依据内容意义来进行信息检索。利用向量存储技术通过检索与用户查询紧密相关的内容片段或片段部分来实现这一目的。相较于仅依赖精确关键字匹配的传统文本数据库而言
理解搜索方法
基于关键词的搜索机制
该方法通过精确匹配文档中的特定单词或短语来进行查询操作。相对而言较为简单的方法是仅依赖于词汇层面的信息匹配;然而由于其局限性在于无法识别和理解不同单词之间的深层语义关联。
基于语义相近性检索的方法中,通过向量模型计算查询与文档之间的语义相近程度来实现信息匹配。该方法在处理自然语言查询时表现出色,尤其适用于自然语言查询场景。
基于分数的相似度搜索**
通过计算每个文档与查询之间的相关程度来确定其相似性评分。这种评分机制通常采用的方法包括余弦相似度计算或基于距离的距离函数评估。
相似度搜索的工作原理
嵌入和向量的本质
表示 是单词或文档在高维空间中的数值形式。它们捕捉了语义信息以便更有效地比较查询与文档。
评估方法** * 余弦相似性指标 用于通过计算两向量间夹角的余弦值来确定它们的相似程度。其数值越接近1,则表示两者间的相似程度越高。
- 在欧几里得空间中测量两点间的点间距时采用欧几里得距离这一指标,在此情况下数值越小则表示两者间的近似程度越高
评估并排序搜索结果
通过计算相似度后获得每个文档的一个分数值。然后根据这些分数值的高低顺序进行排序。
简要介绍搜索算法
- TF-IDF:基于单词在其所在文档中的出现频率以及在整个语料库中出现频率的差异来计算其重要性。
- BM25:是对TF-IDF的一种优化版本,在信息检索领域进一步提升了相关性评估。
- 神经搜索:通过深度学习技术生成具有语境感知能力的嵌入表示。
向量存储中的搜索类型
相似度搜索 :找到与查询最相似的文档。适用于语义搜索应用。
基于最大边际相关性的搜索方法:通过优先筛选具有多样性和相关性的文档,在搜索结果中实现相关性与多样性的平衡。
稀疏检索器:采用基于关键词的传统方法进行文档检索操作,例如采用 TF-IDF 或 BM25 等方法来进行文档检索.该技术体系特别适用于仅掌握有限语境信息的情景.
密集检索器 :基于紧密的向量嵌入来捕获语义信息。通常用于基于深度学习构建现代搜索系统。
密集检索器 :基于紧密的向量嵌入来捕获语义信息。通常用于基于深度学习构建现代搜索系统。
混合检索技术 :将稀疏与密集检索策略进行融合。采用高精度与广覆盖相结合的方式,在保证搜索效率的同时实现最佳综合性能。
向量存储作为检索器
- 功能 :通过调用
.as_retriever()方法将向量存储转换为检索器的方式,在不影响现有接口的情况下实现了对LangChain检索器接口的支持,并提供了高度可定制的解决方案以适应不同检索策略的需求。- 使用案例 :在处理复杂应用场景时尤其适用,在这些情况下检索器作为更大流程中的组件能够支持集成多种检索方法以及深入分析查询结果等功能。
总体而言而言,则是尽管直接采用向量存储实现搜索的基本功能会具备一定的基础能力。然而将其转化为一个用于此类场景下的专门检索器,则能够带来更高的灵活性,并能够在LangChain生态系统中实现良好的集成。这种转变不仅有助于提升数据组织效率还能更好地支持更为复杂且实用的数据检索策略及其应用场景
集成向量数据库
- Chroma 是一个开源的向量数据库 用于 AI 应用 拥有高效的存储与检索功能。
- FAISS 由 Facebook AI 开发 是专门用于高效相似度搜索与密集向量聚类的库。
- Pinecone 是一种托管式的向量数据库服务 提供高性能的向量相似度搜索 并附带便捷的 API 以便开发人员轻松构建可扩展的 AI 应用程序。
- Qdrant (发音类似于 quadrant)是一个专门设计用于向量相似度搜索引擎 的产品 包括易于使用的 API 能够存储 搜索与管理高维向量 还支持额外的数据加载与扩展过滤功能 适用于神经网络或其他基于语义的应用场景。
- Elasticsearch 是一个分布式 RESTful 搜索与分析引擎 支持高效的向量搜索功能 可在大数据集上快速执行相似性查询。
- MongoDB Atlas 提供了针对向量嵌入的支持 包括高效存储 索引与查询功能 并能无缝集成操作数据 从而方便实现基于 AI 的应用。
- pgvector(PostgreSQL) 是 PostgreSQL 的一个扩展 插件增强了对高维向量相似度搜索的支持 让用户能够在关系型数据库中高效存储与查询高维数据。
- Neo4j 是一个图形数据库 通过存储节点与关系实现对原始数据以及嵌入数据的支持 并内置了强大的图计算能力 同时也支持高效的图模式查询及嵌入式查询。
- Weaviate 是一个开源的面向对象的向量数据库 支持存储数据对象及其嵌入 同时提供丰富的数据类型支持 和语义级别的搜索能力。
- Milvus 是一个专为大规模机器学习模型生成的大规模嵌入向量设计的关系型数据库 支持高性能的索引与查询操作 从而优化复杂的数据分析任务。
这些向量被用来构建一个结合了高效相似度搜索与高维数据管理的应用系统,并且发挥着至关重要的作用
LangChain 支持一套完整的接口体系,并非仅提供单一的技术实现方案,在处理向量存储交互方面具有显著的优势。它能够方便地进行向量数据的查询与管理,并且能够灵活地切换不同技术实现方案以适应特定需求。
该接口包括用于 写入 、删除 和 搜索 向量存储中的文档的核心方法。
主要方法如下:
-
add_documents: 将一批文本加入向量存储系统中。upsert_documents: 在该系统中既可新增文件内容,在现有记录存在时也可对其进行更新。- 在本教程后续部分中则会详细介绍并行处理方法
upsert_documents_parallel,该方法旨在提升大规模数据处理效率。
- 在本教程后续部分中则会详细介绍并行处理方法
-
delete_documents: 通过向量存储消除一批文档。similarity_search: 通过检索技术获取与特定查询近似的文件。
Chorma
Create db
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="bge-m3",
base_url='http://localhost:9997/v1',
api_key='cannot be empty',
# dimensions=1024,
)
vector_store = Chroma(
collection_name="langchain_store",
embedding_function=embeddings
)
Add Documents
将一组 Documents 插入到向量存储中,并同时生成一组 id;id 的值可以根据需求自行设定。返回每个 Documents 对应的 id 值。
如果 embedding 功能会出现 InternalServerError, 可能文档文本有问题
若未指定ID,则Chroma将自动生成每个文档的唯一标识符;然而,在某些情况下你可能希望直接指定ID以简化管理流程
from langchain_core.documents import Document
texts = [
"AI helps doctors diagnose diseases faster, improving patient outcomes.",
"AI can analyze medical images to detect conditions like cancer.",
"Machine learning predicts patient outcomes based on health data.",
"AI speeds up drug discovery by predicting the effectiveness of compounds.",
"AI monitors patients remotely, enabling proactive care for chronic diseases.",
"AI automates administrative tasks, saving time for healthcare workers.",
"NLP extracts insights from electronic health records for better care.",
"AI chatbots help with patient assessments and symptom checking.",
"AI improves drug manufacturing, ensuring better quality and efficiency.",
"AI optimizes hospital operations and reduces healthcare costs."
]
# 重复内容会导致报错
# texts = [
# "AI ",
# "AI",
# "Machine learning",
# ]
documents = [
Document(text, metadata={"source":text})
for text in texts
]
ids = [f'{i}' for i in range(len(documents))]
ret_ids = vector_store.add_documents(documents=documents, ids=ids)
print(ret_ids)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
采用不同的处理策略针对不同向量数据集。在 Chrome 中不会报错, 这是因为它直接替代了 ID 的原始数据; 但当使用 Faiss 时会抛出错误, 错误信息表明 ID 已经存在。
ret_ids = vector_store.add_documents(documents=documents, ids=ids)
print(ret_ids)
ret_ids = vector_store.add_documents(documents=documents)
print(ret_ids)
from langchain_core.documents import Document
doc1 = Document(page_content="This is a test document.", metadata={"source": "test"})
doc2 = Document(page_content="This is an updated document.", metadata={"source": "test"})
# 文档 ID 可以根据文本的哈希值来生成唯一的 ID
ids = ["doc_1", "doc_2"]
# 第一次插入
vector_store.add_documents(documents=[doc1], ids=["doc_1"])
# 使用 upsert_documents 更新或插入文档
vector_store.upsert_documents(documents=[doc2], ids=["doc_1"])
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[4], line 13
10 vector_store.add_documents(documents=[doc1], ids=["doc_1"])
12 # 使用 upsert_documents 更新或插入文档
---> 13 vector_store.upsert_documents(documents=[doc2], ids=["doc_1"])
AttributeError: 'Chroma' object has no attribute 'upsert_documents'
Chroma 缺乏直接插入文档的方法, 但提供了具有相同功能的 update downwards 函数。其中 add downwards 函数看似也具备类似的更新能力;针对每个 ID,则会将现有文档替换为新的 Document 对象。
ret_ids = vector_store.update_documents(ids=ids, documents=documents)
print(ret_ids)
None
Delete Documents by id
chrome 没有 delete_documents 函数, 只能基于 id 删除
ids_4_delete = ["3"]
import traceback
try:
vector_store.delete(ids=ids_4_delete)
print(f'ID:{ids_4_delete} deleted')
except Exception as e:
print(traceback.format_exc())
print('deleted non-successfully')
删除整个数据库
vector_store.delete_collection()
Search
- 仅用于检索单个文档的
similarity_search函数。- 提供综合评分的
similarity_search_with_score函数不仅检索文档内容,还能获取每个结果的相关得分。 - 对于那些希望自定义查询向量与数据库中向量计算方式的人而言,提供高度灵活性的
similarity_search_by_vector函数应运而生。此外,在这一函数的基础上还开发了带有评分输出版本。
- 提供综合评分的
该方法依据给定查询和嵌入向量,在向量存储中搜索与其最相似的文档。它常用于简单的、一次性的检索操作。
a_query = "How does AI improve healthcare?"
results = vector_store.similarity_search(
query=a_query,
k=3
)
for index, doc in enumerate(results):
print(f"[{index}]{doc.page_content} [{doc.metadata}]")
[0]AI monitors patients remotely, enabling proactive care for chronic diseases. [{'source': 'AI monitors patients remotely, enabling proactive care for chronic diseases.'}]
[1]AI chatbots help with patient assessments and symptom checking. [{'source': 'AI chatbots help with patient assessments and symptom checking.'}]
[2]AI automates administrative tasks, saving time for healthcare workers. [{'source': 'AI automates administrative tasks, saving time for healthcare workers.'}]
a_query = "How does AI improve healthcare?"
results = vector_store.similarity_search_with_score(
query=a_query,
k=3
)
for index, (doc, score) in enumerate(results):
print(f"[{index}][SIM={score:3f}]{doc.page_content} [{doc.metadata}]")
[0][SIM=0.718768]AI monitors patients remotely, enabling proactive care for chronic diseases. [{'source': 'AI monitors patients remotely, enabling proactive care for chronic diseases.'}]
[1][SIM=0.807140]AI chatbots help with patient assessments and symptom checking. [{'source': 'AI chatbots help with patient assessments and symptom checking.'}]
[2][SIM=0.815210]AI automates administrative tasks, saving time for healthcare workers. [{'source': 'AI automates administrative tasks, saving time for healthcare workers.'}]
query_embedder = OpenAIEmbeddings(
model="bge-m3",
base_url='http://localhost:9997/v1',
api_key='cannot be empty',
# dimensions=1024,
)
query_vector = query_embedder.embed_query(a_query)
results = db.similarity_search_by_vector(query_vector, k=3)
for index, doc in enumerate(results):
print(f"[{index}]{doc.page_content} [{doc.metadata}]")
""" 带 score 版本
results = db.similarity_search_with_relevance_scores(query_vector, k=3)
for index, (doc, score) in enumerate(results):
print(f"[{index}][SIM={score:3f}]{doc.page_content} [{doc.metadata}]")
"""
as_retriever
作为Chroma向量数据库的核心功能之一(aspect),as_retriever方法负责将向量数据库转换为一个Retriever对象(object),这样可以在LangChain管道中进行检索操作(pipeline)。通过这一机制(Aspect),Chroma能够与LangChain实现更好的集成(integration),支持多种检索策略(strategy)并能够与其它组件(如问答系统、文档检索系统等)实现无缝协作(integration)。
以下是参数说明
retriever = vector_store.as_retriever(
# Defines the type of search that the Retriever should perform. Can be “similarity” (default), “mmr”, or “similarity_score_threshold”.
search_type="mmr",
# k: num of documents returned
# fetch_k: Amount of documents to pass to MMR algorithm
# lambda_mult: Diversity of results returned by MMR; 1 for minimum diversity and 0 for maximum.
search_kwargs={
"k": 1,
"fetch_k": 2,
"lambda_mult": 0.5
},
)
# Retrieve more documents with higher diversity
# Useful if your dataset has many similar documents
vector_store.as_retriever(
search_type="mmr",
search_kwargs={'k': 6, 'lambda_mult': 0.25}
)
# Fetch more documents for the MMR algorithm to consider
# But only return the top 5
vector_store.as_retriever(
search_type="mmr",
search_kwargs={'k': 5, 'fetch_k': 50}
)
# Only retrieve documents that have a relevance score
# Above a certain threshold
vector_store.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={'score_threshold': 0.8}
)
# Only get the single most similar document from the dataset
vector_store.as_retriever(search_kwargs={'k': 1})
# Use a filter to only retrieve documents from a specific paper
vector_store.as_retriever(
search_kwargs={'filter': {'paper_title':'GPT-4 Technical Report'}}
)
VectorStoreRetriever(tags=['FAISS', 'OpenAIEmbeddings'], vectorstore=<langchain_community.vectorstores.faiss.FAISS object at 0x7fe87b72e3f0>, search_kwargs={'filter': {'paper_title': 'GPT-4 Technical Report'}})
最重要的是集成到 langchain 框架, 直接 invoke 调用
retriever = vector_store.as_retriever(search_kwargs={'k': 3})
a_query = "How does AI improve healthcare?"
retriever.invoke(a_query)
[Document(metadata={'source': 'AI monitors patients remotely, enabling proactive care for chronic diseases.'}, page_content='AI monitors patients remotely, enabling proactive care for chronic diseases.'),
Document(metadata={'source': 'AI chatbots help with patient assessments and symptom checking.'}, page_content='AI chatbots help with patient assessments and symptom checking.'),
Document(metadata={'source': 'AI automates administrative tasks, saving time for healthcare workers.'}, page_content='AI automates administrative tasks, saving time for healthcare workers.')]
Faiss
初始化
import faiss
from langchain_community.vectorstores import FAISS
from langchain_community.docstore.in_memory import InMemoryDocstore
from langchain_openai import OpenAIEmbeddings
openai_embedding = OpenAIEmbeddings(
model="bge-m3",
base_url='http://localhost:9997/v1',
api_key='cannot be empty',
# dimensions=1024,
)
embed_dim = len(openai_embedding.embed_query("hello world"))
index = faiss.IndexFlatL2(embed_dim)
vector_store = FAISS(
embedding_function=openai_embedding,
index=index,
docstore= InMemoryDocstore(),
index_to_docstore_id={}
)
Add Documents
from langchain_core.documents import Document
texts = [
"AI helps doctors diagnose diseases faster, improving patient outcomes.",
"AI can analyze medical images to detect conditions like cancer.",
"Machine learning predicts patient outcomes based on health data.",
"AI speeds up drug discovery by predicting the effectiveness of compounds.",
"AI monitors patients remotely, enabling proactive care for chronic diseases.",
"AI automates administrative tasks, saving time for healthcare workers.",
"NLP extracts insights from electronic health records for better care.",
"AI chatbots help with patient assessments and symptom checking.",
"AI improves drug manufacturing, ensuring better quality and efficiency.",
"AI optimizes hospital operations and reduces healthcare costs."
]
documents = [
Document(text)
for text in texts
]
ids = [f'{i}' for i in range(len(documents))]
ret_ids = vector_store.add_documents(documents=documents, ids=ids)
print(ret_ids)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
增加重复 Document 会报错
ValueError: Tried to add ids that already exist: {'9', '5', '7', '0', '6', '8', '1', '4', '2', '3'}
import traceback
try:
ret_ids = vector_store.add_documents(documents=documents, ids=ids)
print(ret_ids)
except Exception as e:
print(traceback.format_exc())
Traceback (most recent call last):
File "/tmp/ipykernel_177930/1610519831.py", line 3, in <module>
ret_ids = vector_store.add_documents(documents=documents, ids=ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data02/hyzhang10/miniconda3/envs/xp-nlp/lib/python3.12/site-packages/langchain_core/vectorstores/base.py", line 286, in add_documents
return self.add_texts(texts, metadatas, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data02/hyzhang10/miniconda3/envs/xp-nlp/lib/python3.12/site-packages/langchain_community/vectorstores/faiss.py", line 341, in add_texts
return self.__add(texts, embeddings, metadatas=metadatas, ids=ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data02/hyzhang10/miniconda3/envs/xp-nlp/lib/python3.12/site-packages/langchain_community/vectorstores/faiss.py", line 316, in __add
self.docstore.add({id_: doc for id_, doc in zip(ids, documents)})
File "/data02/hyzhang10/miniconda3/envs/xp-nlp/lib/python3.12/site-packages/langchain_community/docstore/in_memory.py", line 28, in add
raise ValueError(f"Tried to add ids that already exist: {overlapping}")
ValueError: Tried to add ids that already exist: {'9', '5', '7', '0', '6', '8', '1', '4', '2', '3'}
Get by id
通过 id 返回 Documents. Chroma 没有这个函数
docs = vector_store.get_by_ids(["1","2"])
docs
[Document(id='1', metadata={}, page_content='AI can analyze medical images to detect conditions like cancer.'),
Document(id='2', metadata={}, page_content='Machine learning predicts patient outcomes based on health data.')]
其他功能和 chroma 类似, 不赘述了
