跳转至

Embedding 与向量数据库

2.2 Embedding 与向量数据库

一、核心概念

RAG 系统的检索环节本质上是一个"语义匹配"问题:用户问的是"合同违约如何赔偿",知识库里存的是"乙方未按时交付,需按合同金额的 5% 支付违约金"——这两句话没有一个词重合,但语义高度相关。传统的关键词搜索(BM25、Elasticsearch)在这里会完全失效。

解决思路是把文本映射到高维语义空间,让"语义相近"变成"向量距离近"。Embedding 模型就是这个映射函数,它将任意长度的文本压缩成一个固定维度的稠密向量(通常 768–3072 维)。向量数据库则是专门为高效存储和检索这些向量而设计的基础设施——它需要在百万量级的向量中,毫秒级找出与查询向量最相近的 Top-K 条。

理解这个模块的关键在于:Embedding 模型决定检索的天花板,向量数据库决定系统的吞吐和延迟。选错 Embedding 模型,换再好的数据库也救不回来;反之,Embedding 选对了,数据库选型不当会在生产环境中被并发打垮。两个决策都不能省。


二、原理深讲

2.2.1 Embedding 模型选型

工程动机:不同的 Embedding 模型在检索效果、调用成本、是否需要私有化部署上差异显著。开源模型可本地部署避免数据出境合规风险,商业模型省去 GPU 运维成本。选型前必须明确:任务语言、数据敏感程度、预期 QPS、是否需要 Fine-tune。

核心机制:主流 Embedding 模型均基于双塔或单塔 Transformer 结构,通过对比学习(Contrastive Learning)训练,使相关文本对在向量空间中距离更近。关键差异在于:训练数据的语言分布、最大输入 Token 数、向量维度,以及是否支持非对称检索(Query 和 Document 可以用不同表示)。

graph TD
    A[原始文本] --> B[Tokenization]
    B --> C[Transformer Encoder]
    C --> D[Pooling<br/>Mean / CLS / Weighted]
    D --> E[L2 Normalization]
    E --> F[Dense Vector<br/>768 / 1536 / 3072 dim]

    style F fill:#e8f5e9

主流模型横向对比

模型 维度 最大输入 语言 部署方式 适用场景
text-embedding-3-small 1536 8191 Token 多语言 API 调用 快速上线、多语言混合
text-embedding-3-large 3072 8191 Token 多语言 API 调用 效果优先、预算充裕
BGE-M3(BAAI) 1024 8192 Token 中英为主 本地/私有化 中文场景、数据合规要求
GTE-Qwen2-7B 3584 32768 Token 多语言 本地(GPU) 长文档检索、效果顶尖
BGE-large-zh-v1.5 1024 512 Token 中文 本地(CPU可) 中文纯场景、资源受限

工程建议: - 中文业务首选 BGE 系列或 GTE 系列,在 MTEB 中文榜单上持续领先 OpenAI 同级别模型,且可私有化 - 多语言混合场景(中英双语客服)用 text-embedding-3-smallBGE-M3 - 向量维度不是越高越好——维度越高,存储和检索成本越高,低质量数据集上高维度反而容易过拟合噪声 - 如果有标注数据,可以对 BGE 做领域 Fine-tune,通常比直接换大模型效果提升更稳定

2.2.2 向量数据库横向对比

工程动机:能存向量的工具很多(Numpy 数组、Redis、甚至 SQLite),但生产系统需要:持久化存储、CRUD 支持、Metadata 过滤(如按 tenant_id 缩小检索范围)、高并发读写。向量数据库是这些能力的集大成者。

主流数据库对比

特性 Chroma Qdrant Milvus PGVector
部署难度 ⭐(嵌入式/进程) ⭐⭐ ⭐⭐⭐⭐ ⭐⭐(依托 PG)
生产成熟度 中(适合原型) 高(字节/小米在用) 中高
Metadata 过滤 基础 强(Payload 索引) 依赖 SQL WHERE
水平扩展 不支持 支持(分布式版) 原生分布式 依赖 PG 扩展方案
混合查询(向量+SQL) 天然支持
许可证 Apache 2.0 Apache 2.0 Apache 2.0 PostgreSQL
适合场景 快速原型、本地开发 中小生产场景 大规模生产(亿级) 已有 PG 栈的团队

选型决策树

