LangChain 之 【内存\Redis\Pinecone向量存储原理与实战】

目录

[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 类型,导致过滤效率低下。明确定义为 TAGNUMERIC 可以构建高效索引,极大加速过滤

Q3:Pinecone 和 Redis 在生产环境如何选型?

  • 选 Redis:已有 Redis 基础设施,数据量中等(百万级),希望控制成本,且需要低延迟

  • 选 Pinecone:如果数据量巨大(千万以上),希望免运维,对写入实时性和一致性要求极高,预算充足

Q4:MMR 中的 fetch_k 设多大合适?

  • 一般 fetch_k = 2 * k5 * k。太小多样性不足,太大增加计算开销。经验值 k=10 时,fetch_k=20~30。需要根据数据分布调优

Q5:向量存储的"删除"是物理删除还是逻辑删除?

  • 三种实现均为物理删除 (从索引和底层存储移除)。但生产环境往往推荐逻辑删除 (软删除),即保留文档并标记 is_deleted=True,在检索时过滤掉,便于数据恢复和审计。LangChain 原生未内置,需开发者自行在 metadata 中维护
相关推荐
吃饱了得干活2 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
Oo9202 小时前
大模型是怎么随机说话的?—— Temperature、Top-k 与 LangChain 实战
langchain
一碗面4214 小时前
LangSmith:LLM 应用调试与评估平台
langchain
陳陈陳13 小时前
Workflow vs Agent:别再被“调包侠”忽悠了,一张图看懂AI工程的“骨架”与“大脑”
langchain·agent·workflow
一只小bit16 小时前
LangGraph 记忆、人机交互、时间旅行和核心能力
机器学习·langchain·人机交互·langgraph
早点睡啊Y19 小时前
深入学 LangChain 官方文档(十六)Built-in 与 Custom Middleware
langchain
老刘说AI20 小时前
AI服务核心: 高并发原理与性能监控调优
人工智能·神经网络·langchain·llama·持续部署
展示猪肝20 小时前
LangChain学习笔记(一):基础入门与核心概念详解
langchain
TheBestRucy1 天前
RAG知识库问答系统落地:从向量检索到上下文增强的全链路实践
人工智能·python·langchain·aigc·交互