一、承接上篇:文档清干净了,但检索问答还缺"地基"
上篇把几百份资料离线批量清理干净,资产沉淀下来了。但要让工具"读懂"文档、做 RAG 问答,光有文档不够——还得先把向量底座打牢:怎么把文字变成向量(嵌入模型)、向量存哪(向量库)、企业已有的 Milvus 怎么安全接进来。这一篇专讲这块"看不见的地基",下一篇才在上层做混合检索与 RAG 编排。
二、痛点:底座不通,上层全是空中楼阁
嵌入模型必须本地可控:企业文档不能发到云端嵌入,要离线、可复现;还要 CUDA/CPU 自适应、用完释放显存,否则显卡被长期霸占。
向量库不能绑死:今天用 Chroma,明天企业说"我们已有 Milvus",底座要能替换、能并存,不能改一行业务代码就崩。
外部向量库要安全接入:Milvus 连接、密码、维度都要托管好,启用不能偷偷回退到本地库,否则检索结果无声错位。
三、技术核心:本地嵌入 + 可插拔存储 + 外部接入
- 本地 BGE 嵌入模型集成
底座首选本地 BGE-large-zh-v1.5(1024 维,归一化),从本地模型路径加载,禁止任何联网下载——这是"本地可控"的硬底线。
设备自适应 + 懒加载:初始化时探测 CUDA → MPS → CPU,可用才用 cuda;保持懒加载,首次向量化才加载,健康检查不占显存。
配置真实消费:device 配置(auto/cpu/cuda/mps/npu)被 RAG Runtime 真正读取,effective 与降级原因都能在健康接口看到,杜绝"选了 CUDA 实际还在用 CPU"。
GPU 显存释放:所有知识库共享单一 BgeLargeZhEmbedding 实例,编码/释放共用 RLock;入库成功或异常都在 finally 显式 release()(模型转 CPU、解引用、GC、empty_cache),连续三轮入库显存不阶梯增长。
2. ChromaDB 持久化 + 可插拔 VectorStore
ChromaDB 持久化落盘;抽象出 EmbeddingProvider / VectorStore 接口,Milvus / Qdrant / Weaviate / pgvector 预留适配,业务层只依赖接口,不依赖具体实现。
接入外部向量库 Milvus
唯一总控开关:system_config.external_vector.enabled 是唯一开关。关闭时所有入库/检索/问答继续走 ChromaDB;启用时仅支持 Milvus,统一走 runtime._create_vector_store() 工厂,禁止业务层散落存储类型判断。
启用前强校验:连接、Collection Schema、当前 Embedding 维度三项全过才允许保存;连接/SDK/维度失败直接阻断,运行时异常不回退 Chroma,返回明确外部库异常。
密码安全:Milvus 密码用 DPAPI/Fernet 密文保存,只在创建连接瞬间解密;保存采用"密文先落库、后重建 Runtime",失败自动恢复旧配置与运行时。
不自动迁移:旧业务模式若绑定空 Collection,明确提示用户重新选择并绑定已导入的库,不自动迁移 Chroma 数据。双存储文库与业务模式绑定路由
新增 storage_provider + Chroma/Milvus 双绑定字段,旧绑定迁移到 Chroma。问答/文库 API/管理界面按 Provider 路由,禁止跨库回退;Chroma 与 Milvus 两项都允许"不绑定"。元数据隔离 + MD5 去重 + 年份版本
向量元数据维度:knowledge_scope / user_key / conversation_id / document_id / skill_name 等;入库基于原文 MD5 精确去重,自动提取 doc_year / doc_version 并支持年份敏感检索。
嵌入模型(示意):共享实例 + 设备自适应 + 显式释放
emb = get_shared_embedding(model_path, device=“auto”) # cuda if available
vecs = emb.embed(texts)
emb.release() # 转 CPU / 解引用 / empty_cache,显存回落
四、踩坑实录:GPU、路径与版本
选了 CUDA 却没用上 GPU:docweb 原装 torch CPU 版,且 Runtime 没消费 device 配置。装 torch cu128 并真正消费配置后生效。
BGE 不释放显存、阶梯增长:每个知识库 Runtime 各建一份 Embedding。改成共享单一实例 + 显式释放后,显存曲线终于平了。
Milvus 文库上传踩坑:DOCX 上传触发 Windows 非法路径、同名库 SQLite UNIQUE 冲突、绑定 milvus:Collection 产生带冒号目录无法被查到——统一安全 slug + Provider 复合唯一 + 旧绑定转换解决。
pymilvus 3.0 ORM 提示 3.1 移除:需另立任务迁移到 MilvusClient,避免后续升级断链。
本地向量底座为什么值得单独讲
很多团队做 RAG,第一步就把"底座"想简单了:随手接个云嵌入、默认 VectorStore,结果要么文档出内网、要么换库要重写。我们把嵌入钉死在本地 BGE(禁止联网)、把存储抽象成可插拔接口、把企业已有的 Milvus 用开关 + 校验 + 密文安全地接进来——底座稳了,上层的检索编排才敢放心迭代。可插拔设计还留了 Qdrant/Weaviate/pgvector 的口子,后续换存储不必改业务代码。
本阶段使用建议
嵌入模型坚持本地路径加载,禁止联网下载,守住数据不出本机;
device 配置要让 Runtime 真正消费,别让 GPU 空转或显存阶梯增长;
接 Milvus 前先校验连接 / Schema / 维度,密码走 DPAPI/Fernet 密文,运行时异常不回退 Chroma;
双存储绑定按 Provider 路由,禁止跨库回退,旧绑定提示重绑不自动迁移;
入库先做 MD5 精确去重,年份/版本元数据一起写,版本检索才准。
五、小结
本地向量底座的灵魂是"可控与可换":嵌入本地可控、存储可插拔、外部 Milvus 安全接入。它把文档从"文件"变成"可被检索的向量资产",是后面 RAG 问答真正能站住脚的地基。
下期预告:底座打好了,下一篇在上层做"混合检索与 RAG 编排"——BM25 守精确词、向量兜语义、RRF 融合排序,以及 LangChain、多模式隔离与那些"以为跑通其实没跑通"的坑。
标签:#BGE #ChromaDB #Milvus #向量数据库 #RAG #嵌入模型 #本地可控