graph TD
    A[开始选型] --> B{数据规模}
    B -->|< 100万向量| C{现有技术栈}
    B -->|> 1000万向量| D[Milvus]
    C -->|已有 PostgreSQL| E[PGVector]
    C -->|无特殊绑定| F{是否需要生产级可靠}
    F -->|快速验证/原型| G[Chroma 嵌入模式]
    F -->|生产服务| H[Qdrant]

    style D fill:#fff3e0
    style E fill:#e3f2fd
    style G fill:#f3e5f5
    style H fill:#e8f5e9

工程建议: - 新项目从 Qdrant 起步,API 设计现代,支持 Payload 索引(可对 Metadata 字段建立倒排加速过滤),Docker 一键启动,后续扩展空间大 - PGVector 最大优势是"不增加基础设施"——如果系统已经重度依赖 PostgreSQL,向量搜索直接跑在同一个数据库,JOIN 操作天然支持,运维成本低 - 不要用 Chroma 做生产环境——并发写入稳定性存疑,0.4.x 之前有严重数据损坏 bug

2.2.3 索引类型:HNSW vs IVF-Flat

工程动机:向量检索的暴力方案是遍历所有向量计算距离(Brute Force),在百万量级下延迟不可接受。近似最近邻搜索(ANN, Approximate Nearest Neighbor)用可接受的精度损失换取数量级的速度提升。

HNSW(Hierarchical Navigable Small World)

直觉:把向量组织成一张多层图,高层图节点稀疏、只保留"远程连接",低层图节点密集、精确定位。查询时从高层入口快速跳跃到目标区域,再在低层细化搜索,像是先定省再定市再定区。

关键参数: - M:每个节点的最大邻居数。M 越大,图越稠密,精度高但构建慢、内存大。推荐从 16 开始调 - ef_construction:构建时搜索的动态候选集大小,影响索引质量,不影响查询速度 - ef(查询时):查询时搜索的动态候选集大小,直接影响 recall/latency 权衡

特点:查询速度快(无需加载全量数据到内存),内存占用较大,适合高并发低延迟场景。Qdrant 默认使用 HNSW。

IVF-Flat(Inverted File with Flat)

直觉:先用 K-Means 把向量空间划分为 nlist 个簇,每个向量分配到最近的簇中心。查询时先找 nprobe 个最近的簇,再在这些簇内暴力搜索。像是先定区县再搜索区县内的街道。

关键参数: - nlist:簇数量,通常设为 sqrt(N)4*sqrt(N)(N 为向量总数) - nprobe:查询时搜索的簇数,越大越准但越慢

特点:内存效率高(可配合 PQ 量化进一步压缩),但需要先训练聚类中心(冷启动问题),适合静态数据集和内存受限场景。

HNSW vs IVF-Flat 对比

维度 HNSW IVF-Flat
查询速度 极快
内存占用 高(图结构)
构建时间 快(需训练)
动态增删 支持(部分库) 麻烦(需重建)
推荐场景 实时写入、高并发查询 批量导入、内存敏感

工程建议:优先选 HNSW——绝大多数 RAG 场景都是实时写入新文档,HNSW 增量插入友好;IVF 类索引在向量集合频繁变化时需要重建聚类中心,运维复杂。

2.2.4 多租户隔离与权限控制

工程动机:SaaS 产品中,每个租户的数据必须严格隔离——不能让 A 公司查询到 B 公司的文档,也不能让普通员工查到只有 HR 才能访问的数据。

三种隔离模式

graph LR
    subgraph "模式一:Collection 级隔离"
        T1[租户 A] --> C1[Collection_A]
        T2[租户 B] --> C2[Collection_B]
    end

    subgraph "模式二:Metadata 过滤隔离"
        T3[租户 A] --> F1["Filter: tenant_id=A"]
        T4[租户 B] --> F2["Filter: tenant_id=B"]
        F1 --> SC[Shared Collection]
        F2 --> SC
    end

    subgraph "模式三:混合隔离"
        T5[高价值租户] --> DC[Dedicated Collection]
        T6[普通租户] --> SF["Filter on Shared"]
    end
