很多同学搭 RAG 系统时,第一反应是"装个向量数据库、调个 API 就完了"。结果上线后发现:检索结果不相关、速度越来越慢、换个 Embedding 模型全部数据要重导……这些坑的根源,都是没搞懂向量数据库底层到底在干什么。

今天这篇,我们从 Embedding 原理到相似度计算,再到索引结构和数据库选型,一次性讲透。读完这篇,你再去用 Milvus、Pinecone 这些工具,就是知其所以然了。


01 Embedding 是什么:文本到向量的桥梁

Embedding 的本质是把人类语言映射到高维数学空间,让"语义相近"的文本在空间中"距离相近"。

你可以这样理解:每个文本经过 Embedding 模型处理后,会变成一个固定长度的浮点数数组(向量)。比如一段话经过 OpenAI 的 text-embedding-3-small 模型处理后,会变成一个 1536 维的向量。

"LangChain 是一个 AI 应用开发框架"

    ↓ Embedding 模型

[0.0123, -0.0456, 0.0789, ..., 0.0321]  // 1536 个浮点数

为什么这玩意能做语义检索?因为 Embedding 模型在训练时,见过海量的文本对——它学会了"意思相近的句子应该映射到相近的位置"。所以"如何做饭"和"烹饪教程"的向量会很接近,而"如何做饭"和"量子力学"的向量就会差很远。

在 LangChain.js 中,生成 Embedding 只需要几行代码:

importOpenAIEmbeddingsfrom"@langchain/openai"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"dimensions1536// 可选,控制输出维度// 单条文本 EmbeddingconstawaitembedQuery"LangChain 是一个 AI 应用开发框架"consoleloglength// 1536consolelogslice05// [0.0123, -0.0456, 0.0789, ...]// 批量 Embedding(存储时用)constawaitembedDocuments"LangChain 是一个 AI 应用开发框架""向量数据库用于存储和检索向量""RAG 是检索增强生成的缩写"consoleloglength// 3

这里有个关键区别:embedQuery 用于查询时的单条文本,embedDocuments 用于批量存储文本。某些模型对查询和文档会做不同处理(比如加前缀),所以不要混用。

✅ 正确:查询用 embedQuery,存储用 embedDocuments
❌ 错误:为了省事全用 embedDocuments,某些模型下检索效果会明显变差


02 Embedding 模型选型:钱、效果、速度的三角博弈

没有"最好"的 Embedding 模型,只有最适合你场景的。

目前主流的 Embedding 模型分两大阵营:

商业模型

模型 维度 最大 Token 特点
text-embedding-3-small 1536(可调) 8191 性价比最高,适合大多数场景
text-embedding-3-large 3072(可调) 8191 精度更高,成本翻倍
text-embedding-ada-002 1536 8191 上一代,不推荐新项目用

开源模型

模型 维度 特点
BGE-large-zh-v1.5 1024 中文场景最强开源之一
BGE-m3 1024 多语言、多粒度、多功能
E5-large-v2 1024 英文场景表现优秀
GTE-large 1024 通义千问团队出品

选型建议:

场景判断流程:

你的数据主要是中文?

├── 是 → 预算充足?

│   ├── 是 → text-embedding-3-small(简单省心)

│   └── 否 → BGE-large-zh-v1.5 / BGE-m3(本地部署)

└── 否 → 多语言混合?

    ├── 是 → BGE-m3 / text-embedding-3-small

    └── 否(纯英文)→ E5-large-v2 / text-embedding-3-small

在 LangChain.js 中使用开源模型(通过 HuggingFace 或本地部署的 Ollama):

importOllamaEmbeddingsfrom"@langchain/ollama"// 使用本地 Ollama 部署的 BGE 模型constnewOllamaEmbeddingsmodel"bge-large-zh-v1.5"baseUrl"http://localhost:11434"constawaitembedQuery"向量数据库原理"consoleloglength// 1024

✅ 正确:根据数据语言和预算选模型,先在小数据集上对比效果再做决定
❌ 错误:无脑选最贵的 large 模型——维度越高,存储成本和检索延迟都会增加


03 相似度计算:三种距离度量的本质区别

把文本变成向量之后,“找最相关的内容"就变成了"找距离最近的向量”。 但"距离"有好几种算法,用错了效果天差地别。

余弦相似度(Cosine Similarity)

