第 7 章 · 03 四种向量后端与 ovpack 快照


第 7 章 · 03 四种向量后端与 ovpack 快照

本节摘要:第 7 章收官。向量层(「像什么」的那一层)是一个可插拔插槽:vectordb_adapters/ 的注册表里躺着 4+1 种后端——local(上一节的 C++ 嵌入式引擎,默认)、volcengine(火山引擎 VikingDB 云托管)、http(远程 OpenViking 实例)、vikingdb(私有化部署),外加 cuvs(GPU 变体)。切换后端 = 配置文件改一行 vectordb.backend。数据保障侧的主角是 ovpack:把整个上下文数据库——文件、sidecar、向量索引——打包成单个 .ovpack 文件,用于备份、迁移、分享;向量可随包携带,也可导入时重算。本节末尾把 OpenViking 的后端抽象与 Hermes 的模型 provider 路由、semantica 的 polyglot 存储抽象摆上同一张对照表。

内容来源:原项目源码 openviking/storage/vectordb_adapters/(factory.py、local_adapter.py、http_adapter.py、volcengine_adapter.py、vikingdb_private_adapter.py、README.md)、openviking/storage/ovpack/(format.py、operations.py、manifest.py)、docs/zh/configuration/01-server.md

⚠️ 注意:cuvs 在注册表里是与 local 并列的键,但语义上是 local 的 GPU 加速变体(同一套 collection 接口,稠密检索交给 cuVS);vikingdb 指私有化部署的 VikingDB,volcengine 才是公有云——四个「真身」是 local/volcengine/http/vikingdb。另外后端切换只影响向量层,文件层永远在 AGFS:上一节那句「删了向量库可以重建」是这一切换自由的前提。

学习目标

  1. 背出 _ADAPTER_REGISTRY 的 4+1 键与各自定位(嵌入式/云/远程/私有化/GPU)。
  2. 读懂工厂与动态加载:create_collection_adapter 如何按 config.backend 分派、第三方类路径如何注入。
  3. 说清「切换 = 一行配置」的分层保证:适配器接口(CollectionAdapter)挡住了什么差异。
  4. 掌握 ovpack 格式:_ovpack/ 内部目录、manifest、index_records.jsonl、dense.f32、sha256 校验、on_conflict 与 vector_mode 策略。
  5. 用对照表讲清三家抽象:OpenViking 向量后端、Hermes 模型 provider、semantica polyglot 存储。

一、4+1 后端注册表

factory.py 全文不足 60 行,核心就是一张字典加一个工厂:

_ADAPTER_REGISTRY: dict[str, type[CollectionAdapter]] = { "local": LocalCollectionAdapter, "cuvs": CuVSCollectionAdapter, "http": HttpCollectionAdapter, "volcengine": VolcengineCollectionAdapter, "vikingdb": VikingDBPrivateCollectionAdapter, } def create_collection_adapter(config) -> CollectionAdapter: """Unified factory entrypoint for backend-specific collection adapters.""" backend = config.backend adapter_cls = _ADAPTER_REGISTRY.get(backend) # If not in registry, try to load dynamically as a class path if adapter_cls is None and "." in backend: module_name, class_name = backend.rsplit(".", 1) module = importlib.import_module(module_name) ... if adapter_cls is None: raise ValueError( f"Vector backend {backend} is not supported. " f"Available backends: {sorted(_ADAPTER_REGISTRY)}") return adapter_cls.from_config(config)

五个键的定位: local——上一节 C++ 引擎的适配器,LocalCollectionAdapter.from_configconfig.path 拼出项目目录(vectordb/),get_or_create_local_collection 拿进程内 collection;零部署、零网络,pip install openviking 之后的默认形态。volcengine——火山引擎 VikingDB 公有云,托管集群、弹性扩缩,适合团队共享与大规模数据。http——指向一个远程 OpenViking 实例的向量服务,把存储与计算彻底分离。vikingdb——VikingDB 私有化部署,数据不出内网的企业选项。cuvs——GPU 变体:from_config 里若 cuvs.auto_enable 开启,dense_search 后端置为 auto_cuvs 并透传配置——有 GPU 用 GPU,没有自动落回 CPU,「auto」二字就是它的全部策略。local_adapter 的这段配置代码值得贴:

