OpenViking 本地 Embedding Llama-cpp 设计文档
Date: 2026-04-11 Status: 已批准进入实现
目标
为 OpenViking 增加内置的本地 dense embedding 能力,并满足以下产品行为:
- 当用户没有显式配置
embedding时,OpenViking 默认使用本地 embedding backend - 默认本地模型为
bge-small-zh-v1.5-f16 - 本地推理基于
llama-cpp-python加载 GGUF 模型 - 本地推理依赖不放入主依赖,而是通过 optional extra 单独分发,降低安装风险
最终效果是:在不改变“默认走本地 embedding”这一产品目标的前提下,让方案具备可实现性和可维护性。
范围
本次设计包含:
- 新增
embedding.backend = "local"backend - 当用户未提供 embedding 配置时,自动生成隐式本地 embedding 配置
- 基于
llama-cpp-python的 dense embedder - 默认模型
bge-small-zh-v1.5-f16 - 模型路径解析、下载和缓存目录管理
- query/document 双路编码语义
- collection 元数据校验和 rebuild 规则
- 启动期校验与错误提示
- 测试方案和 benchmark 预留
本次设计不包含:
- 本地 sparse embedding
- 本地 hybrid embedding
- 本地失败后静默回退到远程 provider
- 运行时自动安装依赖
- 替换现有远程 provider
决策摘要
OpenViking 采用以下组合策略:
- 产品默认行为:如果用户没有配置 embedding,OpenViking 会隐式选择本地 embedding backend。
- 依赖分发策略:
llama-cpp-python不进入主依赖,而是通过openviking[local-embed]之类的 optional extra 分发。 - 默认本地模型:
bge-small-zh-v1.5-f16。 - 失败策略:如果系统默认选择了本地 embedding,但本地依赖或模型不可用,则直接报错,并给出清晰恢复指引;不会静默回退到远程模型。
这和 QMD 的做法不完全相同。QMD 是 Node CLI 产品,可以把 node-llama-cpp 作为主依赖;而 OpenViking 是 Python SDK 和服务组件,如果让原生依赖阻断主包安装,代价会更高。
为什么这样设计
调研结论和当前代码约束基本指向同一个方向:
- QMD 证明了“默认本地 embedding”这个产品方向是成立的。
- OpenClaw / ArkClaw 证明了基于 GGUF 的本地 memory search 有明确用户价值。
- OpenViking 当前架构决定了 embedding 初始化失败会在启动期暴露,而不是延后到查询时。
- 在 Python 生态里,原生依赖失败的成本通常比 QMD 所在的 npm/Node 生态更高。
因此,这个设计是在保留产品目标的前提下,尽量缩小原生依赖失败的影响范围。
用户可见行为
默认行为
如果配置中没有 embedding:
- OpenViking 自动生成一份隐式 local dense embedding 配置
- backend 设置为
local - model 设置为
bge-small-zh-v1.5-f16 - dimension 设置为该模型对应维度
用户应感知到的行为是:“本地 embedding 是默认值”。
显式行为
如果用户显式配置了 embedding,则始终以显式配置为准,包括:
- 显式
backend: "local" - 显式远程 backend,例如
openai、volcengine、vikingdb - 显式
model_path - 显式
cache_dir
不应存在覆盖用户配置的隐式重写。
安装体验
基础安装:
pip install openviking启用本地 embedding:
pip install "openviking[local-embed]"如果用户依赖默认本地行为,但没有安装 local extra,系统必须在启动时给出可执行的错误提示,至少包含:
- 当前默认启用了本地 embedding
- 缺少
llama-cpp-python - 启用本地 embedding 的安装命令
- 如果用户想改成远程 provider,应如何显式配置
配置设计
在 EmbeddingModelConfig 中新增 local 作为合法 backend。
本地 dense backend 支持的字段:
backend:"local"model: 逻辑模型名,默认bge-small-zh-v1.5-f16model_path: 可选,显式指定 GGUF 文件路径cache_dir: 可选,缓存根目录,默认~/.cache/openviking/models/dimension: 可选,但通常应由内置模型注册表推导batch_size: 预留给后续批量 embedding
建议配置示例:
{
"embedding": {
"dense": {
"backend": "local",
"model": "bge-small-zh-v1.5-f16",
"cache_dir": "~/.cache/openviking/models"
}
}
}显式模型路径示例:
{
"embedding": {
"dense": {
"backend": "local",
"model": "bge-small-zh-v1.5-f16",
"model_path": "/data/models/bge-small-zh-v1.5-f16.gguf"
}
}
}架构设计
新增组件
新增一个本地 dense embedder 实现,例如:
openviking/models/embedder/local_embedders.pyLocalDenseEmbedder
其职责包括:
- 校验
llama-cpp-python是否可用 - 将逻辑模型名解析为 GGUF 模型规格
- 解析或下载模型文件
- 初始化 llama embedding context
- 提供 query/document 双路 embedding 方法
- 返回模型维度
- 在
close()时释放本地资源
Factory 与配置改造
需要修改:
EmbeddingModelConfig.validate_config()以接受backend == "local"EmbeddingConfig._create_embedder()以支持("local", "dense")- 默认配置生成逻辑,使“缺失 embedding 配置”自动变成 local dense
模型注册表
新增一个内置本地模型注册表。第一版可以先做成简单映射,按逻辑模型名索引:
- 逻辑模型名
- GGUF 下载 URL 或 HuggingFace 定位信息
- 预期维度
- 推荐 prompt 规则
- 可选的目标文件名
首个内置模型为:
bge-small-zh-v1.5-f16
Query / Document 双路编码
这部分不是可选优化,而是本方案必须处理的设计点。
BGE/E5 一类模型是检索导向模型,通常需要区分:
- query:用户输入的搜索词或问题
- document:被存储和检索的文本块
OpenViking 当前只有 embed(text),这不足以表达这种语义差异。
设计上新增显式接口:
embed_query(text: str) -> EmbedResultembed_document(text: str) -> EmbedResult
为了兼容现有代码,embed(text) 可以保留为一个薄封装,但内部必须带角色语义。新的检索代码应调用 embed_query(),新的入库代码应调用 embed_document()。
query/document 的格式规则必须封装在本地 embedder 内部,而不是散落在业务层拼装。
模型解析与下载流程
解析顺序
- 如果配置了
model_path,直接使用该路径。 - 否则通过内置本地模型注册表解析
model。 - 如果目标文件不存在,则下载到
cache_dir。 - 用解析后的 GGUF 文件初始化
llama-cpp-python。
缓存目录
默认目录:
~/.cache/openviking/models/
行为要求:
- 目录不存在时自动创建
- 下载后的 GGUF 文件保存在这里
- 如果目标文件已存在,则不重复下载
下载策略
第一版需要支持:
- 可读性好的错误输出
- 稳定可预测的文件命名
- 失败后可手动重试
第一版暂不要求:
- 断点续传
- 多镜像源自动切换
- 后台异步下载器
启动时机与失败行为
OpenViking 当前在 client 启动时就初始化 embedder,本地方案保持这一行为。
因此,下面这些问题都会在启动期直接暴露:
- 没有安装 local extra
llama-cpp-pythonimport 失败- 模型文件缺失且下载失败
- GGUF 文件存在但加载失败
- 当前 collection 元数据与配置模型不一致
错误处理规则
缺少本地依赖:
- 直接抛出明确的配置/运行时错误
- 错误信息中必须包含:缺失包名、安装命令、切换远程 provider 的方法
模型下载失败:
- 抛出包含逻辑模型名、解析 URL、缓存目录和原始异常的错误
模型加载失败:
- 抛出 GGUF 不兼容、文件损坏或当前运行环境不支持的错误
元数据不一致:
- 抛出“当前 embedding 设置与已有索引不兼容,需要 rebuild”的错误
不允许静默回退:
- 本地初始化失败时,不得悄悄切换到
openai、volcengine或vikingdb
Collection 元数据与重建规则
当前系统只在写入时校验向量维度,这在本地模型成为默认值之后是不够的。
需要至少持久化以下元数据:
embedding_backendembedding_modelembedding_dimensionembedding_model_identity
其中 embedding_model_identity 用于区分“看起来模型名相同,但实际模型文件不同”的情况,可以采用:
- 解析后的模型路径
- 模型路径哈希
- 文件哈希(如果成本可接受)
重建触发条件
只要以下任一项发生变化:
- backend
- model
- dimension
- model identity
都应判定现有向量不可兼容。系统需要:
- 在启动时直接报错并提示 rebuild,或
- 在用户显式触发时执行 rebuild 流程
第一版建议采用显式 rebuild,而不是隐式迁移。
数据流改造
入库流程
当前流程:
- 语义处理得到文本
- 队列消费者调用
embed()
改造后流程:
- 队列消费者调用
embed_document() - 本地 embedder 自动套用 document 侧规则
- 向量写入时附带与当前模型一致的 collection 元数据
检索流程
当前流程:
- retriever 调用
embed()
改造后流程:
- retriever 调用
embed_query() - 本地 embedder 自动套用 query 侧规则
- 检索时使用与 document 同体系生成的向量
批量 Embedding 策略
第一版可以先保证单条处理正确,但设计上必须明确批量优化路径。
阶段一:
- 在
LocalDenseEmbedder中实现embed_batch() - 队列层暂时仍允许继续按单条处理
阶段二:
- 在队列侧做消息聚合,一次编码多条待 embedding 文本
如果没有批量能力,本地 CPU 索引构建吞吐大概率会明显低于模型 benchmark。
开发顺序
- 增加
localbackend 的配置校验和 factory 注册。 - 增加“缺失 embedding 配置时默认生成 local dense 配置”的逻辑。
- 实现基于
llama-cpp-python的LocalDenseEmbedder。 - 增加内置本地模型注册表,并接入
bge-small-zh-v1.5-f16。 - 增加模型路径解析、缓存目录和自动下载逻辑。
- 增加
embed_query()/embed_document()双路接口。 - 持久化 collection embedding 元数据,并补一致性检查。
- 增加 rebuild-required 错误流。
- 增加
embed_batch()和 benchmark 基础设施。 - 更新用户文档、示例配置和安装说明。
测试计划
配置测试
- 缺失
embedding时自动生成隐式 local dense 配置 - 显式远程配置时不触发默认本地逻辑
model_path能覆盖逻辑模型解析cache_dir覆盖生效
依赖与初始化测试
- 缺少
llama-cpp-python时,启动错误信息正确 - 显式 local backend 且依赖已安装时可成功初始化
- 非法 GGUF 路径会触发模型加载失败
- 下载失败时错误信息完整可读
Embedding 行为测试
embed_query()与embed_document()走不同路径- 返回维度与模型维度一致
embed_batch()的结果顺序和数量正确
元数据与重建测试
- 首次启动时能生成和当前模型一致的元数据
- 改变 model identity 时会触发需要重建
- 改变 dimension 时会触发需要重建
检索回归测试
- 中文 query 能正确召回中文文档
- query/document 双路编码不会破坏现有检索链路
- 现有远程 provider 行为保持不变
打包测试
pip install openviking可以在不安装本地依赖的情况下成功pip install "openviking[local-embed]"可以启用本地 import 路径- 缺少 extra 且触发默认本地行为时,报错应明确,而不是模糊 import failure
基准测试
至少记录以下指标:
- 依赖已安装且模型已缓存时的启动耗时
- 首次下载模型时的启动耗时
- 单条 embedding 延迟
- 批量 embedding 延迟
- 在代表性中文语料上的索引构建吞吐
在 benchmark 出来之前,不应假设“默认本地 embedding”在所有环境里都同样合适。
运维说明
推荐安装命令:
pip install "openviking[local-embed]"如果用户想使用远程 embedding:
- 显式配置
embedding.dense.backend - 提供相应 provider 的凭证
风险
- 原生依赖安装失败
- 预编译 wheel 覆盖不足
- GGUF 与运行时版本不兼容
- 用户预期“零配置”但实际缺少 local extra
- 模型切换后索引不兼容
- 未做批量聚合时索引吞吐偏低
交付物
- 本地 dense embedder 实现
- local backend 配置与 factory 集成
- 内置模型注册表
- 启动期错误提示与安装指引
- collection 元数据校验
- rebuild-required 机制
- 测试和 benchmark 脚手架
- 用户文档更新