衡量两个向量方向的夹角,不关心长度。值域 [-1, 1],1 表示完全同方向,0 表示正交,-1 表示完全反向。

余弦相似度 = (A · B) / (|A| × |B|)

向量 A = [1, 2, 3]

向量 B = [2, 4, 6]

余弦相似度 = 1.0  (方向完全相同,虽然长度不同)

最常用。 绝大多数 Embedding 模型输出的向量已经做了归一化(长度为 1),这时候余弦相似度等价于内积。

欧氏距离(Euclidean Distance / L2)

衡量两个向量在空间中的直线距离。值越小越相似。

欧氏距离 = √(Σ(Ai - Bi)²)

向量 A = [1, 0]

向量 B = [0, 1]

欧氏距离 = √2 ≈ 1.414

对向量长度敏感。如果你的 Embedding 没有归一化,同样语义的文本可能因为长度不同而被判为"不相似"。

内积(Inner Product / Dot Product)

内积 = Σ(Ai × Bi)

向量 A = [1, 2, 3]

向量 B = [4, 5, 6]

内积 = 1×4 + 2×5 + 3×6 = 32

当向量已归一化时,内积 = 余弦相似度。速度最快,因为不需要额外计算模长。

怎么选?

你的 Embedding 模型输出已归一化?

├── 是(OpenAI、BGE 等大多数模型)

│   → 用内积(IP),速度最快,效果等于余弦相似度

├── 不确定

│   → 用余弦相似度(COSINE),最稳妥

└── 否(原始向量未归一化)

    → 用余弦相似度(COSINE)或先手动归一化再用内积

用 TypeScript 手动实现看一下直觉:

// 余弦相似度functioncosineSimilaritya: number[], b: number[]numberlet000forlet0lengthreturnMathsqrtMathsqrt// 欧氏距离functioneuclideanDistancea: number[], b: number[]numberlet0forlet0length2returnMathsqrt// 内积functioninnerProducta: number[], b: number[]numberlet0forlet0lengthreturn// 验证:归一化向量下 cosine ≈ inner productconst0.60.8// 长度 = 1const0.80.6// 长度 = 1consolelogcosineSimilarity// 0.96consoleloginnerProduct// 0.96  完全一致!

✅ 正确:搞清楚模型输出是否归一化,再选距离度量
❌ 错误:用欧氏距离搜索未归一化的向量,结果一团糟


04 向量索引结构:从暴力搜索到毫秒级检索

向量多了以后,逐一对比(暴力搜索)不现实——100 万条 1536 维向量暴力搜一次要几秒。索引结构就是加速检索的核心。

暴力搜索(Flat / Brute Force)

查询向量 Q

    ↓ 依次计算与每个向量的距离

[V1] → distance = 0.85

[V2] → distance = 0.32

[V3] → distance = 0.91  ← 最相似

...

[Vn] → distance = 0.67

  • 精度:100%(一定能找到最近的)
  • 速度:O(n),数据量一大就完蛋
  • 适用:数据量 < 10 万,或需要精确结果的场景

IVF(Inverted File Index)

把向量空间划分成多个区域(簇),查询时只搜索最近的几个簇,大幅减少计算量。

训练阶段:K-Means 聚类,把 N 个向量分到 K 个簇中

        簇 1          簇 2          簇 3

      [V1,V5,V8]   [V2,V3,V9]   [V4,V6,V7]

         C1            C2            C3

         (质心)        (质心)        (质心)

查询阶段:

  1. 计算 Q 与所有质心的距离

  2. 选 nprobe 个最近的簇(比如 nprobe=2)

  3. 只在这些簇内暴力搜索

Q → 最近质心: C2, C3

  → 只搜索 [V2,V3,V9,V4,V6,V7]  // 搜索量减少了 1/3

  • 精度:近似(可能错过在其他簇中的好结果)
  • 速度:O(K + nprobe × n/K)
  • 适用:百万级数据,对召回率要求不是极致的场景

HNSW(Hierarchical Navigable Small World)

目前最受欢迎的索引算法。 构建多层级的图结构,上层是"高速公路"快速定位大致区域,下层是"小路"精确查找。

Layer 2(最稀疏):  V1 ──────── V7

                      \        /

Layer 1:          V1 ── V3 ── V7 ── V9

                   |  × |    |  × |

