使用 Chroma DB,管理文本文档、将文本嵌入以及进行相似度搜索。
随着大型语言模型 (LLM) 及其应用的兴起,我们看到向量数据库越来越受欢迎。这是因为使用 LLM 需要一种与传统机器学习模型不同的方法。
LLM 的核心支持技术之一是向量嵌入。虽然计算机不能直接理解文本,但嵌入以数字表示文本。所有用户提供的文本都将转换为嵌入,用于生成响应。
将文本转换为嵌入是一个耗时的过程。为了避免这种情况,我们使用专门设计的矢量数据库,用于有效存储和检索矢量嵌入。在本教程中,我们将了解矢量存储和 Chroma DB,这是一个用于存储和管理嵌入的开源数据库。此外,我们将学习如何添加和删除文档、执行相似性搜索以及将文本转换为嵌入。
向量存储是专门为有效地存储和检索向量嵌入而设计的数据库。之所以需要它们,是因为像 SQL 这样的传统数据库没有针对存储和查询大型向量数据进行优化。
嵌入在高维空间中以数字向量格式表示数据(通常是非结构化数据,如文本)。传统的关系数据库不太适合存储和搜索这些向量表示。
向量存储可以使用相似性算法对相似的向量进行索引和快速搜索。它允许应用程序在给定目标向量查询的情况下查找相关向量。
在个性化聊天机器人的情况下,用户输入生成式 AI 模型的提示。然后,该模型使用相似性搜索算法在文档集合中搜索相似文本。然后,由此产生的信息用于生成高度个性化和准确的响应。这是通过在向量存储中嵌入和向量索引来实现的。
Chroma DBhttps://docs.trychroma.com/ChromaDB是一个开源矢量存储,用于存储和检索矢量嵌入。它的主要用途是保存嵌入和元数据,以便以后由大型语言模型使用。此外,它还可用于文本数据的语义搜索引擎。
Chroma DB主要特点:
all-MiniLM-L6-v2
在下一部分中,我们将使用 Chroma 和 OpenAI API 来创建我们自己的矢量数据库。
在本节中,将创建一个向量数据库,添加集合,向集合添加文本,并执行查询搜索。
首先,我们将为向量数据库和更好的嵌入模型安装。确保您已设置 OpenAI API 密钥。
注意:Chroma 需要 SQLite 3.35 或更高版本。如果遇到问题,请升级到 Python 3.11 或安装旧版本的Chroma
- # 环境安装
- !pip install chromadb openai
您可以通过创建不带设置的 Chroma DB 客户端来创建用于测试的内存数据库。
在我们的例子中,我们将创建一个持久数据库,该数据库将存储在“db/”目录中,并在后端使用 DuckDB。
- import chromadb
- from chromadb.config import Settings
-
- client = chromadb.Client(Settings(chroma_db_impl="duckdb+parquet",
- persist_directory="db/"
- ))
之后,我们将使用客户端创建一个集合对象。它类似于在传统数据库中创建表。
collection = client.create_collection(name="Students")
为了将文本添加到我们的集合中,我们需要生成有关学生、俱乐部和大学的随机文本。您可以使用 ChatGPT 生成随机文本。这很简单。
- student_info = """
- Alexandra Thompson, a 19-year-old computer science sophomore with a 3.7 GPA,
- is a member of the programming and chess clubs who enjoys pizza, swimming, and hiking
- in her free time in hopes of working at a tech company after graduating from the University of Washington.
- """
-
- club_info = """
- The university chess club provides an outlet for students to come together and enjoy playing
- the classic strategy game of chess. Members of all skill levels are welcome, from beginners learning
- the rules to experienced tournament players. The club typically meets a few times per week to play casual games,
- participate in tournaments, analyze famous chess matches, and improve members' skills.
- """
-
- university_info = """
- The University of Washington, founded in 1861 in Seattle, is a public research university
- with over 45,000 students across three campuses in Seattle, Tacoma, and Bothell.
- As the flagship institution of the six public universities in Washington state,
- UW encompasses over 500 buildings and 20 million square feet of space,
- including one of the largest library systems in the world.
现在,我们将使用该函数添加带有元数据和唯一 ID 的文本数据。之后,Chroma 将自动下载模型以将文本转换为嵌入并将其存储在“Students”集合中。add
all-MiniLM-L6-v2
- collection.add(
- documents = [student_info, club_info, university_info],
- metadatas = [{"source": "student info"},{"source": "club info"},{'source':'university info'}],
- ids = ["id1", "id2", "id3"]
- )
要运行相似性搜索,您可以使用该函数并以自然语言提问。它会将查询转换为嵌入,并使用相似性算法得出类似的结果。在我们的例子中,它返回了两个类似的结果。query
- results = collection.query(
- query_texts=["What is the student name?"],
- n_results=2
- )
-
- results
使用其它模型
- from chromadb.utils import embedding_functions
- openai_ef = embedding_functions.OpenAIEmbeddingFunction(
- model_name="text-embedding-ada-002"
- )
- students_embeddings = openai_ef([student_info, club_info, university_info])
- print(students_embeddings)
就像关系数据库一样,您可以更新或删除集合中的值。为了更新文本和元数据,我们将提供记录和新文本的特定 ID。
- collection2.update(
- ids=["id1"],
- documents=["Kristiane Carina, a 19-year-old computer science sophomore with a 3.7 GPA"],
- metadatas=[{"source": "student info"}],
- )
运行一个简单的查询以检查是否已成功进行更改。
- results = collection2.query(
- query_texts=["What is the student name?"],
- n_results=2
- )
-
- results
验证结果
要从集合中删除记录,我们将使用“删除”功能并指定唯一 ID。
- collection2.delete(ids = ['id1'])
-
-
- results = collection2.query(
- query_texts=["What is the student name?"],
- n_results=2
- )
-
- results
学生信息文本已被删除;取而代之的是,我们得到了下一个最好的结果。
- # 创建客户端
- # client = chromadb.Client() 内存模式
- client = chromadb.PersistentClient(path="./chromac") # 数据保存在磁盘
- # chroma_client = chromadb.HttpClient(host="localhost", port=8000) docker客户端模式
- # 遍历集合
- client.list_collections()
- # 创建新集合
- collection = client.create_collection("testname")
- # 获取集合
- collection = client.get_collection("testname")
- # 创建或获取集合
- collection = client.get_or_create_collection("testname")
- # 删除集合
- client.delete_collection("testname")
- # 创建或获取集合
- collection = client.get_or_create_collection(name="my_collection2")
- # collection = client.create_collection(name="my_collection2")
- # collection = client.create_collection(name="my_collection", embedding_function=emb_fn)
- # collection = client.get_collection(name="my_collection", embedding_function=emb_fn)
- # Chroma集合创建时带有一个名称和一个可选的嵌入函数。如果提供了嵌入函数,则每次获取集合时都必须提供。
- # 获取集合中最新的5条数据
- collection.peek()
- # 添加数据
- collection.add(
- documents=["2022年2月2号,美国国防部宣布:将向欧洲增派部队,应对俄乌边境地区的紧张局势.", " 2月17号,乌克兰军方称:东部民间武装向政府军控制区发动炮击,而东部民间武装则指责乌政府军先动用了重型武器发动袭击,乌东地区紧张局势持续升级"],
- metadatas=[{"source": "my_source"}, {"source": "my_source"}],
- ids=["id1", "id2"]
- )
- # 如果 Chroma 收到一个文档列表,它会自动标记并使用集合的嵌入函数嵌入这些文档(如果在创建集合时没有提供嵌入函数,则使用默认值)。Chroma也会存储文档本身。如果文档过大,无法使用所选的嵌入函数嵌入,则会出现异常。
- # 每个文档必须有一个唯一的相关ID。尝试.添加相同的ID两次将导致错误。可以为每个文档提供一个可选的元数据字典列表,以存储附加信息并进行过滤。
- # 或者,您也可以直接提供文档相关嵌入的列表,Chroma将存储相关文档,而不会自行嵌入。
- # collection.add(
- # embeddings=[[1.2, 2.3, 4.5], [6.7, 8.2, 9.2]],
- # documents=["This is a document", "This is another document"],
- # metadatas=[{"source": "my_source"}, {"source": "my_source"}],
- # ids=["id1", "id2"]
- # )
- # 改数据
- # 更新所提供 id 的嵌入、元数据或文档。
- def update(ids: OneOrMany[ID],
- embeddings: Optional[OneOrMany[Embedding]] = None,
- metadatas: Optional[OneOrMany[Metadata]] = None,
- documents: Optional[OneOrMany[Document]] = None) -> None
- # 更新所提供 id 的嵌入、元数据或文档,如果不存在,则创建它们。
- def upsert(ids: OneOrMany[ID],
- embeddings: Optional[OneOrMany[Embedding]] = None,
- metadatas: Optional[OneOrMany[Metadata]] = None,
- documents: Optional[OneOrMany[Document]] = None) -> None
- # 删除数据
- # 根据 ID 和/或 where 过滤器删除嵌入数据
- def delete(ids: Optional[IDs] = None,
- where: Optional[Where] = None,
- where_document: Optional[WhereDocument] = None) -> None
- # collection.delete(ids=["3", "4", "5"])
- # 查询数据
- results = collection.query(
- query_embeddings=[[11.1, 12.1, 13.1],[1.1, 2.3, 3.2], ...],
- n_results=10,
- where={"metadata_field": "is_equal_to_this"},
- where_document={"$contains":"search_string"}
- )
- 或者:
- results = collection.query(
- query_texts=["俄乌战争发生在哪天?"],
- n_results=2
- )
- print(156, results)
- 156 {'ids': [['id1', 'id2']], 'embeddings': None, 'documents': [['2022年2月2号,美国国防部宣布:将向欧洲增派部队,应对俄乌边境地区的紧张局势.',
- ' 2月17号,乌克兰军方称:东部民间武装向政府军控制区发动炮击,而东部民间武装则指责乌政府军先动用了重型武器发动袭击,乌东地区紧张局势持续升级']],
- 'metadatas': [[{'source': 'my_source'}, {'source': 'my_source'}]], 'distances': [[1.2127416133880615, 1.3881784677505493]]}
接下来查看LangChain中结合ChromaDB使用
- # /home/knowqa/know_env/lib/python3.10/site-packages/langchain/vectorstores
- from langchain.vectorstores import Chroma
-
- # langchain 默认文档 collections [Collection(name=langchain)]
- # 持久化数据
- persist_directory = './chromadb'
- vectordb = Chroma.from_documents(documents=docs, embedding=embedding, persist_directory=persist_directory)
- vectordb.persist()
- # 直接加载数据
- vectordb = Chroma(persist_directory="./chromadb", embedding_function=embeddings)
- eg:
- from langchain.embeddings.openai import OpenAIEmbeddings
- from langchain.text_splitter import CharacterTextSplitter
- from langchain.vectorstores import Chroma
- from langchain.document_loaders import TextLoader
- import os
- os.environ["OPENAI_API_KEY"] = 'sk-xxxxxx'
- loader = TextLoader('./russia.txt', encoding='gbk') #中文必须带 encoding='gbk'
- documents = loader.load()
- text_splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=0)
- # chunk_size=1000表示每次读取或写入数据时,数据的大小为400个字节, 约200~400个汉字
- # 对于英文LangChain一般会使用RecursiveCharacterTextSplitter处理。由于中文的复杂性,会使用到jieba等处理工具预处理中文语句。
- docs = text_splitter.split_documents(documents)
- embedding = OpenAIEmbeddings()
- vectordb = Chroma.from_documents(docs, embeddings)
- query = "What did the president say about Ketanji Brown Jackson"
- docs = vectordb.similarity_search(query)
- print(docs[0].page_content) # 默认是返回4条数据, k=4
- # 直接加载数据库,然后查询相似度的文本
- vectordb = Chroma(persist_directory=persist_directory, embedding_function=embedding)
- query = "On what date did the war between Russia and Ukraine take place?"
- retriever = vectordb.as_retriever(search_type="mmr")
- s = retriever.get_relevant_documents(query)
- print(123, s)
- /
- 123 [Document(page_content='加意见和建议...乌克兰的战争”标语。', metadata={'source': './russiaX.pdf'}), Document(page_content='综合路透社、雅..导弹。', metadata={'source': './russiaX.pdf'}), Document(page_content='乌克兰东部问....
- print(556, s[0].page_content) # 选第一条的内容
- # 或者这样查询
- s = vectordb.similarity_search(query)
- # print(s[0].page_content)
- # 特别注意
- 在存入数据后有时不能立即查到新添加的数据,此时,关停后重启加载即可!
- # 直接用get获取数据
- res = vectordb.get(limit=2)
- print(266, res)
- //266 {'ids': ['e8661882-358c-11ee-a7f1-fb75c83274a1', 'e8661883-358c-11ee-a7f1-fb75c83274a1'], 'embeddings': None, 'metadatas': [{'source': './uploads/dazhihui.txt'}, {'source': './uploads/dazhihui.txt'}], 'documents': ['大智汇健康科技...', '二、团队介绍...']}
像Chroma DB这样的向量存储正在成为大型语言模型系统的重要组成部分。通过提供专用的存储和向量嵌入的高效检索,它们能够快速访问相关的语义信息,从而为 LLM 提供支持。
在本色度数据库教程中,我们介绍了创建集合、添加文档、将文本转换为嵌入、查询语义相似性以及管理集合的基础知识。