@classmethod def from_config(cls, config: Any): project_path = ( str(Path(config.path) / cls.DEFAULT_LOCAL_PROJECT_NAME) if config.path else "") collection_config: Dict[str, Any] = {} cuvs_config = getattr(config, "cuvs", None) if cuvs_config is not None and getattr(cuvs_config, "auto_enable", False): collection_config = { "dense_search": { "backend": "auto_cuvs", **cuvs_config.model_dump(), } } return cls(collection_name=config.name or "context", project_path=project_path, index_name=config.index_name or "default", collection_config=collection_config)

第六种后端不在字典里,而在 . 里:backend 值含点号时按「模块.类」动态加载,只要该类继承 CollectionAdapter 即插即用——第三方向量库接入不需要给 OpenViking 提 PR。

四个形态怎么选?一张决策表:

场景 推荐 理由
个人/单机原型 local 零依赖零配置,数据在本地目录
有 NVIDIA GPU 的重负载 cuvs 稠密检索 GPU 加速,自动回退保平安
团队共享、数据量大 volcengine 托管扩缩,免运维
数据不出内网 vikingdb 私有化部署
存算分离/复用远端 http 计算与存储异地部署

二、切换后端 = 配置一行

docs/zh/configuration/01-server.md 的配置表写得干脆:

| `vectordb.backend` | `local`、`cuvs`、`http`、`volcengine`、`vikingdb` | `local` | 向量数据库后端 |
{ "vectordb": { "backend": "local", "name": "context", "index_name": "default" } }

为什么改一行就够了?因为 CollectionAdapter 基类把后端差异全部挡在了接口之后。目录里的 README(《VectorDB Adapter 接入指南》)给新后端定好了最小义务:from_config(从配置构造)、_load_existing_collection_if_needed(懒加载 collection 句柄)、以及集合管理与数据 API(查/建/删集合,upsert/get/delete/search)的映射;接入前提则要求想清楚三件事——认证方式(AK/SK、token、header)、过滤语法能力(must/range/and/or)、索引参数约束(稠密/稀疏、距离度量、索引类型)。指南的原则陈述同样是架构陈述:「以最小改动新增一个向量库后端;保持上层业务接口不变(find/search 等无需改调用方式);将后端差异封装在 Adapter 层,不泄漏到业务层」。上层(第 4 章 HierarchicalRetriever 的 search_in_tenant/search_children_in_tenant)自始至终只认适配器接口——检索代码对「今天搜的是进程内引擎还是云上集群」一无所知,这正是上一节「树是真相源、向量是派生索引」换来的自由。

三、ovpack:整个上下文数据库装进一个文件

向量层可插拔,文件层是真相源——那「搬家」怎么办?storage/ovpack/ 的答案是打包快照。format.py 顶部把格式常量一字排开:

OVPACK_FORMAT_VERSION = 3 OVPACK_KIND = "openviking.ovpack" OVPACK_INTERNAL_DIR = "_ovpack" OVPACK_FILES_DIR = "files" OVPACK_MANIFEST_FILENAME = "manifest.json" OVPACK_INDEX_RECORDS_FILENAME = "index_records.jsonl" OVPACK_DENSE_FILENAME = "dense.f32" OVPACK_ON_CONFLICT_VALUES = frozenset({"fail", "overwrite", "skip"}) OVPACK_VECTOR_MODE_VALUES = frozenset({"auto", "recompute", "require"}) OVPACK_BACKUP_NAME = "openviking-backup"

结构一目了然:files/ 装整棵文件树(正文+sidecar 原样),_ovpack/manifest.json 装清单,_ovpack/index_records.jsonl 逐行装向量索引记录,_ovpack/dense.f32 是稠密向量的原始浮点块。每一份内容带 sha256 校验(sha256_hex),路径安全有正则把关(_UNSAFE_PATH_RE.. 穿越、_DRIVE_RE 防 Windows 盘符注入)——一个要在不可信环境间传递的包,防的是「解压即穿越」。包内一棵树的样子:

my-kb.ovpack (ZIP) ├── files/ # 整棵 viking:// 子树原样平铺 │ ├── docs/01-intro.md │ ├── docs/.abstract.md # L0 sidecar 随树携带 │ └── docs/.overview.md # L1 sidecar 同上 └── _ovpack/ ├── manifest.json # 清单:版本、来源、条目与校验和 ├── index_records.jsonl # 向量索引记录(逐行) └── dense.f32 # 稠密向量原始浮点块