Layer 0(最稠密): V1-V2-V3-V4-V5-V6-V7-V8-V9

查询流程:

  1. 从最高层的入口节点出发

  2. 在当前层贪心搜索最近邻

  3. 找不到更近的了,下沉一层

  4. 重复直到最底层,返回结果

  • 精度:非常高(通常 95%+ 召回率)
  • 速度:O(log n),毫秒级
  • 内存:需要额外存储图结构,内存占用较大
  • 适用:对检索质量要求高、数据量百万到千万级

PQ(Product Quantization,乘积量化)

把高维向量压缩成低精度编码,用空间换时间。

原始向量(1536维)

[0.12, 0.34, ..., 0.56, 0.78]

    ↓ 切分成 M 个子空间(比如 M=8)

[子空间1: 192维] [子空间2: 192维] ... [子空间8: 192维]

    ↓ 每个子空间用 K-Means 聚类,用质心编号代替

[    编号: 42   ] [    编号: 15   ] ... [    编号: 78   ]

    ↓ 压缩后

[42, 15, ..., 78]  // 8 个 uint8,只需 8 字节!

原来 1536 维 float32 需要 6144 字节,压缩后只需 8 字节,压缩比高达 768 倍。

  • 精度:有损压缩,精度最低
  • 速度:超快
  • 内存:极省
  • 适用:亿级数据,对精度要求不高但需要控制成本

各索引对比总览

┌──────────┬─────────┬─────────┬──────────┬──────────────────┐

│  索引     │ 精度    │ 速度     │ 内存     │ 适用数据量        │

├──────────┼─────────┼─────────┼──────────┼──────────────────┤

│ Flat     │ ★★★★★  │ ★☆☆☆☆  │ ★★★☆☆   │ < 10万            │

│ IVF      │ ★★★★☆  │ ★★★☆☆  │ ★★★☆☆   │ 10万 - 500万      │

│ HNSW     │ ★★★★★  │ ★★★★★  │ ★★☆☆☆   │ 10万 - 5000万     │

│ PQ       │ ★★★☆☆  │ ★★★★☆  │ ★★★★★   │ > 1000万          │

│ IVF+PQ   │ ★★★★☆  │ ★★★★☆  │ ★★★★☆   │ > 1000万          │

└──────────┴─────────┴─────────┴──────────┴──────────────────┘

✅ 正确:数据量 < 10 万用 Flat,10 万-千万用 HNSW,亿级用 IVF+PQ
❌ 错误:不管数据量多少都用 HNSW——10 亿条数据 HNSW 内存直接爆炸


05 向量数据库选型:一张表搞清楚

向量数据库 = 向量索引 + 元数据存储 + 过滤 + CRUD + 分布式(可选)。 光有 FAISS 这样的索引库还不够,生产环境你需要一个完整的数据库。

┌─────────────────────────────────────────────────────────┐

│                    向量数据库                              │

│  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐   │

│  │ 向量索引引擎  │  │  元数据存储   │  │  过滤/查询引擎 │   │

│  │ HNSW/IVF/PQ │  │ key-value    │  │ WHERE color=  │   │

│  │             │  │ 文档原文/标签  │  │ "red" AND ... │   │

│  └──────┬──────┘  └──────┬───────┘  └──────┬────────┘   │

│         └────────────────┼─────────────────┘             │

│                   ┌──────┴───────┐                       │

│                   │   API 层     │                       │

│                   │  CRUD/搜索   │                       │

│                   └──────────────┘                       │

└─────────────────────────────────────────────────────────┘

主流选型对比

特性 Milvus Pinecone Chroma FAISS Weaviate
类型 开源/自部署 全托管 SaaS 开源/轻量 索引库 开源/自部署
分布式
元数据过滤 ✅ 强 ✅ 强 ✅ 基础 ✅ 强
最大数据量 百亿级 十亿级 百万级 十亿级 十亿级
上手难度 中等 极低 极低 中等
适合场景 大规模生产 快速上线 原型/小项目 离线实验 生产+语义搜索
LangChain.js 支持

选型决策流程:

你在做什么?

├── 快速原型 / 个人项目 / 数据量 < 100 万

│   → Chroma(内存模式秒启动)

├── 生产环境,不想运维

│   → Pinecone(全托管,按量付费)

├── 生产环境,数据量大,需要自控

