本节摘要:第 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:上一节那句「删了向量库可以重建」是这一切换自由的前提。
_ADAPTER_REGISTRY 的 4+1 键与各自定位(嵌入式/云/远程/私有化/GPU)。create_collection_adapter 如何按 config.backend 分派、第三方类路径如何注入。_ovpack/ 内部目录、manifest、index_records.jsonl、dense.f32、sha256 校验、on_conflict 与 vector_mode 策略。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_config 把 config.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)自始至终只认适配器接口——检索代码对「今天搜的是进程内引擎还是云上集群」一无所知,这正是上一节「树是真相源、向量是派生索引」换来的自由。
向量层可插拔,文件层是真相源——那「搬家」怎么办?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,自动回退)。. 按「模块.类」importlib 加载,继承 CollectionAdapter 即插即用,无需改上游。vectordb.backend 见 docs/zh/configuration/01-server.md;README 接入指南三前提(认证/过滤语法/索引约束)、三原则(最小改动/接口不变/差异不出 Adapter 层)。hermes model 切换)、semantica 抽象知识表示层(RDF/属性图/向量 polyglot);OpenViking 特色是插槽自带生产级嵌入式默认值。下一节:
第 8 章 · 01 FastAPI 服务与 MCP 接入——存储之上是服务:FastAPI 的二十余个路由、auth/oauth/API keys 三道门,以及把 OpenViking 暴露成 MCP server、让 Claude Code 直接挂载 viking:// 空间的 endpoint。