隔离模式 数据隔离强度 资源利用率 运维复杂度 适用场景
Collection 级隔离 低(资源碎片化) 高(租户增多后 Collection 爆炸) 少量高价值租户、数据合规严格
Metadata 过滤 中(依赖查询层正确过滤) 中小型 SaaS,租户数百至数千
混合模式 分级 差异化服务(高级版独享,标准版共享)

权限控制最佳实践

# Metadata 过滤隔离的标准实现模式(以 Qdrant 为例)
# 写入时强制注入租户标识和权限标签
payload = {
    "tenant_id": tenant_id,          # 租户隔离键
    "doc_id": doc_id,
    "permission_level": "internal",   # public / internal / confidential
    "department": "hr",              # 部门级权限
    "content": chunk_text,
}

# 查询时必须带上过滤条件——不能依赖上层业务逻辑"记得"加过滤
search_filter = models.Filter(
    must=[
        models.FieldCondition(key="tenant_id", match=models.MatchValue(value=current_tenant_id)),
        models.FieldCondition(key="permission_level", match=models.MatchAnyValue(
            any=user_accessible_levels  # 根据用户角色动态计算
        )),
    ]
)

工程建议: - 权限过滤逻辑应该下沉到数据访问层,而不是散落在业务逻辑里——每个向量检索函数都必须接收 tenant_id 参数,没有默认值,强制调用方传入 - 对 tenant_idpermission_level 字段建立 Payload 索引(Qdrant 的 create_payload_index),否则 Metadata 过滤会退化成全量扫描,延迟直线上升


三、工程视角:常见误区与最佳实践

误区一:用同一个 Embedding 模型编码 Query 和 Document,认为对称就没问题。 → 正确做法:检索任务天然是非对称的——Query 短且口语化,Document 长且正式。BGE 系列提供了 query_instruction_for_retrieval 机制,查询时需在 Query 前加指令前缀(如 "为这个句子生成表示以用于检索相关文章:"),而 Document 无需前缀。忘记加这个前缀会导致检索效果下降 5–15 个点。

误区二:向量数据库的 Collection 建好后不设置 Metadata 索引,直接上线。 → 正确做法:对所有用于过滤的 Metadata 字段(tenant_iddoc_typecreated_at 等)提前建立 Payload 索引。未建索引时,过滤条件会触发 O(N) 全量扫描,数据量增长到 10 万以上时延迟会从 20ms 飙升到 2s+。

误区三:Embedding 模型的 max_tokens 限制被忽视,直接把整页 PDF 塞进去。 → 正确做法:BGE-large-zh-v1.5 最大输入只有 512 Token,超出部分会被截断——模型不报错,但后半段内容完全丢失。使用 tiktoken 或模型对应的 tokenizer 在切块阶段做长度校验,确保每个 chunk 的 Token 数低于模型上限的 90%(留 10% 余量给特殊 Token)。

误区四:开发阶段用 Chroma 内存模式,上线前换 Qdrant,以为"都支持 LangChain 接口所以迁移是零成本"。 → 正确做法:不同向量数据库的 Metadata 过滤语法、Collection 配置、HNSW 参数设置差异很大,LangChain 的抽象层只覆盖了基础 CRUD,高级功能需要直接调用原生 SDK。建议从 MVP 阶段就选定生产向量数据库,或者项目里封装一个抽象接口层,把向量数据库实现细节收敛在一处。

误区五:把向量相似度分数当作绝对置信度,直接透出给用户(如"相关度 0.87")。 → 正确做法:cosine 相似度的分布高度依赖 Embedding 模型和语料领域,同一个分数在不同场景下含义完全不同。线上先做分数分布统计(如 P10/P50/P90),根据业务实际效果确定"有效命中"的分数阈值,再考虑是否要对外展示。


四、延伸思考

🤔 思考题:随着主流大模型的上下文窗口扩展到 100K–1M Token,"把所有文档塞进 Prompt"看起来似乎可以替代 RAG 的检索步骤,那么向量数据库还有存在的意义吗?

长上下文并非向量检索的终结者:其一,长上下文的推理成本随输入长度线性(甚至二次)增长,10 万条文档全塞进去成本无法承受;其二,研究表明 LLM 在超长上下文中存在"Lost in the Middle"现象——关键信息位于中间位置时,模型的利用率显著下降。向量检索在"先过滤、再精读"的分阶段架构中依然不可替代,两者更可能走向结合——检索先召回 Top-K,再交给长上下文模型做精确推理。