理论奠定了坚实的根基,但动手实践对于了解向量数据库的运作方式非常重要。实践涉及直接与向量数据库进行交互,使用一个流行且易于设置的库。执行基本创建、读取、更新和删除(CRUD)操作,以巩固对向量及其相关元数据管理方式的理解。我们将使用chromadb,这是一个开源向量数据库,设计宗旨是简单易用,尤其适合本地起步。尽管存在Qdrant、Weaviate和Milvus等其他强大的选项(我们稍后会提到它们),但ChromaDB使我们能够快速设置环境,并专注于核心交互。设置您的环境首先,请确保您已安装Python(建议使用3.8或更高版本)。您需要安装chromadb库和一个用于嵌入文本数据的句子转换器模型。打开您的终端或命令提示符并运行:pip install chromadb sentence-transformers此命令安装ChromaDB和sentence-transformers库,该库提供了轻松访问预训练模型以创建文本嵌入的功能。连接到数据库ChromaDB可以在内存中运行,也可以将数据持久化到磁盘。本次练习中,我们将使用一个将数据保存到本地目录的持久化客户端。import chromadb import uuid # 用于生成唯一ID # 设置一个持久化客户端。数据将存储在'my_vector_db'目录中。 client = chromadb.PersistentClient(path="./my_vector_db") print("ChromaDB客户端已初始化。") # 您可以验证存储路径: # print(f"Storage path: {client.settings.persist_directory}")此代码初始化一个客户端实例,它将把数据存储在当前工作目录中名为my_vector_db的子目录里。如果该目录不存在,ChromaDB将创建它。创建集合在ChromaDB(以及许多其他向量数据库)中,数据被组织成“集合”(collections),这类似于关系数据库中的表或搜索引擎中的索引。每个集合通常包含相同维度的向量,并且通常使用特定的距离度量。让我们创建一个名为documents_collection的集合。ChromaDB允许直接指定嵌入函数,这简化了过程,因为它将自动处理文本嵌入。from chromadb.utils import embedding_functions # 使用预构建的句子转换器模型进行嵌入 # 此模型适用于通用语义搜索 sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2") # 创建集合,指定嵌入函数。 # 如果集合已存在,请使用get_or_create_collection。 collection_name = "documents_collection" try: client.delete_collection(name=collection_name) # 如果上次运行存在,则删除 print(f"集合 '{collection_name}' 已删除。") except: pass # 集合不存在,忽略错误 collection = client.create_collection( name=collection_name, embedding_function=sentence_transformer_ef, metadata={"hnsw:space": "cosine"} # 指定余弦距离(文本常用) ) print(f"集合 '{collection.name}' 创建成功。")在这里,我们首先尝试删除任何同名集合以确保干净的开始。然后,我们创建集合,传入我们选择的嵌入函数(通过sentence-transformers的all-MiniLM-L6-v2),并使用metadata参数(特别是对于ChromaDB默认使用的HNSW索引)将cosine相似度指定为距离度量。添加数据(创建/更新插入)现在,让我们添加一些数据。ChromaDB集合中的每个项目都需要:一个唯一的id。数据本身(在本例中为documents,即文本字符串)。可选的metadata(与向量关联的键值对)。ChromaDB的add或upsert方法会使用我们在创建集合时提供的函数自动处理文档嵌入。通常更推荐使用upsert,因为它可以在ID已存在时添加新项目或更新现有项目。# 示例数据:带有相关元数据的文档 docs = [ "The Catcher in the Rye is a classic novel by J.D. Salinger.", "Artificial intelligence is transforming many industries.", "Paris is the capital city of France, known for the Eiffel Tower.", "To Kill a Mockingbird examines themes of justice and prejudice." "Machine learning algorithms learn patterns from data.", "The Louvre Museum in Paris houses famous works of art." ] metadata = [ {'genre': 'Fiction', 'year': 1951, 'topic': 'Literature'}, {'genre': 'Non-fiction', 'year': 2023, 'topic': 'Technology'}, {'genre': 'Non-fiction', 'year': 1889, 'topic': 'Geography'}, # 埃菲尔铁塔竣工年份 {'genre': 'Fiction', 'year': 1960, 'topic': 'Literature'}, {'genre': 'Non-fiction', 'year': 2022, 'topic': 'Technology'}, {'genre': 'Non-fiction', 'year': 1793, 'topic': 'Art'} # 卢浮宫开放年份 ] # 为每个文档生成唯一ID ids = [str(uuid.uuid4()) for _ in docs] # 将数据添加到集合 collection.add( documents=docs, metadatas=metadata, ids=ids ) print(f"已向集合添加{collection.count()}个项目。") # 验证是否添加了一个项目(可选) # print(collection.get(ids=[ids[0]]))我们准备了文档列表、相应的元数据字典和唯一ID。collection.add方法接收这些列表。ChromaDB处理documents,使用all-MiniLM-L6-v2模型生成嵌入,并将嵌入与文档、元数据和ID一起存储。搜索相似数据(读取)向量数据库中的主要操作是相似度搜索。我们提供一个查询(本例中为文本),ChromaDB使用相同的嵌入函数对其进行嵌入,然后根据所选的距离度量(余弦相似度)找到存储的与查询向量最接近的向量。# 查询集合 query_texts = ["Tell me about famous books.", "What is AI?"] results = collection.query( query_texts=query_texts, n_results=2, # 为每个查询请求最相似的2个结果 include=['documents', 'distances', 'metadatas'] # 指定要返回的数据 ) # 漂亮地打印结果 import json print("\n搜索结果:") print(json.dumps(results, indent=2))collection.query方法接收我们的query_texts。我们请求每个查询的前n_results=2个匹配项。include参数允许我们指定要返回存储数据的哪些部分(原始文档、距离分数和元数据)。结果显示了集合中每个查询最接近的匹配项,以及它们的距离(余弦距离越小表示相似度越高)。使用元数据过滤(读取)向量数据库通常允许将相似度搜索与元数据过滤结合起来。这对于根据特定属性缩小结果范围非常有效。让我们查找与“欧洲地标”相似的文档,但仅考虑主题为“Geography”或“Art”的文档。# 使用元数据过滤查询 filtered_results = collection.query( query_texts=["European landmarks"], n_results=2, where={"topic": {"$in": ["Geography", "Art"]}}, # 过滤:主题必须是'Geography'或'Art' include=['documents', 'distances', 'metadatas'] ) print("\n过滤后的搜索结果(主题:地理或艺术):") print(json.dumps(filtered_results, indent=2))where子句使用字典来定义过滤条件。这里,$in指定元数据中的topic字段必须是所提供列表中的值之一。请注意这与未过滤的搜索相比,结果可能发生的变化。更新数据(更新)如前所述,许多向量数据库,包括ChromaDB,都使用upsert操作处理更新。如果您使用集合中已存在的ID调用add或upsert,ChromaDB将用为该ID提供的新数据替换现有条目(向量、文档、元数据)。# 让我们更新第一个文档的元数据 first_id = ids[0] print(f"\n正在更新ID为“{first_id}”的元数据") # 获取此ID的原始文档文本 original_doc = collection.get(ids=[first_id], include=['documents'])['documents'][0] collection.update( ids=[first_id], metadatas=[{'genre': 'Classic Fiction', 'year': 1951, 'topic': 'Literature', 'status': 'Updated'}] # 如果需要,您也可以更新文档或嵌入 # documents=[new_document_text] # 如果您想更改文本,例如 ) # 验证更新 updated_item = collection.get(ids=[first_id], include=['metadatas']) print("更新后的项目元数据:") print(json.dumps(updated_item['metadatas'][0], indent=2))我们使用collection.update专门针对first_id。我们只提供了metadatas参数,因此只覆盖了该项目的元数据。原始向量和文档仍与该ID关联,除非明确更新。删除数据(删除)最后,您可以使用ID从集合中移除项目。# 删除我们添加的第二个项目 item_to_delete_id = ids[1] print(f"\n正在删除ID为“{item_to_delete_id}”的项目") initial_count = collection.count() collection.delete(ids=[item_to_delete_id]) final_count = collection.count() print(f"删除前集合计数:{initial_count}") print(f"删除后集合计数:{final_count}") # 验证它是否已删除(尝试获取它应返回空结果或错误) try: deleted_item_check = collection.get(ids=[item_to_delete_id]) if not deleted_item_check['ids']: print(f"项目“{item_to_delete_id}”已成功删除。") else: print(f"项目“{item_to_delete_id}”删除失败。") # 不应发生 except Exception as e: # 根据客户端版本,它可能会引发错误或返回空值 print(f"未找到项目“{item_to_delete_id}”(可能已删除)。错误:{e}") collection.delete方法从集合中移除指定项目。我们通过检查集合计数并尝试检索已删除的项目来验证删除。操作总结本次实践练习演示了使用ChromaDB在向量数据库中数据的基本生命周期:digraph CRUD_Flow { rankdir=LR; node [shape=box, style=filled, fillcolor="#e9ecef", fontname="Arial"]; edge [fontname="Arial"]; Start [label="初始化客户端", shape=ellipse, fillcolor="#a5d8ff"]; CreateCollection [label="创建集合\n(documents_collection)", fillcolor="#96f2d7"]; AddData [label="添加/更新插入数据\n(文档、元数据、ID)", fillcolor="#ffec99"]; QueryData [label="查询(相似度搜索)", fillcolor="#bac8ff"]; FilterQuery [label="带元数据过滤的查询", fillcolor="#d0bfff"]; UpdateData [label="更新项目\n(元数据示例)", fillcolor="#ffe066"]; DeleteData [label="删除项目", fillcolor="#ffc9c9"]; End [label="交互完成", shape=ellipse, fillcolor="#a5d8ff"]; Start -> CreateCollection; CreateCollection -> AddData [label=" 存储向量"]; AddData -> QueryData [label=" 搜索"]; QueryData -> FilterQuery [label=" 细化搜索"]; FilterQuery -> UpdateData [label=" 修改"]; UpdateData -> DeleteData [label=" 移除"]; DeleteData -> End; AddData -> UpdateData [label=" 更新插入现有ID"]; // 更新的替代路径 }向量数据库集合的基本交互流程。我们连接到数据库,创建了一个结构化容器(集合),添加了向量化数据以及描述性元数据,执行了相似度搜索(包括通用和过滤后的),更新了一个条目,最后移除了一个项目。这些主要操作构成了实现语义搜索和由向量相似性支持的其他应用的基本要素。您可以随意添加更多数据、尝试不同的查询以及研究各种元数据过滤组合来进行进一步试验。