上文我们介绍了向量的部分概念和如何生成向量,那么对于RAG来说最后的检索是在知识库中进行检索,我们就要利用向量库去存储生成的好的语义向量。

1.前置知识

1.1向量数据库

向量数据库是专门存储和管理向量的一种介质。

其核心任务是解决传统数据库(如MySQL)不擅⻓的问题:基于内容的相似性搜索(Similarity Search),⽽不是基于精确匹配的查询。

1.2核心机制

1.2.1专门的索引--ANN

这是向量数据库的灵魂。预先为所有向量构建⼀种特殊的索引结构。
常见方法ANN(近似最近邻搜索),牺牲些许精度,以相似向量,通过聚类、分层、压缩等算法找几个候选集范围进行查询。

1.2.2向量相似度计算优化

充分利⽤了 CPU 的 SIMD 指令集和 GPU 的并⾏计算 能⼒,让⼤规模的向量计算速度极快。
能够快速处理⼤规模数据,并且⽀持在⾼维空间中进⾏相似性搜索
SIMD 指令集 单指令多数据流 技术,是⼀种采⽤⼀个控制器来控制多个处理器,本质上⾮常类似⼀个向量处理器,可对控制器上的⼀组数据(⼜称“数据向量”) 同时分别执⾏相同的操作从⽽实现空间上的并⾏。简单来说,就是⼀个指令能够同时处理多个数据。

1.2.3数据管理

(1)CRUD 操作:⽀持增删改查,可以动态地更新向量数据。
(2)元数据过滤:即条件筛选,利用数据的原生字段进行初步的检索过滤。
(3)可扩展性与持久化:它们可以轻松地分布式部署,处理海量数据;同时保证数据持久化,不像纯内存⽅案⼀样断电丢失。
(4)集成⽅便:提供友好的API(如gRPC, RESTful),使得像 LangChain 这样的框架可以轻松地与之集成,开发者⽆需关⼼底层细节。

2.介绍三种向量存储

2.1内存存储

我们将使⽤ LangChain 的InMemoryVectorStore来实现向量的内存存储

 (1)初始化

LangChain 中的⼤多数向量在初始化向量存储时接受嵌⼊模型作为参数
from langchain_openai import OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
# 定义嵌⼊模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# 内存存储初始化
vector_store = InMemoryVectorStore(embedding=embeddings)

(2)添加文档

我们可以使⽤ add_documents ⽅法,向内存存储中去添加⽂档。要注意的是,该⽅法会为添加的⽂档编排索引,索引列表随着该⽅法返回。
这也就是在前⽂中,我们⼀直在提的:当我们想对某⽂本进⾏【数据检索】时的两个步骤:
编制索引: ⽤于从源中摄取数据并为其编制索引。
检索和⽣成 :接受⽤⼾查询并从索引中检索相关数据,然后将其传递给模型
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
# ⽣成分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base", chunk_size=200, chunk_overlap=50
)
# 加载⽂档
data = UnstructuredMarkdownLoader("../Docs/Markdown/xxx.md").load()
# 分割⽂档
documents = text_splitter.split_documents(data)
# 添加⽂档
ids = vector_store.add_documents(documents=documents)
print(f"共编排了{len(ids)}个⽂档索引")
print(f"前3个⽂档的索引是:{ids[:3]}")

(3)获取文档

doc_3 = vector_store.get_by_ids(ids[:3])
print(f"{[doc.page_content for doc in doc_3]}")

(4)删除文档

doc_3 = vector_store.get_by_ids(ids[:3])
print(f"{[doc.page_content for doc in doc_3]}")

(5)向量检索

相似性
search_docs = vector_store.similarity_search(
    query="数据库表怎么设计的?", 
    k=2
)
for doc in search_docs:
    print("*" * 30)
    print(doc.page_content)
元数据过滤
我们给搜索⽅法加⼊了 filter 参数,它接收⼀个 bool 值,表⽰我们可以根据条件选择是否
过滤某些⽂档。因此我们定义了⼀个 _filter_function 过滤函数,可以根据⽂档元数据先过滤出⽂档,再去进⾏搜索。
from langchain_core.documents import Document
def _filter_function(doc: Document) -> bool:
    return doc.metadata.get("source") == "hahaha"
search_docs = vector_store.similarity_search(
    query="数据库表怎么设计的?",
    k=2,
    filter=_filter_function
)
for doc in search_docs:
    print("*" * 30)
    print(doc.page_content)

2.2Redis向量存储

2.2.1RedisSearch

Redis被用于存储向量,因为其速度快、拥有庞⼤的客⼾端库 ⽣态系统,并且多年来已被众多⼤型企业采⽤。从本质上讲,Redis 是⼀种键值型的 NoSQL 数据库, 除了传统⽤例之外,Redis 还提供了诸如搜索和查询功能等额外能⼒,允许⽤⼾在 Redis 内创建⼆级索引结构。这使得 Redis 能够以缓存的速度充当向量数据库

为使其具有高性能的搜索和全文索引的功能,官方开发了RediSearch引擎模块。

该引擎模块将存储分成三层架构,利用索引和分类达到高效的存储和检索

Index

Index是一个总的用于查询的目录结构,是引擎模块的一个概念。

