openai-simple-vectorstore
可扩展的多向量数据库接入库,内置 redis-search、milvus、milvus-lite、 pgvector、elasticsearch、sqlite-vec 与 qdrant 等后端,并提供统一的 embeddings / rerank 二阶段召回、插入、删除、刷新等管理接口。
- redis-search 后端与 openai-redis-vectorstore
行为兼容(相同的 uid 规则与 relevance score,生成的 key 布局一致),但索引
PREFIX由 上游的index_name改为index_name + ":",以隔离同名前缀的其它索引(见版本记录)。 - 通过工厂 + 注册表机制可低成本扩展其它向量数据库后端。
安装
作为业务方使用时,从 PyPI 安装本库并选择所需后端:
# 仅核心依赖(不含任何后端驱动,需配合下面的某个后端 extra 使用)
pip3 install openai-simple-vectorstore
# redis-search 后端(也是 create_vector_store() 的默认后端)
pip3 install "openai-simple-vectorstore[redis]"
# 仅 milvus 后端(依赖 pymilvus)
pip3 install "openai-simple-vectorstore[milvus]"
# 仅 milvus-lite 后端(嵌入式 milvus,无需单独部署,适合本地开发/测试)
pip3 install "openai-simple-vectorstore[milvus-lite]"
# 仅 pgvector 后端(依赖 psycopg2-binary)
pip3 install "openai-simple-vectorstore[pgvector]"
# 仅 elasticsearch 后端
pip3 install "openai-simple-vectorstore[elasticsearch]"
# 仅 sqlite-vec 后端(嵌入式 SQLite,无需单独部署)
pip3 install "openai-simple-vectorstore[sqlite-vec]"
# 仅 qdrant 后端(依赖 qdrant-client)
pip3 install "openai-simple-vectorstore[qdrant]"
# 全部后端
pip3 install "openai-simple-vectorstore[all]"
在仓库源码目录内本地开发时使用
pip3 install -e ".[all]";离线安装本库自身依赖时, 先准备本地 wheel 目录,再执行pip3 install --no-index --find-links <wheel 目录> -r requirements.txt。
快速开始
选择 redis-search 后端(默认)
需先安装 redis 后端:
pip3 install "openai-simple-vectorstore[redis]"。
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store() # 默认后端由 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB 决定
# 插入
uid = vs.insert("今天天气很好", kb_id="kb1", doc_id="doc1", page_id="p1")
# 二阶段召回(向量检索 + rerank 重排)
docs = vs.similarity_search_and_rerank(
query="今天天气怎么样",
index_name="default",
k=3,
)
for doc in docs:
print(doc.vs_page_content, doc.vs_embeddings_score, doc.vs_rerank_score)
# 删除 / 刷新
vs.delete(uid)
vs.flush("default")
选择 milvus 后端
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store( # 或环境变量 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB=milvus
vector_db="milvus"
)
vs.insert("..." , kb_id="kb1", doc_id="doc1", page_id="p1")
docs = vs.similarity_search_and_rerank(query="...", index_name="default", k=3)
选择 milvus-lite 后端(嵌入式,本地开发/测试)
milvus-lite 与 milvus 使用相同的 MilvusClient API,仅连接方式不同:milvus 通过
http://host:19530 连接服务端,milvus-lite 通过本地文件路径启动进程内嵌入式数据库,
无需部署 milvus 服务。
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store(
vector_db="milvus-lite", # 或环境变量 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB=milvus-lite
milvus_lite_uri="./milvus_lite.db", # 默认 OPENAI_SIMPLE_VECTORSTORE_MILVUS_LITE_URI
)
vs.insert("..." , kb_id="kb1", doc_id="doc1", page_id="p1")
docs = vs.similarity_search_and_rerank(query="...", index_name="default", k=3)
选择 sqlite-vec 后端(嵌入式,本地开发/测试)
sqlite-vec 是 SQLite 的向量搜索扩展,所有数据存在本地文件中,无需单独部署服务。 注意:需要 sqlite3 支持 loadable-extension(多数发行版 CPython 均支持)。
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store(
vector_db="sqlite-vec", # 或环境变量 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB=sqlite-vec
sqlite_vec_uri="./sqlite_vec.db", # 默认 OPENAI_SIMPLE_VECTORSTORE_SQLITE_VEC_URI
)
vs.insert("..." , kb_id="kb1", doc_id="doc1", page_id="p1")
docs = vs.similarity_search_and_rerank(query="...", index_name="default", k=3)
选择 pgvector 后端(PostgreSQL 扩展)
需要 PostgreSQL 已安装 pgvector 扩展(程序启动时会尝试 CREATE EXTENSION IF NOT EXISTS vector):
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store(
vector_db="pgvector", # 或环境变量 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB=pgvector
pgvector_url="postgresql://user:pass@localhost:5432/postgres", # 默认 OPENAI_SIMPLE_VECTORSTORE_PGVECTOR_URL
)
vs.insert("..." , kb_id="kb1", doc_id="doc1", page_id="p1")
docs = vs.similarity_search_and_rerank(query="...", index_name="default", k=3)
选择 elasticsearch 后端
需要 Elasticsearch 8.x(dense_vector + HNSW):
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store(
vector_db="elasticsearch", # 或环境变量 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB=elasticsearch
es_url="http://localhost:9200",
)
vs.insert("..." , kb_id="kb1", doc_id="doc1", page_id="p1")
docs = vs.similarity_search_and_rerank(query="...", index_name="default", k=3)
选择 qdrant 后端
需要部署 Qdrant 服务(或使用 Qdrant Cloud)。使用原生 payload 过滤,无需像 milvus / sqlite-vec 那样对候选集做放大:
from openai_simple_vectorstore import create_vector_store
vs = create_vector_store(
vector_db="qdrant", # 或环境变量 OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB=qdrant
qdrant_url="http://localhost:6333", # 默认 OPENAI_SIMPLE_VECTORSTORE_QDRANT_URL
qdrant_api_key=None, # 默认 OPENAI_SIMPLE_VECTORSTORE_QDRANT_API_KEY
# qdrant_prefer_grpc=True, # 默认 OPENAI_SIMPLE_VECTORSTORE_QDRANT_PREFER_GRPC
# qdrant_timeout=60, # 客户端请求超时(秒),默认 OPENAI_SIMPLE_VECTORSTORE_QDRANT_TIMEOUT
# qdrant_on_disk=False, # 总开关,默认 OPENAI_SIMPLE_VECTORSTORE_QDRANT_ON_DISK(False)
# qdrant_vector_on_disk=True, # 向量是否落盘,覆盖总开关
# qdrant_hnsw_on_disk=True, # HNSW 图是否落盘,覆盖总开关
# qdrant_payload_on_disk=True, # payload 索引是否落盘,覆盖总开关
)
vs.insert("..." , kb_id="kb1", doc_id="doc1", page_id="p1")
docs = vs.similarity_search_and_rerank(query="...", index_name="default", k=3)
qdrant 的向量、HNSW 图、payload 索引三部分是否落盘可分别配置:
qdrant_vector_on_disk / qdrant_hnsw_on_disk / qdrant_payload_on_disk
(对应环境变量 OPENAI_SIMPLE_VECTORSTORE_QDRANT_{VECTOR,HNSW,PAYLOAD}_ON_DISK)。
三者默认均为 False,即默认全部驻留内存,以获得最低检索延迟。qdrant_on_disk
是给三者兜底的总开关(默认 False):未单独设置的项会继承它,单独设置项优先。
落盘(mmap)可避免大图常驻内存,但检索时的随机读会变慢,建议搭配 NVMe SSD。
本地快速启动(podman):
podman run -d --name qdrant -p 6333:6333 -p 6334:6334 docker.io/qdrant/qdrant:latest
直接实例化(不依赖全局配置)
from openai_simple_vectorstore import RedisVectorStore
from openai_simple_vectorstore.base import Connection
from openai_simple_vectorstore.utils import YamlSerializer
vs = RedisVectorStore(
redis_stack_url="redis://localhost:6379/0",
embeddings_llm=Connection(base_url="http://localhost/v1", api_key="sk-xxx"),
rerank_llm=Connection(base_url="http://localhost/v1", api_key="sk-xxx"),
embeddings_model="bge-m3",
rerank_model="bge-reranker-v2-m3",
metadata_serializer=YamlSerializer(),
)
metadata_serializer决定业务自定义元数据(metadata/vs_metadata)持久化时的序列化方式, 默认YamlSerializer;也可传入JsonSerializer(均定义于openai_simple_vectorstore.utils)。
使用说明
VectorStore 对业务屏蔽后端差异,统一提供写入 / 检索 / 读取 / 删除 / 索引管理接口。
下面所有示例中的 vs 均指已创建的 VectorStore 实例。
写入
# 单条:返回 uid(格式 <index_name>:<page_id>)
uid = vs.insert(
"今天天气很好",
page_id="p1", # 缺省自动生成 uuid4
kb_id="kb1", # 缺省 "default"
doc_id="doc1", # 缺省 "default"
category="news", # 缺省 "default"
index_name="default",
metadata={"author": "alice", "score": 9.5}, # 业务自定义元数据,随记录持久化
)
# 批量:返回 uid 列表;texts / metadatas / page_ids 长度必须一致
uids = vs.insert_many(
["文本一", "文本二"],
metadatas=[{"author": "alice"}, {"author": "bob"}],
page_ids=["p1", "p2"],
kb_id="kb1",
index_name="default",
)
同一
page_id重复写入是覆盖(upsert 语义),不会产生重复记录;调用方传入的metadata/metadatas不会被就地修改。
检索
# 仅向量检索:相关度 = 1 - distance,返回按相关度降序的 [Document, ...]
docs = vs.similarity_search_with_relevance_scores(
"今天天气怎么样",
index_name="default",
k=5,
kb_id="kb1", # 快捷过滤,等价 filters=[Filter("vs_kb_id", ["kb1"])]
categories=["news", "tech"], # 多值:命中其一即可
embeddings_score_threshold=0.3, # 低于阈值的结果直接丢弃(None 表示不过滤)
)
# 二阶段召回:先向量检索取 k*scale 条候选,再经 rerank 重排取前 k
docs = vs.similarity_search_and_rerank(
"今天天气怎么样",
index_name="default",
k=3,
scale=3, # 候选数 = k * scale,默认 2
kb_ids=["kb1", "kb2"],
rerank_score_threshold=0.5,
)
# 多索引联合检索(跨索引去重、统一排序)
docs = vs.similarity_search_with_relevance_scores(
"查询", index_names=["idx_a", "idx_b"], k=10,
)
# 需要命中记录的原始向量时显式开启(默认 False,见下方字段说明)
docs = vs.similarity_search_with_relevance_scores(
"查询", index_name="default", k=5, include_embedding=True,
)
include_embedding默认False:搜索命中通常不需要原始向量,默认不取回可将 取数从 O(k) 次网络往返降到 0(qdrant 直接复用检索 payload);vs_embedding因 此为None。需要向量时传include_embedding=True恢复取回。该参数在similarity_search_with_relevance_scores与similarity_search_and_rerank上均可用。
读取 / 删除 / 清空
item = vs.get_item(uid) # 返回元数据 dict(含 embedding 字节);不存在返回 {}
count = vs.delete(uid) # 返回实际删除条数
count = vs.delete_many(uids) # 按 index_name 自动分组批量删除
vs.flush("default") # 清空该索引数据(破坏性;是否删除底层索引/表随后端而异)
flush一律清空该索引下的数据(破坏性)。是否同时删除底层索引 / 表随后端而异: redis / milvus / milvus-lite / qdrant / sqlite-vec 会删除索引或表;elasticsearch 仅清空文档并保留 index 与 mapping;pgvector 删除共享表中该索引对应的行。
检索结果字段(Document)
similarity_search_* 返回 Document,可用 document_schema=YourModel 换成自定义模型:
| 字段 | 含义 |
|---|---|
vs_page_content |
原始文本内容 |
vs_embedding |
向量(float32 字节);默认不取回(None),传 include_embedding=True 才填充 |
vs_embeddings_score |
向量相关度 1 - distance,取值 [0, 1] |
vs_rerank_score |
rerank 分数(仅 similarity_search_and_rerank) |
vs_uid |
<index_name>:<page_id> |
vs_index_name |
命中的索引名 |
vs_kb_id / vs_doc_id / vs_page_id / vs_category |
核心元数据 |
vs_metadata |
业务自定义元数据 dict(如 {"author": ..., "score": ...}) |
过滤
from openai_simple_vectorstore.base import Filter
# tag 精确过滤:values 内多值为 OR 命中
docs = vs.similarity_search_with_relevance_scores(
"查询", index_name="default", k=5,
filters=[Filter(field="author", values=["alice", "bob"])],
)
# 多个 Filter 之间为 AND;numeric 用 op="range" 传 [low, high]
docs = vs.similarity_search_and_rerank(
"查询", k=5,
filters=[
Filter(field="author", values=["alice"]),
Filter(field="score", values=[7, 10], op="range"),
],
)
# 也接受 dict(等价 tag 精确过滤,多键为 AND);dict 形式不支持 range
docs = vs.similarity_search_with_relevance_scores(
"查询", k=5, filters={"author": "alice", "lang": "zh"},
)
过滤字段必须先在实例上声明,未声明 / 非法取值(空值、
None、low > high)会明确报错, 而不是静默忽略。
声明可过滤字段
业务自定义元数据默认会随记录持久化并在 vs_metadata 中返回;要作为过滤条件,必须显式声明:
from openai_simple_vectorstore.base import IndexField
# 简单方式:一批 tag 字段
vs = create_vector_store(filterable_metadata_fields=["author", "lang"])
# 精细方式(与上面二选一;一旦提供 declared_fields,则忽略 filterable_metadata_fields)
vs = create_vector_store(
declared_fields=[
IndexField(name="author", type="tag"), # 字符串等值
IndexField(name="tags", type="tag_multi"), # 多值集合命中
IndexField(name="score", type="numeric"), # 数值,支持 op="range"
IndexField(name="body", type="text"), # 全文
],
)
- 自定义核心字段名:构造时传
kb_id_field_name/doc_id_field_name/page_id_field_name/category_field_name,检索结果的Document标准字段会自动回填。 - 更多字段模型与各后端过滤能力见下方《字段模型与过滤语义》。
索引管理
# 建索引(幂等):fields 优先于实例声明;已存在时仅增量补字段,不重建
vs.create_index("default", fields=[IndexField(name="author", type="tag")])
# 查看归一化 schema 与是否存在
info = vs.describe_index("default")
print(info.exists, [f.name for f in info.fields])
# 漂移检测与重建(重建是破坏性的,只能显式调用)
diff = vs.compare_index_schema("default")
if diff.drifted or not diff.exists:
vs.rebuild_index("default") # force=True:无条件重建
# vs.rebuild_index("default", force=False) # 仅在检测到漂移时重建
# 原生过滤索引的检查与补建(缺字段时 qdrant 仅变慢,milvus/pgvector/sqlite-vec 由元数据兜底;redis/elasticsearch 可能报错或失配)
if vs.check_index_fields("default"):
vs.build_index_fields("default")
连接与模型参数
from openai_simple_vectorstore.base import Connection
vs = create_vector_store(
vector_db="redis",
redis_stack_url="redis://localhost:6379/0",
embeddings_llm=Connection(base_url="http://localhost/v1", api_key="sk-xxx"),
rerank_llm=Connection(base_url="http://localhost/v1", api_key="sk-xxx"),
embeddings_model="bge-m3",
rerank_model="bge-reranker-v2-m3",
embeddings_dims=1024, # 嵌入维度,需与模型输出一致(默认 1024)
embeddings_max_size=8192, # 单段文本上限,超出截断(默认 1024)
rerank_max_size=8192, # rerank 单段文本上限,超出截断(默认 1024)
embeddings_score_threshold=None, # None 表示不过滤,见下
rerank_score_threshold=None, # None 表示不过滤,见下
redis_timeout=60,
)
- 只传
llm时同时用于 embeddings 与 rerank;分别配置请用embeddings_llm/rerank_llm。 - 各远程后端的超时可用
redis_timeout/milvus_timeout/pgvector_timeout/es_timeout/qdrant_timeout覆盖(默认 60 秒)。 - 两个阈值语义相同:
None(默认)表示不过滤;给出数字时,低于该分数的结果被丢弃。 区别只在作用对象:embeddings_score_threshold作用于向量相关度1 - distance ∈ [0, 1](因此设0.0等同于不过滤),rerank_score_threshold作用于 rerank 模型分数 (取值范围由模型决定,如 bge-reranker 近似[0, 1])。
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENAI_SIMPLE_VECTORSTORE_VECTOR_DB |
redis |
后端:redis / redis-search / milvus / milvus-lite / pgvector / elasticsearch / sqlite-vec / qdrant |
OPENAI_SIMPLE_VECTORSTORE_REDIS_STACK_URL |
redis://localhost:6379/0 |
redis-stack 地址(redis 后端) |
OPENAI_SIMPLE_VECTORSTORE_REDIS_TIMEOUT |
60 |
redis 连接/读写超时秒数(别名 REDIS_TIMEOUT) |
OPENAI_SIMPLE_VECTORSTORE_MILVUS_URI |
http://localhost:19530 |
milvus 地址 |
OPENAI_SIMPLE_VECTORSTORE_MILVUS_TOKEN |
空 | milvus 鉴权 token |
OPENAI_SIMPLE_VECTORSTORE_MILVUS_CONNECTION_ALIAS |
default |
milvus 连接别名 |
OPENAI_SIMPLE_VECTORSTORE_MILVUS_TIMEOUT |
60 |
milvus RPC 超时秒数(别名 MILVUS_TIMEOUT) |
OPENAI_SIMPLE_VECTORSTORE_MILVUS_LITE_URI |
./milvus_lite.db |
milvus-lite 本地数据库文件路径 |
OPENAI_SIMPLE_VECTORSTORE_PGVECTOR_URL |
postgresql://localhost:5432/postgres |
pgvector 连接串(pgvector 后端) |
OPENAI_SIMPLE_VECTORSTORE_PGVECTOR_TIMEOUT |
60 |
pgvector 连接超时秒数(别名 PGVECTOR_TIMEOUT) |
OPENAI_SIMPLE_VECTORSTORE_ELASTICSEARCH_URL |
http://localhost:9200 |
elasticsearch 地址 |
OPENAI_SIMPLE_VECTORSTORE_ELASTICSEARCH_API_KEY |
空 | elasticsearch API key(优先于账号密码) |
OPENAI_SIMPLE_VECTORSTORE_ELASTICSEARCH_USERNAME |
空 | elasticsearch 用户名 |
OPENAI_SIMPLE_VECTORSTORE_ELASTICSEARCH_PASSWORD |
空 | elasticsearch 密码 |
OPENAI_SIMPLE_VECTORSTORE_ELASTICSEARCH_VERIFY_CERTS |
True |
是否校验证书(自签 HTTPS 集群可设 false) |
OPENAI_SIMPLE_VECTORSTORE_ELASTICSEARCH_TIMEOUT |
60 |
elasticsearch 请求超时秒数(别名 ELASTICSEARCH_TIMEOUT / ES_TIMEOUT) |
OPENAI_SIMPLE_VECTORSTORE_SQLITE_VEC_URI |
./sqlite_vec.db |
sqlite-vec 本地数据库文件路径 |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_URL |
http://localhost:6333 |
qdrant 服务地址(别名 QDRANT_URL) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_API_KEY |
空 | qdrant API key(别名 QDRANT_API_KEY) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_PREFER_GRPC |
False |
是否优先使用 gRPC(别名 QDRANT_PREFER_GRPC) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_TIMEOUT |
60 |
qdrant-client 请求超时秒数(别名 QDRANT_TIMEOUT) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_ON_DISK |
False |
向量/HNSW/payload 是否落盘的总开关(别名 QDRANT_ON_DISK) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_VECTOR_ON_DISK |
继承总开关 | 向量列是否落盘(别名 QDRANT_VECTOR_ON_DISK) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_HNSW_ON_DISK |
继承总开关 | HNSW 图是否落盘(别名 QDRANT_HNSW_ON_DISK) |
OPENAI_SIMPLE_VECTORSTORE_QDRANT_PAYLOAD_ON_DISK |
继承总开关 | payload 索引是否落盘(别名 QDRANT_PAYLOAD_ON_DISK) |
OPENAI_BASE_URL |
http://localhost/v1 |
OpenAI 兼容服务基础地址(embeddings / rerank 共用) |
OPENAI_API_KEY |
空 | OpenAI 兼容服务密钥(embeddings / rerank 共用) |
OPENAI_EMBEDDINGS_BASE_URL |
同 OPENAI_BASE_URL |
embeddings 服务地址(单独覆盖) |
OPENAI_EMBEDDINGS_API_KEY |
同 OPENAI_API_KEY |
embeddings 服务密钥(单独覆盖) |
OPENAI_EMBEDDINGS_MODEL |
bge-m3 |
embeddings 模型名 |
OPENAI_EMBEDDINGS_MAX_SIZE |
1024 |
embeddings 单段文本上限,超出截断 |
OPENAI_EMBEDDINGS_DIMS |
1024 |
嵌入向量维度(需与模型输出一致) |
OPENAI_RERANK_BASE_URL |
同 OPENAI_BASE_URL |
rerank 服务地址(单独覆盖) |
OPENAI_RERANK_API_KEY |
同 OPENAI_API_KEY |
rerank 服务密钥(单独覆盖) |
OPENAI_RERANK_MODEL |
bge-reranker-v2-m3 |
rerank 模型名 |
OPENAI_RERANK_MAX_SIZE |
1024 |
rerank 单段文本上限,超出截断 |
兼容
openai-redis-vectorstore:OPENAI_REDIS_VECTORSTORE_REDIS_STACK_URL、OPENAI_EMBEDDINGS_*、OPENAI_RERANK_*、OPENAI_BASE_URL、OPENAI_API_KEY等变量可直接使用。
扩展新的向量数据库后端
继承共享抽象基类 VectorStore 并实现以下方法,再注册到工厂即可:
- 实现
get_cached_vectorstore(引擎的获取与缓存) - 实现
_search_index,返回[(page_id, distance, item), ...] - 实现
get_item/delete/delete_many/flush
from openai_simple_vectorstore.base import VectorStore, IndexField
from openai_simple_vectorstore.registry import register_vector_store, create_vector_store
class MyStore(VectorStore):
# 可选:当 ``_search_index`` 返回的 item 已是"与 get_item 同源的完整序列化
# metadata"(如 qdrant 的 with_payload payload)时置 True,base 在
# include_embedding=False 时可直接复用、省去逐条 get_item 的 N+1 往返。
# 仅返回部分字段(如 redis 默认只返回 page_id)的后端必须保持 False。
_search_payload_is_document = False
def get_cached_vectorstore(self, **kwargs): ...
def _search_index(self, engine, query_embedding, index_name,
filters, filter_expression, k): ...
def get_item(self, uid): ...
def delete(self, uid): ...
def delete_many(self, uids): ...
def flush(self, index_name=None): ...
# 可选的统一字段模型 / 能力矩阵 / schema 查询契约
def _capabilities(self): ...
def _describe_index(self, index_name): ...
def _ensure_index_created(self, index_name, fields): ...
register_vector_store("mydb", MyStore)
vs = create_vector_store(vector_db="mydb")
base 会统一处理 embeddings 生成、rerank、过滤、去重、排序、relevance_score 与
Document 组装,后端只需专注各自的检索实现。
_search_payload_is_document契约:置True表示_search_index返回的item是完整的序列化 metadata(与get_item内部交给get_unserialized_metadata的 dict 同源),base 会在include_embedding=False时直接反序列化复用它。若后端搜索只返回少量字段(如只返回page_id),必须保持False,否则会静默返回缺失内容/核心字段的文档。
字段模型与过滤语义
各引擎通过统一的字段声明 / 能力矩阵 / schema 查询接口,向业务暴露"哪些扩展字段可过滤、索引现状如何":
IndexField:声明字段类型,支持tag/tag_multi/numeric/text/vector; 向量字段必须给出dims,并与引擎配置的embeddings_dims一致。capabilities():返回能力矩阵(Capabilities)——indexed_types表示后端建成 原生可过滤列的类型(如 redis、elasticsearch),meta_filter_types表示在元数据上做 Python/表达式侧兜底过滤的类型(如 milvus / sqlite-vec / pgvector 的部分类型)。describe_index(index_name):返回归一化的IndexSchemaInfo——exists=False表示 索引尚未创建(此时fields返回声明/期望字段,方便业务预创建)。索引存在且后端有可读的 逐字段线上 schema 时返回线上真实字段(redis / elasticsearch / milvus / milvus-lite); qdrant / pgvector / sqlite-vec 无逐字段线上 schema(payload / metadata JSON),fields恒为声明字段。create_index(index_name, fields):按声明的IndexField列表构建索引(显式传入的fields优先于实例构造时的declared_fields);已存在时仅补齐可增量添加的部分 (redisFT.ALTER/ elasticsearchput_mapping/ qdrant payload 索引),不会重建。compare_index_schema(index_name)/rebuild_index(index_name, force=...):只读检测 线上 schema 与期望是否漂移;返回的IndexDiff中exists=False表示索引已不存在 (如被外部删除),drifted=True表示字段 / 维度 / 距离度量不一致。重建一律是破坏性的 (删除并重新索引、丢失该索引下的数据),只能显式调用rebuild_index(force=True无条件重建,force=False仅在检测到漂移时重建),不会在创建 / ensure 索引时自动触发。check_index_fields(index_name)/build_index_fields(index_name):检查声明字段中 哪些缺少后端原生过滤索引(IndexFieldStatus.missing),并按需补建。补建是显式 API, 不会在 ensure / 写入路径上隐式执行——缺字段时 qdrant 仍能正确过滤(仅变慢), milvus / pgvector / sqlite-vec 由元数据侧兜底,redis / elasticsearch 则会过滤报错或 失配;建议由调用方主动调用补建。例外:首次创建索引(redisFT.CREATE/ elasticsearch 建 index / qdrant 建 collection)时会按当时声明一并建好原生索引, 仅当之后新增字段时才需要显式补建。
过滤统一使用 Filter 对象(tag 精确、tag_multi 多值命中、numeric 区间 via op="range");
base 把归一化后的 filters 传入各后端的 engine/search。后端只需实现
_build_filter_expression(filters, filter_expression) 把归一化过滤器组装为原生过滤表达式。
- 索引初始化一次性:引擎实例首次执行
ensure_*(建表 / 建 collection / 建 index) 时完成创建或存在性探测,之后所有读写路径不再重复探测(内部_index_ready/_table_ready标记)。若索引被外部删除或 schema 漂移,用compare_index_schema检测后显式rebuild_index,而不是期望每次操作都自动ensure。 - redis
tag_multi的逗号限制:RediSearch 对 HASH 字段的 TAG 以逗号分隔且 不解析反斜杠转义(实测a\,b会被拆成两个标签),因此单个标签值内部不能包含 逗号;普通多值标签(元素本身不含分隔符)可被正确索引与过滤。
概念说明
- uid:
<index_name>:<page_id>,用于唯一定位一条记录。 - relevance_score:
1 - distance,取值[0, 1],越大越相关。该分数与embeddings_score_threshold统一按余弦距离语义计算,因此所有后端的向量度量 均固定为 cosine;redis 的vector_store_distance_metric也仅接受cosine, 声明的向量IndexField同样只接受metric="cosine",传入其它度量(IP / L2 等) 会在实例化时报ValueError,避免返回错误的相似度。 - 二阶段召回:
similarity_search_and_rerank先用向量检索取k * scale条候选, 再经 rerank 重排取前k条。 - 检索错误诊断:任一后端的向量检索(含超时、连接失败等)都会在抛出前
记录并返回带后端地址 + 索引名 + 检索参数的
RuntimeError,便于定位, 不会把底层驱动的原始调用堆栈直接抛给调用方;索引不存在(未初始化)仍按 空结果处理。 - 连接/查询超时:各远程后端默认 60 秒(
OPENAI_SIMPLE_VECTORSTORE_*_TIMEOUT, qdrant 见上文),也可在实例化时用redis_timeout/milvus_timeout/pgvector_timeout/es_timeout覆盖(qdrant_timeout同理)。 - elasticsearch 批量写入非事务:
insert_many逐条写入,任一条失败时会尽力 回滚(删除)本次已写入的文档后再抛出,尽量表现为“全成功或全不写入”,但 ES 无多 文档事务,回滚本身失败时仅记录 warning,不掩盖原始异常。 - elasticsearch numeric 字段:
numeric映射为float,ES 默认coerce=true,因此字符串数值(如"7")会被接受并转换为数值,属预期行为。
开发与测试
python3 -m pytest -q
跑 pytest 时的覆盖率门槛在 pytest.ini 中(--cov-fail-under=90);
.coveragerc 的 fail_under=80 只作用于单独执行 coverage report 的场合。
测试分两类:
- 单元测试(
test_unit.py/test_engines_*.py):全部基于内存 fake,离线可跑。 - 真实嵌入式/外部服务集成测试(
test_engines_integration.py):优先使用真实数据库 跑完整链路(本地 OpenAI 兼容 HTTP stub 提供 embeddings/rerank,覆盖 base 真实 HTTP 路径):-
milvus-lite:进程内嵌入,直接运行; -
sqlite-vec:需要 sqlite3 支持 loadable-extension,否则自动跳过 (本机 python.org 构建默认不支持,可用支持扩展的 Homebrew python3 执行); -
pgvector:外部服务,通过OPENAI_SIMPLE_VECTORSTORE_PGVECTOR_URL指定连接串, 连接可达才执行。例如:podman run -d --name pgvector-pg17 -p 55432:5432 \ -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres \ docker.io/pgvector/pgvector:pg17 OPENAI_SIMPLE_VECTORSTORE_PGVECTOR_URL='postgresql://postgres:postgres@localhost:55432/postgres' \ python3 -m pytest test_engines_integration.py -q
-
qdrant:外部服务,安装qdrant-client且通过OPENAI_SIMPLE_VECTORSTORE_QDRANT_URL指定的服务可达才执行。例如:podman run -d --name qdrant -p 6333:6333 -p 6334:6334 docker.io/qdrant/qdrant:latest OPENAI_SIMPLE_VECTORSTORE_QDRANT_URL='http://localhost:6333' \ python3 -m pytest test_engines_integration.py -q
未安装
qdrant-client时,test_engines_qdrant.py会注入内存 fake 客户端, 离线即可完整覆盖 qdrant 引擎逻辑。 -
redis:外部服务,需带 RediSearch 的 redis-stack(或内置 search 模块的 Redis 8), 通过OPENAI_SIMPLE_VECTORSTORE_REDIS_STACK_URL指定地址且FT._LIST可用才执行; 普通 redis 会自动跳过。例如:podman run -d --name redis-stack -p 6379:6379 docker.io/redis/redis-stack:latest OPENAI_SIMPLE_VECTORSTORE_REDIS_STACK_URL='redis://localhost:6379/0' \ python3 -m pytest test_engines_integration.py -q
-
# 真实嵌入式/外部后端(milvus-lite / sqlite-vec / pgvector / qdrant / redis,按运行时能力自动启用)
python3 -m pytest test_engines_integration.py -q
版本记录
0.3.2(2026-10-05)
- 检索默认不再取回向量字段(性能修复):
similarity_search_with_relevance_scores/similarity_search_and_rerank新增include_embedding参数,默认False。此前每个命中 都会逐条get_item取回向量字段,向量检索本身是 O(1) 往返,而取数是 O(k)(rerank 为 O(k*scale))次网络往返,是高 k / 生产网络 RTT 下的主要延迟来源。默认关闭后检索结果vs_embedding为None,需要原始向量时传include_embedding=True恢复原行为。 - qdrant 复用检索 payload 消除 N+1:qdrant 检索使用
with_payload=True,命中已携带内容 与全部元数据;关闭include_embedding时直接反序列化复用该 payload,完全省去逐条get_item往返(实测 k=100 由 ~140ms 降至 ~17ms,rerank 100 候选由 ~250ms 降至 ~13ms)。 - 多索引检索只计算一次 query embedding:
index_names联合检索时,query embedding 只与 query 及 embeddings 配置有关、与索引无关;改为先解析各索引引擎,再在循环外只计算一次 embedding 供所有索引复用,避免对每个索引重复发起相同的 embeddings 请求(并保持"后端未 配置先于 embedding 报错"的既有次序)。 - 入口校验与防御性加固:声明的向量
IndexField仅接受metric="cosine";redis 的vector_store_distance_metric也仅接受cosine(非 cosine 在实例化时报ValueError, 避免 relevance score /embeddings_score_threshold静默失真);Connection()未给base_url时回退OPENAI_BASE_URL(不再拼出"/v1/..."相对 URL);numeric 过滤拒绝NaN(避免比较恒为 False 导致静默失配);schema / 引擎钩子补NotImplementedError缺省实现 (第三方后端遗漏实现时不再抛AttributeError);create_index(fields=...)显式字段重名报错。 include_embedding置于参数末尾:similarity_search_with_relevance_scores/similarity_search_and_rerank新增的include_embedding放在参数列表末尾,避免调用方按 位置传参时与既有参数错绑。_split_uid收敛到基类:uid 拆解统一由基类VectorStore._split_uid提供,删除 6 个 引擎中的重复实现。
0.3.1(2026-10-02)
- 索引生命周期收敛:
ensure_*(建表 / collection / index)改为每个引擎实例只探测 一次(内部_index_ready/_table_ready),读写热路径不再重复探测;新增IndexDiff.exists区分"未创建"与"漂移";新增check_index_fields/build_index_fields按需补建后端原生过滤索引(显式 API,不在 ensure / 写入路径隐式触发)。 - qdrant 落盘拆分可配:向量 / HNSW 图 / payload 索引三部分是否落盘可分别配置
(
qdrant_vector_on_disk/qdrant_hnsw_on_disk/qdrant_payload_on_disk,qdrant_on_disk为兜底总开关);默认全部走内存(False,相对 0.3.0 的行为变更)。 - 统一检索错误诊断:任一后端的向量检索(含超时、连接失败)抛出带后端地址 + 索引名 +
检索参数的
RuntimeError,并用结构化index_missing标记只吞"真正未建索引",避免按 文案子串匹配误吞真实错误。 - 可配置超时:redis / milvus / pgvector / elasticsearch / qdrant 新增连接与请求超时
(
*_timeout参数或OPENAI_SIMPLE_VECTORSTORE_*_TIMEOUT,默认 60 秒),redisvl 的SearchIndex同样受 socket 超时约束。 - 跨引擎正确性修复:
- redis:
tag_multi改为直接以 TAG 分隔符拼接/拆分(RediSearch HASH 不解析反斜杠转义),FT.INFO兼容 RESP2/RESP3 与dims=/DIM两种向量参数,describe_index返回线上 FT schema,flushglob 转义并清理缓存引擎,PREFIX改为index_name + ":"以隔离 同名前缀索引(旧索引需rebuild_index后生效); - pgvector:共享表
to_regclass为 NULL 视为"未建表"(不再建幻影表),每连接锁改用WeakKeyDictionary安装,批量写入改为整批事务 + 失败回滚,filter_expression字面%转义; - sqlite-vec:整批写入持有连接锁并支持回滚,读/删不再隐式建表;
- milvus / milvus-lite:含单引号的
page_id回退到自建转义表达式,删除不再因可见性滞后 短路,describe_index缺失向量维度时逐级回退; - elasticsearch:
insert_many中途失败时回滚,覆盖写恢复批处理前旧文档; - qdrant:
filter_expression的 must/should/must_not 归一,numeric 等值过滤改用Range(gte=v, lte=v)。
- redis:
- 过滤与字段模型加固:
Filter取值归一(字符串不逐字符拆分、集合排序、接受单个Filter/dict),拒绝未声明字段、空值 /None、非法op与low > high;numeric 取值 统一转float;tag_multi全后端恒返回 list;核心 id 字段恒按字符串解码;create_index(index_name, fields=...)保留与向量同名的显式声明并合并进实例声明。 - 打包:
requires-python提升至>=3.10,核心依赖补numpy并同步requirements.txt,pydantic>=2与各 extras 增加版本下界,entry-points 导入时自动注册。 - 审计回归(round4 / round5 / round6 / round7 / round8):新增
test_audit_round4.py/test_audit_round5.py/test_audit_round6.py/test_audit_round7.py/test_audit_round8.py,分支覆盖率 100%。round5 / round6 另修复: redis 向量datatype配置生效且capabilities不再把text当作可过滤类型; elasticsearchnum_candidates封顶 10000;qdrant 无FloatIndexParams时 numeric 索引 保持 float;sqlite-vecensure_table加锁并双重检查;并统一澄清flush/describe_index/ 引擎缓存等注释与 README 表述。round7 / round8 另修复: 自定义document_schema不含标准分数字段时(README 允许只传子模型)检索排序 / rerank 不再抛AttributeError/ValueError(排序改用内部计算的分数,写入 rerank 分数时容忍 缺失字段);milvus-lite 不再在模块顶层硬导入pymilvus.milvus_client.index.IndexParams(改为回退分支内惰性导入,并优先尝试旧签名),避免旧版 pymilvus 下整个后端 import 失败。
0.3.0(2026-09-24)
- 新增 qdrant 后端(原生 payload 过滤、
uuid5point id 映射、可配置on_disk, 同时作用于向量、HNSW 图与 payload 索引),并补齐capabilities/describe_index/create_index/ 漂移检测与重建。 create_index(index_name, fields=...)语义统一:显式传入的fields优先于实例构造时的declared_fields,redis / elasticsearch / qdrant 均生效;已存在索引时按可增量方式补齐 字段(redisFT.ALTER ... SCHEMA ADD、elasticsearchput_mapping、qdrant payload 索引)。- 索引重建收敛为显式调用:
rebuild_index不再在创建 / ensure 时自动触发;新增只读的compare_index_schema漂移检测(force=False仅在漂移时重建)。 - 修复 redis 索引 schema 漂移误报:
FT.INFO向量参数兼容dims=/DIM键值对两种格式, 字段类型与向量维度 / 距离度量归一化后比对。 - 移除未被调用的死钩子
_prepare_engine(milvus / milvus-lite / pgvector / elasticsearch / sqlite-vec)。 - 包元数据完善:描述列出全部后端并补充 embeddings / rerank 关键词。
0.2.0(2026-09-08)
- 演进为完整字段模型:新增
IndexField声明(tag/tag_multi/numeric/text/vector)、capabilities()能力矩阵、describe_index()归一化 schema 查询、create_index()schema 声明建索引。 - 过滤 DSL 升级为统一的
Filter(含numeric区间op="range")并新增validate_filters; 各引擎_search_index收敛为(engine, query_embedding, index_name, filters, filter_expression, k), 移除旧的kb_ids/categories参数(内部经Filter适配为vs_kb_id/vs_category字段)。 - redis / elasticsearch / milvus / milvus-lite / pgvector / sqlite-vec 全部适配新签名,
并补齐
capabilities/describe_index/_ensure_index_created。 Document新增vs_metadata字段:检索结果携带业务自定义元数据(如声明IndexField的可过滤标量vs_score),非标准vs_*模型字段在_extract_custom_metadata中聚拢。- milvus(独立部署)insert / delete 不再显式
flush():写入后检索可见依赖服务端自动落盘 (cacheFlushInterval,默认约 3s),有短暂写后读不一致窗口;集成测试据此改为有界轮询收敛。 - 覆盖率提升至 97%(阈值 90%)。
0.1.0(2026-09-08)
- 首个正式版本:在 redis-search 与 milvus 基础上新增 pgvector、elasticsearch、 sqlite-vec 三个后端(factory + registry 注册,支持 extras 选择性安装)。
- 统一 embeddings / rerank 二阶段召回、插入 / 删除 / 更新(upsert)/ 查询 / 刷新等接口。
- 新增 kb / category 元数据过滤语义:各引擎在向量候选上按元数据过滤。
- milvus(独立部署)与 milvus-lite 支持同 page_id 更新(upsert)与删除后立即可见。
- elasticsearch 显式声明 kb / doc / category 等 keyword 映射以支持过滤,并自动迁移旧 schema。
- 新增对 pgvector / elasticsearch / sqlite-vec / milvus 真实外部服务的端到端测试
(
test_engines_integration.py),覆盖率 96%(阈值 90%)。
License
Metadata
Release files for openai-simple-vectorstore 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openai_simple_vectorstore-0.3.2.tar.gz | 117.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openai_simple_vectorstore-0.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 227.8 kB
Release files / openai_simple_vectorstore-0.3.2.tar.gz
| Download URL | openai_simple_vectorstore-0.3.2.tar.gz |
|---|---|
| Size | 117.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb7d702c667b2ce509fb87e5ad9d576d8eeb6479f1075c9f6146dc2be5d9aa8c
|
|
BLAKE2b-256 checksum How to use checksums |
046794c670fb530a67a3cb87e5648f1455828cdb80c6af1a6068244470820c8a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / openai_simple_vectorstore-0.3.2-py3-none-any.whl
| Download URL | openai_simple_vectorstore-0.3.2-py3-none-any.whl |
|---|---|
| Size | 110.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ea20462d5562f3f82a011767e2f7f444bbd7acb18cd96d802a9c6bf8592bae3
|
|
BLAKE2b-256 checksum How to use checksums |
fa41890010d73d1885dcb662c14ebf0e91d995b12e2f883ef7c9f0b97b34169a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|