目录
[1.内存向量存储 ------ InMemoryVectorStore](#1.内存向量存储 —— InMemoryVectorStore)
[1.2. 核心接口](#1.2. 核心接口)
[2.Redis 向量存储 ------ RedisVectorStore](#2.Redis 向量存储 —— RedisVectorStore)
[2.2. 环境配置](#2.2. 环境配置)
[2.3. 核心接口操作](#2.3. 核心接口操作)
[filter 参数](#filter 参数)
[3. Pinecone 向量存储](#3. Pinecone 向量存储)
[3.3. 核心接口操作](#3.3. 核心接口操作)
[filter 参数](#filter 参数)
[4. 对比](#4. 对比)
[5. 一些问题](#5. 一些问题)
| 存储类型 | 适用场景 | 核心特点 |
|---|---|---|
| InMemoryVectorStore | 本地开发、单元测试、小型原型 | 轻量、无依赖、基于余弦相似度暴力搜索 |
| RedisVectorStore | 生产级缓存、中等规模检索 | 基于 RediSearch 模块,支持高速索引与过滤 |
| PineconeVectorStore | 全托管生产环境、海量向量 | 云原生 、自动扩展、强一致性、丰富过滤 |
1.内存向量存储 ------ InMemoryVectorStore
1.1. 原理速览
InMemoryVectorStore 是 LangChain core 包内置的简易实现。它将所有文档向量化后使用一个 Python 字典(dict)来存储文档及其向量,检索时对全部向量进行暴力相似度计算(默认余弦相似度),没有向量索引。 因此:
优点:零配置,极速启动,适合测试
缺点:O(N) 复杂度,数据量大时性能急剧下降,且重启丢失
1.2. 核心接口
| 操作 | 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|---|
| 初始化 | InMemoryVectorStore(embedding) |
embedding: 嵌入模型实例 |
实例对象 | 必须传入 Embeddings 对象 |
| 添加文档 | add_documents(documents) |
documents: List[Document] |
List[str]:生成的 ID 列表 |
自动生成 UUID 作为主键索引(哈希表) |
| 获取文档 | get_by_ids(ids) |
ids: List[str] |
List[Document] |
根据 ID 列表批量获取 |
| 删除文档 | delete(ids=None) |
ids: List[str],可选 |
None |
不传则全量删除 |
| 相似性搜索 | similarity_search(query, k, filter) |
query: 字符串,k: 数量,filter: 过滤函数 |
List[Document] |
余弦相似度,支持自定义过滤函数 |
| 带分数搜索 | 不支持 | - | - | 内存版不返回分数 |
# 1. 导入必要模块
from langchain_openai import OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.documents import Document
# 2. 初始化嵌入模型 & 向量存储
# 注意:需设置环境变量 OPENAI_API_KEY,或直接传入 api_key
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vector_store = InMemoryVectorStore(embedding=embeddings)
# 3. 准备示例文档
docs = [
Document(
page_content="MySQL 是关系型数据库,支持事务和 ACID。",
metadata={"source": "db", "topic": "mysql"}
),
Document(
page_content="Redis 是基于内存的键值存储,常用于缓存。",
metadata={"source": "cache", "topic": "redis"}
),
Document(
page_content="Pinecone 是云原生的向量数据库,专为 AI 应用设计。",
metadata={"source": "vector", "topic": "pinecone"}
),
]
# 4. 添加文档(索引)
ids = vector_store.add_documents(docs)
print("添加后生成的 ID:", ids)
# 输出示例: ['uuid1', 'uuid2', 'uuid3']
# 5. 根据 ID 获取文档
retrieved_docs = vector_store.get_by_ids(ids[:2]) # 取前两个
print("\n获取的文档内容:")
for doc in retrieved_docs:
print(f" - {doc.page_content[:50]}...")
# 6. 相似性搜索(无过滤)
query = "哪种数据库适合缓存?"
results = vector_store.similarity_search(query, k=2)
print("\n相似性搜索(k=2)结果:")
for doc in results:
print(f" - {doc.page_content}")
# 7. 相似性搜索 + 元数据过滤
def filter_by_source(doc: Document) -> bool:
# 只保留 source 为 "db" 的文档
return doc.metadata.get("source") == "db"
filtered_results = vector_store.similarity_search(
query="支持事务的数据库",
k=2,
filter=filter_by_source
)
print("\n带过滤的搜索(仅 source=db):")
for doc in filtered_results:
print(f" - {doc.page_content}")
# 8. 删除指定文档
vector_store.delete(ids=[ids[0]]) # 删除第一个
print(f"\n删除 ID {ids[0]} 后,剩余文档数:{len(vector_store.get_by_ids(ids))}")
# 注意:get_by_ids 传入已被删除的 ID 会返回空列表
filter 参数(元数据过滤)
InMemoryVectorStore 的 similarity_search 支持 filter 参数,
接收一个可调用对象,返回 bool 决定是否保留文档
def filter_by_source(doc: Document) -> bool:
return doc.metadata.get("source") == "expected_path"
results = vector_store.similarity_search(
query="数据库设计",
k=3,
filter=filter_by_source
)
为什么内存版不用表达式而用函数?
- InMemoryVectorStore 本质是一个运行在 Python 进程内的调试工具类 ,执行 similarity_search 时就是直接对内存列表做 for 循环迭代 。采用函数式过滤器,可以直接在 Python 对象层面执行任意复杂逻辑(如组合条件、正则匹配、闭包状态),无需额外开发一套查询语法解析器(Parser)和抽象语法树(AST)执行引擎,避免了不必要的依赖膨胀和性能开销。即,用 Python 原生能力解决 Python 原生问题
- 这与 Milvus、Pinecone 等生产级 Client-Server 架构的向量库形成鲜明对比------后者因查询需跨网络序列化传输,且存储引擎多为 C++/Go 编写,无法直接操作 Python 对象,必须将表达式(如 {"source": "path"})序列化后由服务端解析为可下推的物理执行计划
| 对比维度 | 内存版过滤(Function) | 生产库过滤(Expression) |
|---|---|---|
| 写法 | filter=lambda doc: doc.metadata["source"] == "path" |
filter={"source": "path"} |
| 本质 | 可执行代码(Code) | 静态数据(Data) |
| 作用 | 告诉 Python 解释器 "如何执行" 迭代判断 | 告诉数据库 "要什么条件" |
| 执行者 | 当前 Python 进程(直接 eval/call) |
远端数据库引擎(需解析 JSON 为 AST 再下推) |
| 能否跨网络 | 不能(无法序列化函数字节码) | 能(JSON 是通用网络传输格式) |
2.Redis 向量存储 ------ RedisVectorStore
2.1. 原理
Redis 本身是键值数据库,但通过RediSearch 模块,它扩展出了二级索引能力,支持向量相似性搜索。其核心概念:
- Index:一个独立的查询目录,定义在多个 Redis Hash Key 之上,不存数据,只存指针和索引信息
- Index Fields:索引字段,包括 TAG(精确匹配)、NUMERIC(范围)、TEXT(全文)、GEO(地理)以及向量字段(VECTOR)
- Metadata Schema:描述元数据的结构声明,每个元数据字段对应一个 Index Field


- **index_name 关联 Index:**你创建的 index_name(如 "qa")就是 RediSearch 中那个独立的 Index(查询目录) 的名字。Redis 服务端通过这个名字来存储索引的倒排列表和向量 HNSW 图结构
- metadata_schema + distance_metric 关联 Index Fields:
- metadata_schema 定义了元数据字段(如 category 是 TAG,num 是 NUMERIC),这些直接转化为 Index Fields 中的非向量字段。
- distance_metric(如 COSINE)决定了 VECTOR 字段的相似度算法,这也是 Index Fields 中向量字段的核心属性。
- prefix 是连接"物理 Key"与"逻辑 Index"的纽带: Index(逻辑目录)本身不存储数据,它只存储指向 Redis Keys 的指针
- 当你执行 add_documents 时,数据被写入以 prefix 开头的 Redis Hash Key(物理层)。
- 当你执行 similarity_search 时,RediSearch 会扫描所有匹配 prefix 规则的 Keys,利用 Index Fields 定义的算法(如余弦相似度)进行检索。
- 如果没有 prefix,RediSearch 默认会扫描整个 Redis 数据库的所有 Key,性能极低且极易误读到无关数据。
**存储模型:**每个文档被序列化为一个 Redis Hash,包含 text(内容)、embedding(二进制向量)以及自定义元数据字段。检索时,RediSearch 会基于 embedding 字段执行近似最近邻(ANN)搜索,同时利用其他字段做前置或后置过滤
- Q:如果同一个 Redis 实例有多个向量库(如 QA 库和商品库),如何隔离?
- A(面试标准答案):通过 不同的 index_name + 不同的 prefix 实现双重隔离
- 逻辑隔离:index_name 分别为 qa_index 和 product_index,避免索引定义混淆
- 物理隔离:prefix 分别为 qa: 和 prod:,确保检索时只扫描对应业务的数据 Keys,互不干扰
- Q:prefix 可以动态修改吗?修改后旧数据会怎样?
- A:千万不要在生产环境随意修改! 因为 prefix 在创建索引时绑定。如果修改了 prefix,新写入的数据会使用新前缀,但旧前缀的数据依然存在于 Redis 中。RediSearch 只会索引匹配当前 prefix 的 Keys,导致旧数据"游离"在索引之外(既查不到,也删不掉,变成僵尸数据)
正确的做法是删除旧索引,重建并重新导入数据 - Q:prefix 和生成的文档 ID 是什么关系?
- A:当你调用 add_documents 返回 ID 列表(如 'qa::01K4Q0A3DS...')时,这个 ID 就是 {prefix}::{ULID} 的完整拼接。通过这个 ID,你可以直接用 redis_client.get() 获取原始 Hash 数据,因为它就是 Redis 的真实 Key
2.2. 环境配置
# 启动 Redis(>=8.0 自带 RediSearch)
docker run -d -p 6379:6379 redis:latest
# 或旧版使用 redis-stack
docker run -d -p 6379:6379 redis/redis-stack:latest
# 安装 Python 依赖
pip install redis langchain-redis redisvl
# 定义 Redis 连接 URL,客⼾端连接 Redis 时需要使⽤
Redis 连接 URL 的基本结构是:[protocol]://[auth]@[host]:[port]/[database]
# 测试连接(Ping)
import redis
redis_url = "redis://localhost:6379"
# 定义Redis客⼾端
redis_client = redis.from_url(redis_url)
# Ping
print(redis_client.ping())
# 若输出 True ,则表⽰连接测试成功
2.3. 核心接口操作
| 操作 | 方法 | 关键参数 | 返回 | 注意 |
|---|---|---|---|---|
| 初始化 | RedisVectorStore(embeddings, config) |
config 包含 index_name, redis_url, metadata_schema |
实例 | 索引会自动创建(如果不存在) |
| 添加文档 | add_documents(documents) |
同前 | List[str]:Redis Key 列表 |
Key 格式为 {prefix}::{ULID} |
| 获取文档 | get_by_ids(ids) |
ids: 完整的 Redis Key |
List[Document] |
需包含前缀 |
| 删除文档 | z(ids) 或 index.drop_keys(ids) |
同上 | None |
也可 index.delete(drop=True) 删索引 |
| 相似性搜索 | similarity_search(query, k, filter) |
filter: RedisVL 过滤表达式 |
List[Document] |
默认余弦距离 |
| 带分数搜索 | similarity_search_with_score(query, k, filter) |
同上 | List[Tuple[Document, float]] |
分数越低相似度越高 |
| 按向量搜索 | similarity_search_by_vector(embedding, ...) |
直接传入向量 | 同 | 适用于已生成向量的场景 |
| MMR 搜索 | max_marginal_relevance_search(query, k, fetch_k, filter) |
fetch_k 为候选池大小 |
List[Document] |
兼顾相关性与多样性 |
- distance_metric
| 度量参数 | 全称 | 计算公式(本质) | 分数(Score)含义 |
|---|---|---|---|
"cosine"(默认) |
余弦距离 | 1 - 余弦相似度 |
越低越好(范围 0~2)。值越接近 0,向量方向越一致。 |
"l2" |
欧几里得距离 | 直线距离(平方和的平方根) | 越低越好(范围 0~∞)。值越接近 0,向量在多维空间中位置越近。 |
"ip" |
内积距离 | -1 * 点积 |
越低越好(Redis 对原始 IP 取负,以统一"越低越相似")。值越小,点积越大(方向越一致)。 |
filter 参数(元数据过滤)
在 RedisVectorStore 中,filter 参数使用的是 RedisVL 的查询语法
它基于 Redis 的 RediSearch 模块,支持强大的索引字段过滤
所有过滤表达式都必须使用 @ 符号引用 metadata 中的字段名
按字段类型的语法速查
| 字段类型 | Schema 定义 | 过滤表达式写法 | 匹配逻辑 | 真实示例 |
|---|---|---|---|---|
| Tag(标签) | {"type": "tag"} |
@字段名:{值} |
精确匹配(区分大小写) | @category:{database} |
| Text(文本) | {"type": "text"} |
@字段名:(关键词) |
全文分词模糊搜索 | @content:(向量数据库) |
| Numeric(数值) | {"type": "numeric"} |
@字段名:[最小值 最大值] |
闭区间范围匹配 | @price:[10 100] |
| Geo(地理) | {"type": "geo"} |
@字段名:[经度 纬度 半径 单位] |
球面距离查询 | @location:[-122.4 37.7 10 km] |
逻辑组合运算符(多条件拼接)
| 运算符 | 含义(数学逻辑) | 写法格式 | 示例(字符串写法) |
|---|---|---|---|
| 空格 | AND(交集,同时满足) | 条件1 条件2 |
@category:{db} @price:[0 100] |
| **` | ** 或 **OR`** |
OR(并集,满足任一) | `条件1 |
-(负号) |
NOT(排除) | -条件 |
-@status:{archived} |
| ( )(括号) | 分组(改变优先级) | `(条件A | 条件B) 条件C` |
高级匹配与通配符
| 使用场景 | 写法格式 | 说明 | 示例 |
|---|---|---|---|
| Tag 多值匹配(IN) | `@字段:{值1 | 值2 | 值3}` |
| 前缀通配符(Tag/Text) | 在值后加 * |
匹配以某前缀开头的值或词根 | @tag:{data*}(匹配 data1, data2) |
| 排除闭区间端点 | 用 ( ) 代替 [ ] |
表示大于/小于(不包含边界) | @price:[(10 (100](即 10 < price < 100) |
| 无穷数值范围 | 使用 -inf 或 +inf |
匹配大于或小于某值 | @price:[100 +inf](大于等于100) |
字符串 vs Python 对象
| 对比维度 | 手写字符串(不推荐) | Python 对象(强烈推荐) |
|---|---|---|
| Tag 精确匹配 | "@category:{database}" |
Tag("category") == "database" |
| Numeric 大于 | "@price:[50 +inf]" |
Numeric("price") > 50 |
| Numeric 范围 | "@price:[10 100]" |
(Numeric("price") >= 10) & (Numeric("price") <= 100) |
| NOT 排除 | "-@status:{archived}" |
Tag("status") != "archived" |
| AND 组合 | "@cat:{A} @price:[1 10]" |
(Tag("cat") == "A") & (Numeric("price") > 1) |
| OR 组合 | `"@cat:{A} | @cat:{B}"` |
| 核心优势 | 容易因空格、转义、大小写导致查不到数据 | 自动转义特殊字符,类型安全,IDE 有代码提示 |
使用 Python 对象构建 RedisVL 过滤条件:从 redisvl.query.filter 中导入与字段类型对应的 Tag、Numeric、Text 等类,直接用 ==、!=、>、<、>=、<= 这些原生运算符来定义单条件,再通过位运算符 &(AND)、|(OR)和 ~(NOT)将多个条件灵活组合成嵌套逻辑表达式,最后直接把该对象传给 similarity_search 的 filter 参数即可
import os
from langchain_community.vectorstores import RedisVectorStore
from langchain_community.embeddings import OpenAIEmbeddings # 可替换为其他 Embedding
from langchain_core.documents import Document
from redisvl.schema import IndexSchema # 用于定义 metadata_schema
# 配置
# 请替换为您的 Redis 连接信息
REDIS_URL = "redis://localhost:6379"
INDEX_NAME = "my_docs"
PREFIX = "doc" # Key 前缀,自动拼接到每个文档 Key 前
# 初始化 Embeddings(示例用 OpenAI)
embeddings = OpenAIEmbeddings(model='text-embedding-3-large')
# 1. 定义 metadata_schema(可选,但建议显式声明)
# 如果您的文档包含 metadata 字段,建议定义 schema 以支持过滤查询
metadata_schema = [
{"name": "category", "type": "tag"}, # 分类标签
{"name": "source", "type": "text"}, # 来源
{"name": "timestamp", "type": "numeric"}, # 时间戳
]
# 构建配置
config = {
"index_name": INDEX_NAME,
"redis_url": REDIS_URL,
"metadata_schema": metadata_schema,
"prefix": PREFIX, # 可选,默认 "doc"
"distance_metric": "COSINE", # 或 "L2" / "IP"
}
# 或使用 RedisConfig 类
config = RedisConfig(
index_name=INDEX_NAME,
redis_url=REDIS_URL,
metadata_schema=metadata_schema,
key_prefix=PREFIX, # 注意:这里用 key_prefix 而非 prefix
distance_metric="COSINE",
)
# 2. 初始化 VectorStore(索引会自动创建)
vector_store = RedisVectorStore(embeddings=embeddings, config=config)
# 3. 准备文档
documents = [
Document(
page_content="Redis 是一个开源的内存数据库,支持多种数据结构。",
metadata={"category": "database", "source": "redis.io", "timestamp": 1678900000}
),
Document(
page_content="向量相似性搜索(VSS)在推荐系统中广泛应用。",
metadata={"category": "ml", "source": "paper", "timestamp": 1679000000}
),
Document(
page_content="RedisVL 为 Redis 提供了向量搜索的 Python 客户端。",
metadata={"category": "database", "source": "github", "timestamp": 1679100000}
),
]
# 4. 添加文档
keys = vector_store.add_documents(documents)
print(f"添加了 {len(keys)} 个文档,Keys: {keys}")
# 输出类似: ['doc::01H3X...', 'doc::01H3Y...', ...]
# 5. 根据 ID(完整 Key)获取文档
# 注意:get_by_ids 需要完整的 Redis Key(包含前缀和 ULID)
first_key = keys[0]
doc = vector_store.get_by_ids([first_key])
print(f"获取文档: {doc[0].page_content[:30]}...")
# 6. 相似性搜索(返回 Document 列表)
query = "什么是向量搜索?"
results = vector_store.similarity_search(query, k=2)
print("\n相似性搜索(前2个):")
for r in results:
print(f" - {r.page_content[:50]}...")
# 7. 带分数的相似性搜索(分数越低越相似)
results_with_score = vector_store.similarity_search_with_score(query, k=2)
print("\n带分数结果:")
for doc, score in results_with_score:
print(f"分数: {score:.4f} - {doc.page_content[:50]}...")
# 8. 带过滤的搜索(使用 RedisVL 过滤表达式)
# 只搜索 category 为 "database" 的文档
filter_expr = "@category:{database}"
filtered_results = vector_store.similarity_search(
query, k=2, filter=filter_expr
)
print(f"\n过滤后结果数: {len(filtered_results)}")
for r in filtered_results:
print(f" - {r.page_content[:40]}... (category={r.metadata['category']})")
# 9. 直接按向量搜索(适合已生成向量的场景)
# 先为 query 生成向量
query_embedding = embeddings.embed_query(query)
vector_results = vector_store.similarity_search_by_vector(
embedding=query_embedding, k=2
)
print("\n按向量直接搜索:")
for r in vector_results:
print(f" - {r.page_content[:40]}...")
# 10. MMR 搜索(兼顾相关性和多样性)
mmr_results = vector_store.max_marginal_relevance_search(
query, k=2, fetch_k=5, filter=None
)
print("\nMMR 搜索结果:")
for r in mmr_results:
print(f" - {r.page_content[:40]}...")
# 11. 删除文档(按 Key 删除)
# 删除刚才添加的第一个文档
vector_store.delete(ids=[first_key]) # 或 index.drop_keys([first_key])
print(f"\n已删除 Key: {first_key}")
# 验证删除
remaining = vector_store.get_by_ids([first_key])
print(f"剩余文档数: {len(remaining)}") # 应为 0
# 12. (可选)删除整个索引
# vector_store.index.delete(drop=True) # 会删索引及所有数据

使⽤ rvl 命令⾏⼯具来检查索引
3. Pinecone 向量存储
3.1. Pinecone 核心优势
- 全托管:无需关心索引构建、分片、副本、升级等运维
- 高性能:采用专有 ANN 算法,支持毫秒级查询,实时写入
- 强一致性:写入即查询可见(相比某些最终一致性系统)
- 元数据过滤:支持结构化过滤,且与向量搜索并行执行
- 多租户与命名空间:支持 namespace 隔离数据
3.2. 环境设置与初始化
pip install pinecone langchain-pinecone
The vector database to build knowledgeable AI | Pinecone
注册 Pinecone 获取 API Key,并设置环境变量 PINECONE_API_KEY
# ==================== 1. 导入依赖 ====================
# Pinecone: 官方客户端,用于管理索引和向量数据
# ServerlessSpec: 指定索引部署模式为"无服务器"(按量付费,自动扩缩容)
from pinecone import Pinecone, ServerlessSpec
# LangChain 的 Pinecone 适配器,将 Pinecone 接口封装为标准 VectorStore
from langchain_pinecone import PineconeVectorStore
# ==================== 2. 初始化 Pinecone 客户端 ====================
# Pinecone() 会自动从环境变量 PINECONE_API_KEY 读取 API Key
# 也可以显式传入: Pinecone(api_key="your-key")
# 若未设置环境变量,此处会抛出 AuthenticationError
pc = Pinecone()
# 定义索引名称(Pinecone 中索引名需全局唯一,且只能包含小写字母、数字、连字符)
index_name = "qa"
# ==================== 3. 创建索引(幂等性保护) ====================
# has_index() 检查控制台中是否存在同名索引,避免重复创建报错
# 注意:如果索引已存在但维度/metrics 不符,此 if 不会进入,后续连接会因维度不匹配报错
if not pc.has_index(index_name):
pc.create_index(
name=index_name,
# 【致命陷阱】dimension 必须与 embedding 模型输出的向量维度严格一致!
# text-embedding-3-large 默认输出 3072 维。
# 如果你在 OpenAIEmbeddings 中设置了 dimensions=1024,这里必须改成 1024!!!
dimension=3072,
# 距离度量(Pinecone 命名与 Redis 略有不同):
# - "cosine": 余弦距离(推荐,适合文本语义)
# - "euclidean": 欧几里得距离(等价于 Redis 的 "l2")
# - "dotproduct": 点积(要求向量归一化,等价于 Redis 的 "ip")
metric="cosine",
# ServerlessSpec: 使用无服务器模式(适合原型和小规模生产)
# cloud: 选择云厂商("aws", "gcp", "azure")
# region: 选择地域(需与你的数据存储位置靠近,降低延迟)
spec=ServerlessSpec(cloud="aws", region="us-east-1")
# 如果你需要固定吞吐量(如生产大流量),可改用 PodSpec:
# spec=PodSpec(environment="gcp-starter", pod_type="starter")
)
# 注意:create_index 是异步操作,创建完成后约需几秒~几十秒才能完全就绪。
# 若立即执行后续操作,偶尔会报 "Index not ready",建议加 time.sleep(5) 或轮询 describe_index()
# ==================== 4. 获取索引句柄(连接对象) ====================
# Index() 返回一个客户端句柄,用于执行 upsert、query、delete 等原生操作
# 即使索引刚创建还未完全就绪,此处也能正常获取句柄(执行操作时才报错)
index = pc.Index(index_name)
# ==================== 5. 初始化 LangChain VectorStore ====================
vector_store = PineconeVectorStore(
# embedding: 嵌入模型实例(必须与创建索引时的 dimension 匹配)
embedding=OpenAIEmbeddings(model="text-embedding-3-large"),
# index: 传入上一步获取的 Pinecone 索引句柄
# PineconeVectorStore 会利用此句柄,将 LangChain 的 Document 自动
# 切分、编码成向量,并通过 index.upsert() 写入 Pinecone
index=index
# 可选参数:text_key="text"(指定 Pinecone 中存储原文的 metadata 字段名,默认 "text")
# 可选参数:namespace="namespace"(Pinecone 支持命名空间隔离,类似 Redis 的 prefix)
)
3.3. 核心接口操作
| 操作 | 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|---|
| 初始化 | PineconeVectorStore(embedding, index) |
index: 已创建的 pinecone.Index |
实例 | 不自动创建索引 |
| 添加文档 | add_documents(documents, namespace=None) |
namespace: 可选隔离 |
List[str]:生成的 ID |
自动 upsert(覆盖相同 ID) |
| 获取文档 | get_by_ids(ids, namespace=None) |
ids: List[str] |
List[Document] |
查询指定 ID |
| 删除文档 | delete(ids=None, delete_all=False, namespace=None) |
指定 ID 或全部 | None |
小心 delete_all=True |
| 相似性搜索 | similarity_search(query, k, filter, namespace) |
filter: 字典 |
List[Document] |
元数据过滤用字典 |
| 带分数搜索 | similarity_search_with_score(query, k, filter) |
同 | List[Tuple[Document, float]] |
分数为余弦相似度(越高越相似) |
| MMR 搜索 | 不支持原生 | - | - | Pinecone 无内置 MMR,需自行在客户端实现 |
filter 参数(元数据过滤)
Pinecone 的 filter 参数是一个 Python 字典,用于在向量检索时只对满足特定元数据条件的记录进行相似性搜索。
元数据支持的数据类型:字符串、数字、布尔值、字符串列表
| 操作符 | 含义 | 适用类型 | 示例 |
|---|---|---|---|
$eq |
等于 | number, string, boolean | {"category": {"$eq": "database"}} |
$ne |
不等于 | number, string, boolean | {"status": {"$ne": "archived"}} |
$gt |
大于 | number | {"views": {"$gt": 1000}} |
$gte |
大于等于 | number | {"views": {"$gte": 1000}} |
$lt |
小于 | number | {"price": {"$lt": 50}} |
$lte |
小于等于 | number | {"price": {"$lte": 50}} |
$in |
在数组中 | string, number | {"category": {"$in": ["db", "ml"]}} |
$nin |
不在数组中 | string, number | {"category": {"$nin": ["test", "draft"]}} |
from pinecone import Pinecone
pc = Pinecone(api_key="YOUR_API_KEY")
index = pc.Index(host="YOUR_INDEX_HOST")
# 示例1:精确匹配
results = index.query(
vector=[0.1, 0.2, 0.3],
top_k=10,
filter={"category": "database"} # 简写,等价于 $eq
)
# 示例2:范围过滤
results = index.query(
vector=[0.1, 0.2, 0.3],
top_k=10,
filter={"views": {"$gte": 100, "$lte": 500}}
)
# 示例3:数组成员匹配
results = index.query(
vector=[0.1, 0.2, 0.3],
top_k=10,
filter={"tags": {"$in": ["python", "ai"]}}
)
# 示例4:复杂组合
results = index.query(
vector=[0.1, 0.2, 0.3],
top_k=10,
filter={
"$and": [
{"category": {"$in": ["database", "ml"]}},
{"views": {"$gt": 1000}},
{"status": {"$ne": "archived"}}
]
}
)
import os
import time
from pinecone import Pinecone, ServerlessSpec
from langchain_pinecone import PineconeVectorStore
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
# ==================== 1. 环境准备 ====================
# 请确保环境变量中已设置:PINECONE_API_KEY, OPENAI_API_KEY
# 或在代码中直接赋值(生产环境不推荐)
pc = Pinecone() # 自动读取 PINECONE_API_KEY
embeddings = OpenAIEmbeddings(model="text-embedding-3-large") # 默认 3072 维
index_name = "qa-demo"
namespace = "test_ns" # 命名空间用于数据隔离(类似 Redis 的 prefix)
# ==================== 2. 创建索引(Pinecone 不会自动创建,必须手动) ====================
if not pc.has_index(index_name):
print(f"正在创建索引: {index_name} ...")
pc.create_index(
name=index_name,
dimension=3072, # 必须与 embeddings 维度严格一致
metric="cosine", # 余弦距离
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
# 【关键】等待索引就绪(否则后续写入会报错 Index not ready)
while not pc.describe_index(index_name).status["ready"]:
time.sleep(1)
print("索引创建完成并已就绪")
else:
print(f"索引 {index_name} 已存在,直接连接")
# 获取索引句柄
index = pc.Index(index_name)
# ==================== 3. 初始化 VectorStore ====================
vector_store = PineconeVectorStore(
embedding=embeddings,
index=index
# text_key="text" # 可选,指定存储原文的字段名,默认为 "text"
)
# ==================== 4. 准备文档(带元数据) ====================
documents = [
Document(
page_content="Pinecone 是一个专为 AI 应用设计的向量数据库。",
metadata={"category": "database", "source": "pinecone.io", "views": 1200}
),
Document(
page_content="向量相似性搜索常用于推荐系统和 RAG 应用。",
metadata={"category": "ml", "source": "paper", "views": 800}
),
Document(
page_content="LangChain 简化了与向量存储的集成流程。",
metadata={"category": "framework", "source": "github", "views": 1500}
),
]
# ==================== 5. 添加文档(返回生成的 ID 列表) ====================
# 注意:add_documents 内部使用 upsert,相同 ID 会自动覆盖
ids = vector_store.add_documents(documents, namespace=namespace)
print(f"成功添加 {len(ids)} 条文档,IDs: {ids}")
# 输出示例: ['doc_001', 'doc_002', 'doc_003'] (LangChain 默认生成 UUID)
# ==================== 6. 根据 ID 获取文档 ====================
# 注意:传入的 ID 必须是上述 add_documents 返回的完整 ID
fetched_docs = vector_store.get_by_ids(ids=[ids[0]], namespace=namespace)
print(f"\n根据 ID 获取文档: {fetched_docs[0].page_content[:30]}...")
# ==================== 7. 相似性搜索(返回 Document 列表) ====================
query = "什么是向量数据库?"
results = vector_store.similarity_search(
query,
k=2,
namespace=namespace,
# filter 使用字典(Pinecone 原生语法,见下文详解)
filter={"category": {"$eq": "database"}} # 只查 category 为 database 的
)
print("\n相似性搜索结果(过滤后):")
for doc in results:
print(f" - {doc.page_content[:40]}... (类别: {doc.metadata['category']})")
# ==================== 8. 带分数的搜索(分数含义与 Redis 完全不同!) ====================
results_with_score = vector_store.similarity_search_with_score(
query,
k=2,
namespace=namespace
)
print("\n带分数的搜索结果:")
for doc, score in results_with_score:
# 【致命陷阱】Pinecone 返回的是"相似度"(越高越相似),而非距离!
# - 余弦相似度范围:[-1, 1],越接近 1 越相关。
# - 这完全不同于 Redis 的"距离"(越低越好)。
print(f"相似度分数: {score:.4f} (越高越好) - {doc.page_content[:30]}...")
# ==================== 9. 删除操作 ====================
# 9.1 删除指定 ID
vector_store.delete(ids=[ids[0]], namespace=namespace)
print(f"\n已删除 ID: {ids[0]}")
# 9.2 删除命名空间下的所有数据
# vector_store.delete(delete_all=True, namespace=namespace) # 取消注释即全删
# print("已清空整个命名空间的数据")
# 验证删除
remaining = vector_store.get_by_ids(ids=[ids[0]], namespace=namespace)
print(f"验证删除结果(剩余条数): {len(remaining)}") # 输出 0
# ==================== 10. 关于 MMR 搜索的补充说明 ====================
# Pinecone 原生不支持 MMR (Max Marginal Relevance),
# 需要在客户端自行实现:先拉取 fetch_k 条,再在本地计算 MMR 排序。
# 简单做法:similarity_search(k=fetch_k) 拿到候选集后手动重排。

4. 对比
| 维度 | InMemoryVectorStore | RedisVectorStore | PineconeVectorStore |
|---|---|---|---|
| 部署依赖 | 无 | Redis + RediSearch 模块 | 云端账号 + API Key |
| 持久化 | 否(重启丢失) | 是(RDB/AOF) | 是(全托管) |
| 索引算法 | 暴力搜索(O(N)) | HNSW / FLAT(ANN) | 专有 ANN(高性能) |
| 相似度度量 | 余弦(固定) | 余弦 / 欧氏 / 内积 | 余弦 / 欧氏 / 点积 |
| 元数据过滤 | 自定义过滤函数 | RedisVL 表达式 | 字典 + 查询语法 |
| MMR 支持 | 否 | max_marginal_relevance_search |
否 |
| 带分数返回 | 否 | _with_score 系列 |
_with_score |
| 批量获取 | get_by_ids |
get_by_ids |
get_by_ids |
| 适用规模 | < 1 万文档 | 1 万 ~ 百万级 | 百万级以上 |
| 典型场景 | 开发测试、单机脚本 | 缓存加速、中等规模 RAG | 生产级大规模 AI 应用 |
5. 一些问题
Q1:为什么相似性搜索要用余弦相似度而不是欧氏距离?
- 文本嵌入向量的模长 通常反映文本长度或词频,而非语义。余弦相似度只关心方向,能更好地捕捉语义上的"一致性"。例如"我喜欢猫"和"猫我喜欢"嵌入方向相近,但欧氏距离可能因长度差异而偏大
Q2:Redis 的 metadata_schema 必须提前定义吗?不定义行不行?
- 答 :可省略,但强烈建议定义 。未定义时 RediSearch 会为所有元数据字段创建默认的
TEXT类型,导致过滤效率低下。明确定义为TAG或NUMERIC可以构建高效索引,极大加速过滤
Q3:Pinecone 和 Redis 在生产环境如何选型?
-
选 Redis:已有 Redis 基础设施,数据量中等(百万级),希望控制成本,且需要低延迟
-
选 Pinecone:如果数据量巨大(千万以上),希望免运维,对写入实时性和一致性要求极高,预算充足
Q4:MMR 中的 fetch_k 设多大合适?
- 一般
fetch_k = 2 * k到5 * k。太小多样性不足,太大增加计算开销。经验值k=10时,fetch_k=20~30。需要根据数据分布调优
Q5:向量存储的"删除"是物理删除还是逻辑删除?
- 三种实现均为物理删除 (从索引和底层存储移除)。但生产环境往往推荐逻辑删除 (软删除),即保留文档并标记
is_deleted=True,在检索时过滤掉,便于数据恢复和审计。LangChain 原生未内置,需开发者自行在 metadata 中维护