│   → Milvus(功能最全,社区最活跃)

├── 纯做实验,不需要持久化

│   → FAISS(纯索引库,最灵活)

└── 需要混合搜索(向量 + 关键词)

    → Weaviate / Milvus

✅ 正确:先用 Chroma 跑通流程,验证可行再迁移到 Milvus/Pinecone
❌ 错误:一上来就搭 Milvus 集群,结果发现你只有 1000 条数据


06 LangChain.js 向量存储实战

LangChain.js 提供了统一的 VectorStore 接口,切换不同数据库只需要换一行初始化代码。

用 Chroma 快速上手

importOpenAIEmbeddingsfrom"@langchain/openai"importChromafrom"@langchain/community/vectorstores/chroma"importDocumentfrom"@langchain/core/documents"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"// 准备文档constnewDocumentpageContent"LangChain 是一个用于构建 AI 应用的开发框架"metadatasource"docs"category"framework"newDocumentpageContent"向量数据库可以存储和检索高维向量数据"metadatasource"docs"category"database"newDocumentpageContent"RAG 通过检索外部知识来增强大模型的回答质量"metadatasource"blog"category"rag"newDocumentpageContent"Embedding 模型将文本转换为稠密向量表示"metadatasource"docs"category"embedding"// 创建向量存储并插入文档(自动调用 embedDocuments)constawaitChromafromDocumentscollectionName"my-collection"url"http://localhost:8000"// Chroma 服务地址// 相似度搜索constawaitsimilaritySearch"什么是向量数据库?"2// 返回 top 2consolelog// [//   Document { pageContent: "向量数据库可以存储和检索高维向量数据", metadata: {...} },//   Document { pageContent: "Embedding 模型将文本转换为稠密向量表示", metadata: {...} }// ]

带元数据过滤的搜索

// 只在 source="docs" 的文档中搜索constawaitsimilaritySearch"什么是向量数据库?"2source"docs"// 元数据过滤// 带相似度分数的搜索constawaitsimilaritySearchWithScore"什么是向量数据库?"3forconstofconsolelog`[${score.toFixed(4)}] ${doc.pageContent}`// [0.9234] 向量数据库可以存储和检索高维向量数据// [0.8567] Embedding 模型将文本转换为稠密向量表示// [0.7891] RAG 通过检索外部知识来增强大模型的回答质量

✅ 正确:用 similaritySearchWithScore 获取分数,设置阈值过滤低质量结果
❌ 错误:只用 similaritySearch 取 top-K,不关心分数——可能把完全不相关的也返回了

用 FAISS 做本地实验

importOpenAIEmbeddingsfrom"@langchain/openai"importFaissStorefrom"@langchain/community/vectorstores/faiss"importDocumentfrom"@langchain/core/documents"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"constnewDocumentpageContent"TypeScript 是 JavaScript 的超集"newDocumentpageContent"Python 是一门动态类型语言"newDocumentpageContent"Rust 以内存安全著称"// 创建 FAISS 向量存储constawaitFaissStorefromDocuments// 搜索constawaitsimilaritySearch"静态类型语言"2consolelog0pageContent// "TypeScript 是 JavaScript 的超集"// 保存到磁盘(下次可以直接加载,不用重新 Embedding)awaitsave"./faiss-index"// 从磁盘加载constawaitFaissStoreload"./faiss-index"

用 Milvus 对接生产环境

importOpenAIEmbeddingsfrom"@langchain/openai"importMilvusfrom"@langchain/community/vectorstores/milvus"importDocumentfrom"@langchain/core/documents"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"constnewDocumentpageContent"HNSW 是目前最流行的向量索引算法"metadatatopic"index"level"advanced"newDocumentpageContent"IVF 通过聚类减少搜索范围"metadatatopic"index"level"intermediate"// 连接 Milvus 并创建 CollectionconstawaitMilvusfromDocumentscollectionName"langchain_demo"url"http://localhost:19530"// Milvus 支持指定索引类型indexCreateParamsindex_type"HNSW"metric_type"IP"// 内积(向量已归一化)paramsJSONstringifyM16efConstruction256searchParamsef128// 搜索时的精度参数constawaitsimilaritySearch"向量索引算法"2consolelog

✅ 正确:Milvus 中指定 index_typemetric_type,根据数据量调整参数
❌ 错误:全用默认参数——10 万条和 1000 万条数据的最优配置差别很大


