1. 向量存储(Vector Stores)
1.1 向量数据库介绍
在 LangChain 中,关键步骤需要将我们提取出的嵌入向量存储到向量数据库中。向量存储是比较核心的组件,在整个 RAG 框架中扮演着重要的角色,如下图所示:
实际上,向量数据库就是专门用于存储向量的数据库,它能够高效地存储和检索向量数据。
向量数据库提供了一种高效的方式来存储和检索向量数据。基于向量相似度搜索(Similarity Search),而不是基于精确匹配或传统的关键词搜索,能够更好地捕捉语义的相似性。
1.2 向量数据库的工作原理
根据上图,我们可以理解向量数据库的工作流程:
- 文档嵌入:将文档转换成向量
- 索引构建:将向量存储到向量数据库中,建立索引
- 查询嵌入:将用户查询转换成向量
- 相似度搜索:在向量数据库中搜索与查询向量最相似的文档向量
- 返回结果:返回最相似的文档
1.3 向量数据库的核心功能
向量数据库提供了两个主要功能:数据存储 和 相似度搜索。
① 数据存储
这部分涉及到如何将向量数据高效地存储到数据库中,而且还需要考虑到数据的持久化、索引的构建等问题。
常见的存储方式:
- 内存存储:数据存储在内存中,速度快但不持久化
- 磁盘存储:数据存储在磁盘上,持久化但速度相对较慢
索引方法:
这部分涉及到如何构建索引以加速相似度搜索。可以下面这些方法来加速搜索,而且还需要考虑到索引的构建时间、内存占用等问题。对于,正如前面所说的,我们需要在速度和精度之间做出权衡。
常见的方法有近似最近邻搜索(ANN)算法:如 FAISS 向量数据库,它能提供高效的向量搜索能力,并且支持多种索引方法。
常见的方法有近似最近邻(ANN)搜索:如 FAISS 向量数据库,它能提供高效的向量搜索能力,并且支持多种索引方法。它不会遍历所有向量,而是通过索引结构(如树结构、图结构),快速定位到可能相似的向量区域,然后在这些区域中进行精确搜索。这样可以大大减少搜索时间,但可能会牺牲一定的精度。
这就像在一个大图书馆里找书,你不会从第一本书开始一本一本地翻,而是先根据分类(文学、科学、历史等)找到大致的区域,然后在这个区域里找到具体的书。这样可以大大减少搜索时间,但可能会错过一些相关的书。
重要提示:
- 基于向量的相似度搜索(如 ANN 搜索)通常比传统的关键词搜索更能捕捉语义相似性
- 但它也可能返回一些看似相关但实际上不太相关的结果
HNSW向量存储原理:
👉 HNSW 的物理结构:多层地图 👈
HNSW 将所有的向量点分成了好几层,每一层都是一张“图”,点与点之间有连线。
-
顶层(Layer L): 只有极少数“精英”点。它们距离非常远。就像全国地图,只标出北京、上海、广州。
-
中间层: 点的数量逐渐增多。就像省际地图,标出了所有的地级市。
-
底层(Layer 0): 包含了数据库里所有的向量点。就像街道地图,密密麻麻地标出了每一栋建筑。
② 向量相似度计算优化
向量相似度计算是向量数据库的核心功能之一。如 FAISS 向量数据库,它能提供高效的向量搜索能力,并且支持多种索引方法。
这些都是为了提高 CPU 和 SIMD 指令集的利用率,从而加速向量相似度计算。这些优化技术可以大大提高向量搜索的速度,使得向量数据库能够处理大规模的向量数据。
一个简单的例子,可以把向量看成是一排一排的数字(又称"向量化操作"),而不是逐个计算相似度,这样可以大大提高计算速度。
举个例子:
1. 数据准备
- 目标 (Q):
[0.9, 0.1, 0.0](想找“偏红”的内容) - 候选库:
- 🅰️
[1.0, 0.0, 0.0](纯红) - 🅱️
[0.0, 1.0, 0.0](纯绿) - 🅲
[0.8, 0.2, 0.0](大红微绿) - 🅳
[0.0, 0.0, 1.0](纯蓝)
- 🅰️
2. 核心动作 (SIMD 并行计算) CPU 同时计算 Q 与所有候选的匹配度(点积):
- 🅰️:
- 🅱️:
- 🅲:
- 🅳:
3. 最终结果
- 得分榜:
[0.9, 0.1, 0.74, 0.0] - 冠军: 🅰️ (0.9分)
- 结论: 系统瞬间返回 文档 A。
关键点:不是算完 A 再算 B,而是像“多车道高速公路”一样,一次性算出所有结果,这就是 SIMD 加速的精髓。
③ 数据管理功能
现代向量数据库通常还会提供一些数据管理功能,以便更好地管理向量数据:
- CRUD 操作:支持增删改查,可以动态更新向量数据库
- 元数据过滤:除了向量本身,还可以存储元数据(Metadata),比如时间戳、作者、分类等。通过元数据过滤,可以在搜索时进一步筛选结果(例如"找出2023年发布的、关于机器学习的文档"),再结合向量相似度搜索,可以得到更精确的结果
- 可扩展性和高可用性:支持分布式部署和水平扩展,适应海量数据场景。同时提供数据备份、容错机制,不会轻易丢失数据
- 集成方便:很多向量数据库(如 Chroma、Weaviate、Pinecone、Qdrant、Milvus等)都提供了 Python SDK 和 RESTful API,可以方便地与 LangChain 等框架集成
理想上,我们可以利用向量数据库的这些功能,构建一个高效、可扩展的 RAG 系统。从而提供更好的搜索体验,让用户能够快速找到相关的文档。
下面我们介绍如何使用 LangChain 提供的向量数据库,包括如何创建向量数据库、如何进行相似度搜索等。更多 LangChain 支持的向量数据库可以参考官方文档,除了常见的上面提到的向量数据库,其实还有很多其他的向量数据库可以选择。
2. 内存存储
我们将使用 LangChain 的 InMemoryVectorStore 类实现向量的内存存储。
2.1 初始化
from langchain_core.vectorstores import InMemoryVectorStore
# 初始化内存向量存储
vector_store = InMemoryVectorStore(embeddings)
2.2 向量搜索
如果我们传入一个查询,向量存储将嵌入该查询,在所有嵌入的文档中执行相似性搜索,并返回最相似的文档。
这体现了两个重要的概念:
- 首先,需要有一种方法来衡量查询与任何嵌入文档之间的相似性
- 其次,需要有一种算法能够高效地在所有嵌入文档中执行这种相似性搜索
对于【相似性】这一点,之前已经讲过,再来回顾下:
- 欧氏距离(Euclidean Distance):就是我们高中几何学的两点之间的直线距离。距离越短,相似度越高
- 余弦相似度(Cosine Similarity):它忽略向量的绝对长度(大小),只关注两个向量在方向上的差异。在文本和语义的世界里,"方向"代表"含义",而"长度"往往只代表"文本的长度"或"词汇的多少"。换句话说,余弦相似度关注的是"你们是否指向同一个方向"/"你们是否代表同一个含义"
因此,在捕捉语义上的相似性上,余弦相似度是更常用的度量方式。
对于【相似性搜索】则可以通过向量存储提供的搜索方法实现。
2.2.1 相似性搜索
想要获取根据相似性搜索的结果,即嵌入单个查询,并查找相似的文档,并将它们作为文档列表返回。这可以使用 similarity_search 方法来实现。说明:InMemoryVectorStore 是根据【余弦相似度】来进行相似性搜索的。
2.3 完整代码示例
# 嵌入模型生成向量(通过md文件按照token进行切分得到chunk也就是document,在进行转化成向量)
# 导入谷歌的嵌入模型
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_community.vectorstores import Chroma
import tiktoken
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.document_loaders.markdown import UnstructuredMarkdownLoader
# 导入内存向量存储
from langchain_core.vectorstores import InMemoryVectorStore
# 加载文档(让 Markdown 按结构解析(标题、段落、列表分开))
loader = UnstructuredMarkdownLoader("test.md", mode="elements")
documents = loader.load()
# 设置基于token的文本分割器
splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base",
chunk_size=120,
chunk_overlap=50, # 文档重叠长度
)
# 原始文本(一个document的page_content) → 编码成 token 序列 → 滑动窗口切分 → 解码成 chunk
# [0,1,...,119][70,...,189][140,...,299] → "chunk1" "chunk2" "chunk3"
# 分割文档
chunks = splitter.split_documents(documents)
# 初始化嵌入模型
embeddings = GoogleGenerativeAIEmbeddings(model="gemini-embedding-001")
# 创建内存向量存储
vectorstore = InMemoryVectorStore(embeddings)
ids = vectorstore.add_documents(chunks)
# 打印对应id的前三个
print(ids[:3])
print("----------------------------------------")
# 根据id查询文档内容
docs = vectorstore.get_by_ids(ids[:3])
print(docs)
print("----------------------------------------")
# 删除id
vectorstore.delete(ids[:3])
print(vectorstore.get_by_ids(ids[:3]))
print("----------------------------------------")
# 相似性搜索
docs = vectorstore.similarity_search("什么是 Claude Skills?")
print(docs)
print("----------------------------------------")
# 写一个filter函数,关于元数据的source字段是test.md的文档
def filter_by_source(documents):
return documents.metadata["source"] == "test.md"
# 元数据过滤
docs = vectorstore.similarity_search(query="什么是 Claude Skills?", k=2, filter=filter_by_source)
print(docs)
print("----------------------------------------")
2.4 代码详解
步骤1:加载和分割文档
# 加载 Markdown 文档
loader = UnstructuredMarkdownLoader("test.md", mode="elements")
documents = loader.load()
# 使用 Token 分割器切分文档
splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base",
chunk_size=120,
chunk_overlap=50,
)
chunks = splitter.split_documents(documents)
说明:
mode="elements":按元素(标题、段落、列表)分开加载chunk_size=120:每块最多 120 个 tokenchunk_overlap=50:相邻块重叠 50 个 token
步骤2:初始化嵌入模型和向量存储
# 初始化 Google Gemini 嵌入模型
embeddings = GoogleGenerativeAIEmbeddings(model="gemini-embedding-001")
# 创建内存向量存储
vectorstore = InMemoryVectorStore(embeddings)
说明:
InMemoryVectorStore:内存向量存储,数据存储在内存中- 程序结束后数据会丢失,适合测试场景
步骤3:添加文档并获取ID
# 添加文档到向量存储,返回文档ID列表
ids = vectorstore.add_documents(chunks)
# 打印前三个文档的ID
print(ids[:3])
输出示例:
['doc_001', 'doc_002', 'doc_003']
说明:
add_documents()会自动调用embeddings.embed_documents()将文档转换为向量- 返回每个文档的唯一ID,用于后续的查询和删除操作
步骤4:根据ID查询文档
# 根据ID查询文档内容
docs = vectorstore.get_by_ids(ids[:3])
print(docs)
输出示例:
[Document(page_content='Claude Skills 是...', metadata={'source': 'test.md'}), Document(page_content='Skills 可以...', metadata={'source': 'test.md'}), Document(page_content='使用 Skills...', metadata={'source': 'test.md'})]
说明:
get_by_ids():根据文档ID列表查询文档内容- 返回
List[Document]对象列表
步骤5:删除文档
# 删除指定ID的文档
vectorstore.delete(ids[:3])
# 验证删除结果(应该返回空列表)
print(vectorstore.get_by_ids(ids[:3]))
输出示例:
[]
说明:
delete():根据ID删除文档- 删除后再查询会返回空列表
步骤6:相似性搜索
# 进行相似度搜索
docs = vectorstore.similarity_search("什么是 Claude Skills?")
print(docs)
工作流程:
- 自动调用
embeddings.embed_query()将查询转换为向量 - 在向量数据库中搜索与查询向量最相似的文档向量
- 默认返回最相似的 4 个文档
输出示例:
[Document(page_content='Claude Skills 是一种扩展 Claude 能力的方式...'), Document(page_content='Skills 可以让 Claude 执行特定的任务...'), Document(page_content='使用 Skills 可以提高工作效率...'), Document(page_content='Skills 支持多种编程语言...')]
步骤7:元数据过滤搜索
# 定义过滤函数
def filter_by_source(documents):
return documents.metadata["source"] == "test.md"
# 带过滤条件的相似度搜索
docs = vectorstore.similarity_search(
query="什么是 Claude Skills?",
k=2,
filter=filter_by_source
)
print(docs)
参数说明:
query:查询字符串k=2:返回 2 个最相似的文档filter:过滤函数,只返回满足条件的文档
输出示例:
[Document(page_content='Claude Skills 是...', metadata={'source': 'test.md'}), Document(page_content='Skills 可以...', metadata={'source': 'test.md'})]
说明:
- 元数据过滤可以在相似度搜索的基础上进一步筛选结果
- 例如:只搜索特定来源、特定时间、特定作者的文档
2.5 向量存储的核心操作总结
| 操作 | 方法 | 说明 |
|---|---|---|
| 添加文档 | add_documents(chunks) | 将文档转换为向量并存储,返回ID列表 |
| 查询文档 | get_by_ids(ids) | 根据ID查询文档内容 |
| 删除文档 | delete(ids) | 根据ID删除文档 |
| 相似度搜索 | similarity_search(query, k) | 搜索最相似的k个文档 |
| 过滤搜索 | similarity_search(query, k, filter) | 带元数据过滤的相似度搜索 |
3. 持久化存储(Chroma)
3.1 为什么需要持久化存储?
InMemoryVectorStore 的缺点:
- 数据存储在内存中,程序结束后数据会丢失
- 不适合大规模数据
- 每次运行都需要重新加载和嵌入文档
Chroma 是一个开源的向量数据库,支持持久化存储:
- 数据存储在磁盘上,程序结束后数据不会丢失
- 支持大规模数据
- 只需要加载一次,后续可以直接使用
3.2 使用 Chroma
from langchain_community.vectorstores import Chroma
# 初始化 Chroma 向量存储(持久化到磁盘)
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化目录
)
# 进行相似度搜索
results = vector_store.similarity_search(query, k=3)
参数说明:
documents:要存储的文档列表embedding:嵌入模型persist_directory:持久化目录路径
3.3 加载已有的 Chroma 数据库
# 加载已有的 Chroma 数据库
vector_store = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
# 直接进行搜索,无需重新加载文档
results = vector_store.similarity_search(query, k=3)
4. 相似度搜索方法对比
4.1 similarity_search()
基本的相似度搜索,返回最相似的文档。
results = vector_store.similarity_search(query, k=3)
返回:List[Document]
4.2 similarity_search_with_score()
带分数的相似度搜索,返回文档和相似度分数。
results = vector_store.similarity_search_with_score(query, k=3)
for doc, score in results:
print(f"相似度分数: {score}")
print(f"文档内容: {doc.page_content}")
返回:List[Tuple[Document, float]]
相似度分数说明:
- 分数越低,相似度越高(距离越近)
- 分数范围取决于使用的相似度度量方法
4.3 max_marginal_relevance_search()
最大边际相关性搜索,在相似度和多样性之间取得平衡。
results = vector_store.max_marginal_relevance_search(query, k=3)
特点:
- 返回的文档既相似又多样化
- 避免返回过于相似的重复文档
5. 总结
5.1 核心要点
- 向量存储是 RAG 系统的核心组件,用于存储和检索向量数据
- InMemoryVectorStore 适合小规模数据和测试
- Chroma 支持持久化存储,适合生产环境
- 相似度搜索基于余弦相似度,能够找到语义相关的文档
- add_documents() 自动调用 embed_documents() 进行向量化
- similarity_search() 自动调用 embed_query() 进行查询向量化
5.2 完整工作流程
文档加载 → 文本分割 → 向量嵌入 → 向量存储 → 相似度搜索 → 返回结果
↓ ↓ ↓ ↓ ↓ ↓
Loader Splitter Embeddings VectorStore Query Documents
6. 常用向量数据库对比
6.1 InMemoryVectorStore(内存向量存储)
特点:
- 数据存储在内存中
- 速度最快
- 不持久化(程序关闭后数据丢失)
- 适合小规模数据和测试
使用场景:
- 快速原型开发
- 小规模数据集
- 临时测试
6.2 Chroma
特点:
- 开源向量数据库
- 支持本地持久化
- 轻量级,易于使用
- 支持元数据过滤
使用场景:
- 中小规模应用
- 本地开发和测试
- 需要持久化存储
代码示例:
from langchain_community.vectorstores import Chroma
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化目录
)
6.3 Pinecone
特点:
- 云端托管向量数据库
- 高性能、可扩展
- 支持大规模数据
- 需要 API Key
使用场景:
- 生产环境
- 大规模数据
- 需要高可用性
6.4 FAISS
特点:
- Facebook 开源
- 高性能相似度搜索
- 支持多种索引方法
- 适合大规模数据
使用场景:
- 大规模向量搜索
- 需要高性能
- 本地部署
6.5 Redis(RedisSearch 向量数据库)
特点:
- 基于 Redis 生态,利用 RedisSearch 模块实现向量存储与搜索
- 支持内存存储,性能极高
- 支持持久化(RDB/AOF)
- 原生支持向量索引(HNSW 算法)
- 支持元数据过滤与混合查询(向量+标量)
- 兼容 Redis 生态,易于集成
使用场景:
- 需要极高性能的实时向量检索
- 已有 Redis 基础设施的项目
- 需要结合缓存与向量搜索的场景
- 中小到大规模数据(取决于 Redis 内存容量)
代码示例:
from langchain_community.vectorstores import Redis
from langchain_community.embeddings import GoogleGenerativeAIEmbeddings
# 初始化嵌入模型
embeddings = GoogleGenerativeAIEmbeddings(model="gemini-embedding-001")
# 创建 Redis 向量存储
vector_store = Redis.from_documents(
documents=chunks,
embedding=embeddings,
redis_url="redis://localhost:6379",
index_name="my_vector_index", # RedisSearch 索引名
)
6.6 对比表格
| 特性 | InMemoryVectorStore | Chroma | Pinecone | FAISS | Redis |
|---|---|---|---|---|---|
| 持久化 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 云端托管 | ❌ | ❌ | ✅ | ❌ | ❌(可自托管) |
| 性能 | 快 | 中 | 快 | 非常快 | 非常快 |
| 易用性 | 简单 | 简单 | 中等 | 复杂 | 中等 |
| 成本 | 免费 | 免费 | 付费 | 免费 | 免费(自托管) |
| 适用规模 | 小 | 中 | 大 | 大 | 中~大 |
| 混合查询支持 | ❌ | ✅ | ✅ | ❌ | ✅ |
| 生态集成 | 无 | 独立 | 云服务 | 独立 | Redis 生态 |
6.7 选型总结
在选择向量存储方案时,需综合考虑数据规模、性能需求、持久化要求、成本预算及开发复杂度:
- 快速验证与小数据:优先选 InMemoryVectorStore(内存快但易失)或 Chroma(轻量且支持本地持久化)。
- 生产级高可用:Pinecone(云端托管,省心但付费)或 Redis(自托管灵活,生态强大)。
- 极致性能与可控性:FAISS(本地高性能搜索,适合技术团队自主优化)。
- 已有 Redis 基础设施:直接使用 RedisSearch 向量模块,低成本集成向量检索与缓存能力。
7. 总结
7.1 核心要点
- 向量存储是 RAG 系统的核心组件
- 相似度搜索基于向量距离计算(余弦相似度、欧氏距离)
- InMemoryVectorStore 适合快速开发和测试
- 生产环境建议使用 Chroma、Pinecone 等持久化数据库
7.2 完整工作流程
文档加载 → 文本分割 → 向量嵌入 → 向量存储 → 相似度搜索 → 返回结果