本节摘要:本章装配管线第⑥段(polyglot 存储)的第一块基石——向量存储。
semantica/vector_store/共 19 个文件、11263 行,用一个VectorStore门面统一了 8 种后端:本地 FAISS、内嵌 inmemory、轻量 SQLite-vec、复用 PostgreSQL 的 PgVector,以及云上四家 Qdrant/Weaviate/Milvus/Pinecone。本节拆解统一接口(add/search/delete/维度校验)如何在不同后端之间平滑分发,给出选型表,并演示"换后端=换一行参数"。
内容来源:原项目源码
semantica/vector_store/vector_store.py、faiss_store.py、pgvector_store.py、sqlite_vec_store.py、qdrant_store.py、__init__.py。
⚠️ 注意:8 个后端里只有
inmemory与 FAISS 是真正零外部服务的;pgvector必须在 config 里给connection_string,sqlite必须给db_path,缺了会在初始化时直接报错——不会静默降级到内存(未知名才会降级并打 warning)。
阅读完本节,你应当能够:
SUPPORTED_BACKENDS 里的 8 个后端名,并说出每个的部署形态(本地/自托管/云托管)。VectorStore.__init__ 如何做后端校验与分发,_init_backend_store 的 if/elif 链长什么样。store_vectors 用 hasattr 探测 add/add_vectors 的适配技巧。先看门面类 VectorStore 的开头(vector_store.py:100-119):
100 class VectorStore: ... 112 SUPPORTED_BACKENDS = {"faiss", "weaviate", "qdrant", "milvus", "pinecone", "pgvector", "inmemory", "sqlite"} 114 def __init__(self, backend="faiss", config=None, max_workers=6, **kwargs): 115 if backend.lower() not in self.SUPPORTED_BACKENDS: 116 raise ValueError( 117 f"Unsupported backend: {backend}. " 118 f"Supported backends are: {...}" 119 )
一个 frozenset 语义的集合变量就是全部后端清单。8 个后端按部署形态分三档:
| 后端 | 文件 | 行数 | 形态 | 一句话定位 |
|---|---|---|---|---|
faiss |
faiss_store.py | 551 | 本地库 | 单机向量检索事实标准,四种索引可玩 |
inmemory |
vector_store.py 内置 | — | 进程内 | 两个 dict,测试与最小示例专用 |
sqlite |
sqlite_vec_store.py | 777 | 嵌入式 | sqlite-vec 扩展,一个文件落盘的轻量持久化 |
pgvector |
pgvector_store.py | 975 | 自托管 | 复用已有 PostgreSQL,不引入新组件 |
qdrant |
qdrant_store.py | 650 | 自托管/云 | payload 过滤好用,生产热门 |
milvus |
milvus_store.py | 701 | 自托管/云 | 十亿级规模与分区能力 |
weaviate |
weaviate_store.py | 712 | 自托管/云 | schema 感知 + GraphQL 查询 |
pinecone |
pinecone_store.py | 766 | 云托管 | 全托管免运维,按用量计费 |
注意 __init__.py 导出面(135-191 行):除 VectorStore 外还导出每个后端的 Client/Collection/Search 细粒度类(如 QdrantClient/QdrantCollection/QdrantSearch)——想绕过门面直连后端也留了门。同目录还住着第 6 章的老朋友 DecisionEmbeddingPipeline(921 行)与 decision_vector_methods.py:决策向量化就长在向量库里,门面还提供 store_decision(vector_store.py:875)直接把决策存成向量。
_init_backend_store(vector_store.py:165-269)是 8 后端的分发现场。以 pgvector 与 faiss 为例:
168 if self.backend == "pgvector": 180 if not connection_string: 180 raise ValueError( 181 "pgvector backend requires 'connection_string' in config. ...") 186 self._backend_store = PgVectorStore(...) 196 elif self.backend == "faiss": 198 self._backend_store = FAISSStore(...) ... 232 elif self.backend == "sqlite": 237 if not db_path: 238 raise ValueError( 239 "sqlite backend requires 'db_path' in config. ...") 246 self._backend_store = SQLiteVecStore(...) ... 258 else: # 未知名后端:降级 in-memory 并告警 259 self.logger.warning(f"Backend '{...}' not implemented, using in-memory") 260 self.backend = "inmemory"
三层容错值得留意:缺必填参数→ValueError(fail fast,不吞错);缺依赖包→ImportError(263 行提示安装对应 extras);未知名→降级 inmemory(带 warning)。inmemory 分支不走这条链(137 行 if self.backend != "inmemory" 才初始化后端),而是在 141-163 行直接建两个字典:
141 if self.backend == "inmemory": 142 self.vectors: Dict[str, np.ndarray] = {} 143 self.metadata: Dict[str, Dict[str, Any]] = {}
inmemory 就是"字典 + 余弦相似度暴力搜",VectorRetriever.search_similar(1350 行)里注释写明用带 epsilon 的余弦相似度避免除零。慢,但结果精确、零依赖——这正是第 1 章"无 key 跑通"承诺在存储层的落点。
SearchResult 的类型定义(vector_store.py:81-89)规定了所有后端必须对齐的返回形状:id/score/metadata/vector/distance,其中注释强调 score 在所有后端归一化到 0.0–1.0、distance 保留后端原生距离值。这是"换后端不换业务代码"的契约核心。
写入路径 store_vectors(vector_store.py:490-529)展示了最有趣的适配层——各后端写入方法名不一致,用 hasattr 逐个探测:
507 if self._backend_store: 510 if hasattr(self._backend_store, 'add'): 511 return self._backend_store.add(vectors, metadata, **options) 512 elif hasattr(self._backend_store, 'add_vectors'): ... 519 params = inspect.signature( self._backend_store.add_vectors).parameters ... 529 raise NotImplementedError(f"Backend store {...} does not have " "add or add_vectors method")
三级探测:先 add(pgvector/sqlite-vec/qdrant 的新接口)→ 再 add_vectors(FAISS 等旧接口,519 行还用 inspect.signature 检查它是否接受 metadata 参数)→ 都没有就 NotImplementedError。删除路径同理(786-795 行探测 delete/delete_vectors)。维度契约则在写入前由后端自行校验(FAISS 建索引时固定 self.dimension,vector_store.py:127 默认 768)。
FAISS(faiss_store.py:193-236)——FAISSIndexBuilder.build_index 一个方法造四种索引:
215 index = faiss.IndexFlatL2(self.dimension) # flat:暴力精确 222 index = faiss.IndexIVFFlat(quantizer, ..., nlist) # ivf:k-means 倒排 226 index = faiss.IndexHNSWFlat(self.dimension, M) # hnsw:多层图 231 index = faiss.IndexPQ(self.dimension, m, bits) # pq:乘积量化压缩
flat 精确但要全扫;IVF 需先 train_index(238-246 行,只对 IVF 训练);HNSW 免训练、M=32 控图密度;PQ 换内存。索引可 save/load(118-133 行,faiss.write_index)。
PgVector(pgvector_store.py)——卖点是"不引入新组件":_get_connection(184-194 行)拿到连接后 register_vector(conn) 注册 vector 类型;_verify_pgvector_extension(210 行)确认 CREATE EXTENSION vector 已装;_ensure_table_exists(230 行)建表存向量列;create_index(769 行)支持 hnsw/ivfflat 索引。向量检索的运维复杂度=你已有的 PG。
SQLite-vec(sqlite_vec_store.py)——依赖是可选的(52-58 行 SQLITE_VEC_AVAILABLE = find_spec("sqlite_vec") is not None),连接支持 :memory:(160 行)或文件路径,底层是 sqlite-vec 的 vec0 虚拟表(65 行)。一个 .db 文件就是一套可持久化的向量库——嵌入式场景(桌面应用、边缘设备)的最优解。
Qdrant(qdrant_store.py:137-156)——写入走 client.upsert、检索走 client.search,payload 过滤是它的招牌;pinecone/milvus/weaviate 同构,都是"客户端类 + 集合管理 + 检索类"三件套,差异被门面吸收。
| 场景 | 推荐 | 理由 |
|---|---|---|
| 开发/单元测试 | inmemory |
零依赖、随起随灭、结果精确 |
| 桌面/边缘/单机原型 | sqlite(或 faiss) |
一个文件落盘;FAISS 还能玩索引类型 |
| 中小生产、检索质量优先 | qdrant |
payload 过滤 + 性能均衡 |
| 亿级规模、多租户分区 | milvus |
分区与水平扩展成熟 |
| 已有 PostgreSQL 基础设施 | pgvector |
复用运维/备份/权限,不添新件 |
| 不想管任何服务器 | pinecone |
全托管,代价是数据出域与按量计费 |
切换后端只动构造那一行,下游全链路无感知:
# 开发:内存起步,无 key 无服务 store = VectorStore(backend="inmemory", dimension=768) # 上生产:换 qdrant,业务代码一行不改 store = VectorStore(backend="qdrant", config={"url": "http://qdrant:6333"}, dimension=768) # 已有 PG:直接复用 store = VectorStore(backend="pgvector", config={ "connection_string": "postgresql://user:pw@host:5432/db", "table_name": "semantica_vectors"}, dimension=768)
这就是"polyglot"三个字的分量:多样性在后端层消化,统一性在门面层供给。第 3 章图谱落库、第 6 章决策向量化、第 9 章混合检索,全都只认 VectorStore 这张脸。
💡 装配要点:polyglot 向量层的三板斧——①一个集合变量
SUPPORTED_BACKENDS做白名单校验(vector_store.py:112);②一条 if/elif 分发链加三层容错(参数缺失 fail fast、依赖缺失 ImportError、未知名降级 inmemory);③hasattr+inspect.signature的鸭子类型适配,抹平各后端方法名差异。score 归一化到 0–1 是跨后端可比的隐藏契约。
_init_backend_store if/elif 链;pgvector 缺 connection_string、sqlite 缺 db_path 直接 ValueError;未知名降级 inmemory 并告警。SearchResult 的 score 全后端归一化 0–1;store_vectors/delete_vectors 用 hasattr 探测 add/add_vectors 完成适配。DecisionEmbeddingPipeline 与 store_decision 就住在 vector_store 里,向量库同时是第 6 章决策智能的底座。下一节:01 节装好了向量这一维。02 节把另外两维也装上——4 种图数据库(Neo4j/FalkorDB/Apache AGE/Amazon Neptune)与 RDF 三元组库(Oxigraph/Blazegraph/Jena/RDF4J),属性图与语义网两大阵营在同一个抽象下共处。