Index 是⼀个独⽴的数据结构,它建⽴在多个 Redis Keys (Hash 类型)之上,这专⻔为了极速执⾏⽂本搜索、过滤和聚合⽽设计。它 本⾝不存储数据,⽽是存储了指向其他 Redis Keys 的指针,和这些 Keys 中特定字段的索引信 息。类似Mysql中的B+树索引。
Index Fields
Index Fields(索引字段) 是创建索引时,明确指定的那些需要被索引的字段。它们定义了索引的“结构”或“蓝图”,告诉 RediSearch:“请针对这些字段的内容,以其特定的⽅式为我构建快速搜索的能⼒。”
类似与利用片段中的关键字段建立索引并生成索引ID进行该片段的存储。
Metadata schema
metadata schema 则⽤来描述元数据的结构声明。这⾥的元数据是指我们将来要嵌⼊⽂档的元数据。因为对于⽂档元数据来说,它在存⼊ Redis 后,就被定义成了索引字段。
对于⽂档元数据来说,⾥⾯存放的就是⼀些⽂档属性值,如 source 表⽰⽂档来源。我们还可以⼿动加⼊其他元数据,这需要设置每个字段的声明: name 表⽰字段名, type 表⽰字段类型。
其作用就是便于条件筛选检索

2.2.2环境配置

# Redis
from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisConfig, RedisVectorStore
# 定义嵌⼊模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# 配置 Redis 客⼾端
redis_url = "redis://xxx.xxx.xxx:xxx"
config = RedisConfig(
    index_name="qa",
    redis_url=redis_url,
    metadata_schema=[
        {"name": "category", "type": "tag"},
        {"name": "num", "type": "numeric"},
    ],
)
# Redis 存储初始化
vector_store = RedisVectorStore(embeddings, config=config)
from langchain_text_splitters import CharacterTextSplitter
# ⽣成分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base", chunk_size=200, chunk_overlap=50
)
# 加载⽂档
data = UnstructuredMarkdownLoader("../Docs/Markdown/xxx.md", category="QA").load()
# 分割⽂档
documents = text_splitter.split_documents(data)
# 为⽂档添加元数据
for i, doc in enumerate(documents, start=1): #枚举
    doc.metadata["category"] = "QA"
    doc.metadata["num"] = i
ids = vector_store.add_documents(documents=documents)
print(f"共编排了{len(ids)}个⽂档索引")
print(f"前3个⽂档的索引是:{ids[:3]}")
ids = [":01K4Q0A3DSQVZBRFKJD5MS25HJ", ":01K4Q0A3DSQVZBRFKJD5MS25HK",":01K4Q0A3DSQVZBRFKJD5MS25HM"]
# 获取索引
doc_3 = vector_store.get_by_ids(ids)
print(f"{[doc.page_content for doc in doc_3]}")
# 删除
# 删除指定内容
vector_store.index.drop_keys(["qa::01K4Q0A3DSQVZBRFKJD5MS25HJ"])
# 全量删除,删除索引
vector_store.index.delete(drop=True)
# 检索
category_is_qa = Tag("category") == "qa"
num_is_under_50 = Num("num") < 50
filter_condition = category_is_qa & num_is_under_50
search_docs = vector_store.similarity_search(
    query="数据库表怎么设计的?",  #相似性检索
    k=2,                        #返回检索结构数量
    fetch_k = 10,               #MMR初步候选集数量
    filter = filter_condition,  #元数据条件筛选
)
for doc in search_docs:
    print("*" * 30)
    print(doc.page_content)

2.3Pinecone向量存储

要使用这个亚马逊这个云存储我们需要去其官网注册信息获得密钥,将密钥写进环境变量中进行使用。

from langchain_openai import OpenAIEmbeddings
from langchain_pinecone import PineconeVectorStore
from pinecone import Pinecone, ServerlessSpec
# 建⽴索引
pc = Pinecone()
index_name = "qa"
if not pc.has_index(index_name):
    pc.create_index(
        name=index_name, # 索引名称
        dimension=3072, # 尺⼨,表⽰向量维度,需要和嵌⼊模型维度⼀致
        metric="cosine", # 度量⽅式,cosine 表⽰余弦相似度
        spec=ServerlessSpec(
            cloud="aws", # 亚⻢逊云
            region="us-east-1" # 区域
        ),
)
# 定义嵌⼊模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# 获取索引
index = pc.Index(index_name)
# 定义 Pinecone 向量存储
vector_store = PineconeVectorStore(embedding=embeddings, index=index)

# ⽣成分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base", chunk_size=200, chunk_overlap=50
)
# 加载⽂档
data = UnstructuredMarkdownLoader("../Docs/Markdown/xxx.md", category="QA").load()
# 分割⽂档
documents = text_splitter.split_documents(data)
# 为⽂档添加元数据
for i, doc in enumerate(documents, start=1):
    doc.metadata["category"] = "QA"
    doc.metadata["num"] = i
ids = vector_store.add_documents(documents=documents)
print(f"共编排了{len(ids)}个⽂档索引")
print(f"前3个⽂档的索引是:{ids[:3]}")

# 全量删除
vector_store.delete(delete_all=True)
# 删除指定id的⽂档列表
delete_ids = []
vector_store.delete(ids=delete_ids)

search_docs = vector_store.similarity_search(
    query="数据库表怎么设计的?",
    k=2,
    filter={"category": "QA"},
)
for doc in search_docs:
    print("*" * 30)
    print(f"Content: {doc.page_content[:100]}...")
    print(f"Metadata: {doc.metadata}")

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