07 完整 RAG 检索流程:把所有东西串起来

到这里,我们已经理解了所有核心组件。来看一个完整的 RAG 检索流程,从文档到回答:

┌─────────────────── 离线索引阶段 ───────────────────────┐

│                                                        │

│  原始文档                                               │

│    ↓ TextSplitter(上一篇的内容)                         │

│  文本块 [chunk1, chunk2, ..., chunkN]                   │

│    ↓ Embedding Model(embedDocuments)                  │

│  向量 [[0.1, 0.2, ...], [0.3, 0.4, ...], ...]          │

│    ↓ 写入向量数据库                                      │

│  VectorStore(带索引 + 元数据)                           │

│                                                        │

└────────────────────────────────────────────────────────┘

┌─────────────────── 在线查询阶段 ───────────────────────┐

│                                                        │

│  用户问题: "HNSW 算法的优缺点?"                         │

│    ↓ Embedding Model(embedQuery)                     │

│  查询向量 [0.5, 0.6, ...]                              │

│    ↓ 向量数据库检索(ANN 近似最近邻)                      │

│  Top-K 相关文档块                                       │

│    ↓ 拼接到 Prompt 中                                   │

│  LLM 生成回答                                          │

│    ↓                                                   │

│  "HNSW 的优点是检索速度快(O(logN)),                    │

│   精度高(95%+召回率);缺点是内存占用大..."               │

│                                                        │

└────────────────────────────────────────────────────────┘

这个流程中每一步都可能出问题:Splitter 切得不好、Embedding 模型选错、索引类型不对、top-K 设太小……所以理解每个环节的原理非常重要。


08 常见坑

坑 1:维度不匹配

换了 Embedding 模型但没有重新生成向量,老数据 1536 维、新查询 1024 维,直接报错。

// ❌ 存储时用 text-embedding-3-small(1536维)// 查询时换成了 BGE(1024维)// 结果:维度不匹配,检索直接报错// ✅ 换模型 = 全量重新 Embedding + 重建索引,没有捷径

坑 2:归一化问题

用欧氏距离但向量没归一化,长文本和短文本的向量模长差异大,检索偏向长文本。

// 手动归一化functionnormalizevec: number[]numberconstMathsqrtreduce(sum, v) =>0returnmapv =>// ✅ 用余弦相似度就不用管归一化// ✅ 或者存入前统一归一化,然后用内积

坑 3:索引没重建

持续往向量数据库插数据,但 IVF 的聚类中心还是最初训练时的,新数据分布和旧的差异很大,检索质量下降。

解决:定期重建索引。Milvus 提供 compactcreateIndex 接口。

坑 4:Top-K 设得不合理

Top-K 太小(比如 1),可能漏掉关键信息;太大(比如 20),塞太多无关内容给 LLM,反而干扰回答质量还浪费 Token。

建议:先取 Top-10,用分数阈值过滤(比如余弦相似度 < 0.7 的扔掉),再取前 3-5 条给 LLM。

坑 5:没做元数据过滤就全库搜索

你的知识库混了产品文档、内部周报、技术博客,用户问产品问题却搜出了周报。加元数据过滤,先缩小范围再做向量搜索。

// ✅ 先过滤再搜索,又快又准constawaitsimilaritySearch"产品定价策略"5category"product-docs"// 只搜产品文档

总结

  • Embedding 是 RAG 的地基:把文本变成向量,让机器能用数学方式衡量"语义相似度"
  • 模型选型要看场景:OpenAI embedding 简单省心,开源 BGE/E5 适合本地部署和定制化需求,别无脑选最贵的
  • 距离度量决定检索质量:向量已归一化就用内积(最快),不确定就用余弦相似度(最稳)
  • 索引结构决定检索速度:小数据量用 Flat,百万级用 HNSW(精度和速度最平衡),亿级用 IVF+PQ
  • 向量数据库 ≠ 向量索引:生产环境需要元数据过滤、持久化、CRUD、分布式能力,不能只靠 FAISS
  • LangChain.js 的 VectorStore 接口统一了上层调用:换数据库只需改初始化代码,业务逻辑不用动

下一篇我们进入 Milvus 实战——从安装部署到百万级数据的索引构建与检索优化,手把手带你跑通生产级向量数据库。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

在这里插入图片描述

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