operations.py 提供四个动词:export_ovpack(导出,可只导子树)、import_ovpack(导入)、backup_ovpack(全量备份,固定名 openviking-backup)、restore_ovpack(恢复,导入前先 _backup_entries 保护现场)。导入不是简单解压:_exportable_entries 会筛掉不可导出的内部条目,_filter_existing_optional_sidecars 处理可选 sidecar 的撞车,_enqueue_direct_vectorization 在 vector_mode 需要重算时把向量化任务直接排进队列——导入结束即可检索,不需要手动重建索引。两个策略旋钮值得记: on_conflict——导入撞上已有 URI 时 fail(报错)/overwrite(覆盖)/skip(跳过),语义清晰到不用解释; vector_mode——auto(有向量记录就复用,缺了就重算)/recompute(一律重算)/require(必须有,缺失即失败)。recompute 模式的存在本身就是上一节结论的应用:向量是文件层的派生物,嵌入模型换了版本、或目标后端换了格式,重算即得新世界——ovpack 换后端迁移时不必携带旧引擎的向量。典型用法:备份(backup/restore 一对)、迁移(本地 local 后端 → 导出 → 云上 volcengine 导入)、分享(把一个知识库 ovpack 发给同事,导入即得完整目录树+索引)。

四、三家抽象对照:插槽思维的三种用法

把镜头拉远。可插拔后端是当下 Agent 基础设施的通用手法,三家各有各的「插槽」:

维度 OpenViking Hermes semantica
抽象对象 向量存储后端(vectordb_adapters) 模型 provider(约 8 家:OpenAI/Anthropic/OpenRouter/自建端点等) 知识存储后端(polyglot)
内置选项 local(C++ 嵌入)/cuvs/http/volcengine/vikingdb + 动态类路径 Nous Portal 聚合多家,hermes model 一键切换 RDF 三元组库(Oxigraph/Jena/RDF4J)、属性图(Neo4j/Neptune/AGE)、向量库
切换成本 配置一行 vectordb.backend 一条命令 hermes model 代码不动换存储引擎
差异藏在 CollectionAdapter 接口 provider 适配层 查询方言编译层
兜底默认 local 零依赖嵌入式 自建 endpoint 内嵌 Oxigraph

三家的分野在「抽象的是哪一层」:Hermes 抽象算力(谁来推理),OpenViking 抽象索引(在哪找相似),semantica 抽象知识表示(图模型落在哪种引擎)。OpenViking 的独特处是插槽里自带一个生产级默认值——local 后端让它「不依赖任何外部服务也完整可用」,云后端则是同一接口上的扩容选项;多数项目的顺序恰好相反(先接外部服务,嵌入式反而缺位)。

💡 漫游要点:本章从上到下三层看完——viking_fs 语义门面(01 节)→ C++/Rust 机制层(02 节)→ 可插拔后端与数据保障(本节)。串起来的逻辑链:向量是派生索引,所以可换后端;后端可换,所以适配器挡差异;差异被挡住,所以切换=一行配置;派生索引可重算,所以 ovpack 敢提供 recompute;文件树+清单+向量单文件打包,所以备份/迁移/分享三合一。存储章的全部设计,都是「文件系统范式」这一个决定的下游。

本节要点回顾

  • _ADAPTER_REGISTRY = {local, cuvs, http, volcengine, vikingdb};真身四家:local=进程内 C++ 引擎(默认零依赖)、volcengine=VikingDB 公有云、http=远程实例、vikingdb=私有化部署;cuvs 是 GPU 变体(auto_enable 时 dense_search=auto_cuvs,自动回退)。
  • 动态后端:backend 含 . 按「模块.类」importlib 加载,继承 CollectionAdapter 即插即用,无需改上游。
  • 切换=一行:vectordb.backend 见 docs/zh/configuration/01-server.md;README 接入指南三前提(认证/过滤语法/索引约束)、三原则(最小改动/接口不变/差异不出 Adapter 层)。
  • ovpack v3:files/ 全树 + _ovpack/(manifest.json、index_records.jsonl、dense.f32),sha256 校验、防路径穿越;export/import/backup/restore 四动词;on_conflict=fail/overwrite/skip,vector_mode=auto/recompute/require;备份固定名 openviking-backup,restore 前先保护现场。
  • 三家对照:OpenViking 抽象索引层、Hermes 抽象模型算力层(约 8 provider,hermes model 切换)、semantica 抽象知识表示层(RDF/属性图/向量 polyglot);OpenViking 特色是插槽自带生产级嵌入式默认值。

下一节:第 8 章 · 01 FastAPI 服务与 MCP 接入——存储之上是服务:FastAPI 的二十余个路由、auth/oauth/API keys 三道门,以及把 OpenViking 暴露成 MCP server、让 Claude Code 直接挂载 viking:// 空间的 endpoint。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U