背景
最近面试了一位候选人,聊到了向量检索在组件库文档搜索中的实践。听起来很高级,但本质上是个很实用的技术。这篇文章用最通俗的话解释一下。
先说结论
向量检索:把文字转换成"数字",然后在这个数字空间里找"意思相近"的内容。
简单说:让机器理解你的意图,而不是死板地匹配文字。
什么是向量检索?
一个生活化的比喻
想象你在图书馆找书。
传统搜索:
店员问:"你要什么书?" 你说:"《挪威的森林》。" 店员精确找到这本书。
向量检索:
店员问:"你想看什么类型的书?" 你说:"我想看关于一个人经历了很多困难,最后成长起来的故事。" 店员不仅找到了《挪威的森林》,还推荐了《少年维特的烦恼》《平凡的世界》... 因为它们"讲的是类似的事情"。
为什么需要它?
你有没有遇到过这种情况:
| 场景 | 搜索结果 |
|---|---|
| 搜"手机" | 只找到含"手机"两个字的内容 |
| 搜"苹果" | 不知道你要水果还是公司 |
| 搜"怎么把手机变成热点" | 搜不到"如何开启手机热点" |
| 打错字 | 什么都搜不到 |
传统搜索就是"字面对字面",你说什么我就找什么,一字不差。
向量检索能解决什么?
| 传统搜索 | 向量检索 |
|---|---|
| 搜"快乐"找不到"开心" | 语义相近都能找到 |
| 搜"苹果"可能混淆 | 理解上下文(水果or公司) |
| 搜法不同就搜不到 | 同一意思不同说法都能搜到 |
它是怎么工作的?
整个过程可以拆成三步:
第一步:转成数字(Embedding)
把一段文字变成一串数字,这串数字就是"向量"。
"如何实现按钮点击" → [0.12, -0.34, 0.56, 0.89, ...] (1536个数字)
"按钮的点击事件怎么做" → [0.11, -0.32, 0.58, 0.88, ...] (1536个数字)关键是:意思相近的文字,生成的数字也会"长得像"。
第二步:找相似的
在所有已转换的文字中,找到数字"距离近"的。
"如何实现按钮点击" → [0.12, -0.34, 0.56, ...]
"按钮的点击事件怎么做" → [0.11, -0.32, 0.58, ...]
↓ 距离很近(相似度 95%)什么是"距离近"?
想象在一个巨大的空间里,每个向量都是一个点:
开心
★
/
/
快乐 ---★--- 悲伤
/ \
/ ★
高兴 难过相近的词在这个空间中的位置也近。计算"距离"有几种方法:
| 方法 | 适用场景 | 了解更多 |
|---|---|---|
| 余弦相似度 | 语义搜索(方向相近) | 余弦相似度 - 维基百科 |
| 欧氏距离 | 绝对距离敏感 | 欧几里得距离 |
| 点积 | 计算效率优先 | 点积 - Khan Academy |
第三步:返回结果
把最相似的几个结果返回给你。
底层原理:文字是怎么变成数字的?
1. 最早的做法:独热编码
最早的词向量很简单,但没什么用。
假设我们有 10000 个词:
"苹果" → [1, 0, 0, 0, ..., 0] (第1位是1,其他都是0)
"香蕉" → [0, 1, 0, 0, ..., 0] (第2位是1,其他都是0)问题:每个词都是"孤立的",苹果和香蕉的距离 = 苹果和手机的距离,完全无法表达语义。
2. 从上下文学习
后来研究发现:一个词的意义,由它的上下文决定。
如果两个词经常出现在相似的上下文里,它们意思就相近。
"我吃了一个苹果" → "苹果"是水果
"我吃了一个香蕉" → "香蕉"是水果
"我买了一部苹果手机" → "苹果"是公司
模型学习后发现:
- "苹果"和"香蕉"经常出现在相似的上下文 → 位置相近
- "苹果"和"手机"有时也在一起 → 有一定关系3. 现代做法:Transformer
现在的 Embedding 模型用的是 Transformer 架构,更加强大。
核心是"注意力机制",让模型理解上下文:
句子:"苹果的营养价值很高"
传统方法:每个词独立看
- "苹果" → 固定向量
Transformer:结合上下文
- "苹果" 在这句话里 → [0.12, -0.34, ...] (理解为水果)
- "苹果" 在另一句话里 → [0.11, -0.31, ...] (可能理解为公司)Attention 的比喻:
读一句话时,你会"关注"不同的词。
读"苹果手机很好用":
- 你关注"手机" → 理解这里"苹果"是公司
读"苹果很甜很好吃":
- 你关注"甜" → 理解这里"苹果"是水果
Transformer 就是模拟这个过程。
4. 一句话总结
| 阶段 | 方法 | 特点 |
|---|---|---|
| 早期 | 独热编码 | 每个词独立,无法表示语义 |
| 后来 | 上下文学习 | 语义相近则向量相近 |
| 现在 | Transformer | 结合上下文,理解深层语义 |
能做什么?
身边的产品例子
你可能已经用过向量检索了,只是没意识到:
1. Google 搜索
当你搜索"手机死机怎么办"时:
- 早期 Google:精确匹配"手机死机怎么办"这几个字
- 现在的 Google:理解你的意图,返回"手机卡顿解决方法""手机开不了机怎么解决"等相关文章
📖 参考:Google AI 搜索技术介绍
Google 从多年前开始大量使用语义搜索技术(BERT、Muennighoff 等)。
2. AI 搜索产品
现在的 AI 搜索(比如 Perplexity、秘塔搜索、知乎直达):
🔗 体验:Perplexity | 秘塔搜索 | 知乎直达
你问:"为什么早起对身体好?"
传统搜索:返回一堆网页链接你自己点开看
AI 搜索:
1. 理解你的问题
2. 向量检索找到相关的健康知识
3. 让 AI 总结成答案直接给你
4. 还会标注信息来源3. 电商搜索
在淘宝搜"显白的裙子":
- 传统搜索:找不到"显白"这个词
- 向量检索:理解你要"显肤色白",返回白色、浅色、粉色等裙子
4. 代码搜索
在 GitHub 搜"发送网络请求":
- 传统搜索:只找含"发送网络请求"的文件
- 向量检索:找到 fetch、axios、XMLHttpRequest 相关的代码
🔗 GitHub 已经在使用语义代码搜索,可以试试 GitHub Code Search
实际应用场景
| 场景 | 用法 |
|---|---|
| 智能客服 | 用户问"怎么退货",找到退货相关的FAQ |
| 文档搜索 | 搜"怎么做表格",找到Excel教程 |
| 代码搜索 | 搜"发送请求",找到HTTP相关代码 |
| 内容推荐 | 给你推荐"你可能感兴趣的文章" |
一个具体例子
假设你在做一个组件库文档搜索:
用户搜索:"点击按钮没反应"
传统搜索:只找含"点击按钮没反应"这几个字的内容
向量检索:找到
- "按钮点击事件无效"
- "button点击没有响应"
- "点击button没反应怎么办"
因为它们"说的是同一件事"。
如何搭建一个向量检索系统?
第一步:准备数据
把所有文档拆分成小块。比如一篇长文档,可以按章节、按段落拆分。
📖 参考:LangChain 文本分割器
原文档
├─ 1. 按钮组件介绍
├─ 2. 按钮的用法
├─ 3. 按钮的事件
└─ 4. 按钮的样式
拆分成:
├─ chunk-1: "按钮组件用于触发操作事件"
├─ chunk-2: "button组件支持onClick事件"
├─ chunk-3: "如何设置按钮的样式"
└─ chunk-4: ...第二步:调用 Embedding API
把每块内容转换成向量。这步不需要自己写算法,调用现成的服务就行。
import OpenAI from "openai"
const client = new OpenAI()
// 把文字转成向量
const response = await client.embeddings.create({
model: "text-embedding-3-small",
input: "按钮组件用于触发操作事件"
})
// 拿到这1536个数字(1536维向量)
const vector = response.data[0].embedding
// [0.12, -0.34, 0.56, 0.89, ...]
1536维是什么意思?
想象一个坐标系,每个向量有1536个坐标。这1536个数字一起定义了它在"语义空间"中的位置。
第三步:存入向量数据库
把每块内容及其对应的向量存到数据库里。
// 存入向量数据库(以Pinecone为例)
await index.upsert({
vectors: [
{ id: "chunk-1", values: vector1, metadata: { content: "按钮组件用于触发操作事件" } },
{ id: "chunk-2", values: vector2, metadata: { content: "button组件支持onClick事件" } },
// ...更多内容
]
})第四步:处理用户搜索
当用户搜索时,同样的逻辑:
// 1. 把用户的搜索词转成向量
const queryVector = await client.embeddings.create({
model: "text-embedding-3-small",
input: "按钮点击怎么做"
})
// 2. 在向量空间里找相似的
const results = await index.query({
vector: queryVector.data[0].embedding,
topK: 5 // 返回最相似的5个结果
})
// 3. 返回结果
console.log(results.matches)
// [
// { id: "chunk-1", score: 0.95, metadata: { content: "按钮组件用于触发操作事件" } },
// { id: "chunk-2", score: 0.89, metadata: { content: "button组件支持onClick事件" } },
// ...
// ]
完整流程图
用户搜索"按钮点击怎么做"
↓
转成向量 [0.15, -0.31, 0.52, ...]
↓
在数据库中找距离近的
↓
返回最相似的5条内容
↓
用户得到答案常用工具
| 用途 | 工具 | 参考链接 |
|---|---|---|
| Embedding 生成 | OpenAI | OpenAI Embeddings 文档 |
| Claude | Anthropic Embeddings | |
| Google Embeddings API | ||
| 开源模型 | Hugging Face Sentence Transformers | |
| 向量数据库 | Pinecone | Pinecone 官网 |
| Milvus | Milvus 官网 | |
| Chroma | Chroma 官网 | |
| Qdrant | Qdrant 官网 |
术语表
| 术语 | 解释 |
|---|---|
| 向量(Vector) | 一串数字,代表文字在语义空间中的位置 |
| Embedding | 把文字转换成向量的过程或模型 |
| 向量检索 | 在向量空间中找相似内容的技术 |
| 语义搜索 | 理解意图的搜索,而不是关键词匹配 |
| 相似度(Similarity) | 两个向量"距离近"的程度 |
| 余弦相似度 | 最常用的相似度计算方法 |
| Transformer | 现在的Embedding模型用的架构 |
| Attention | Transformer的核心,理解上下文 |
| Chunk | 把大文档拆成的小块 |
| TopK | 返回最相似的K个结果 |
| Reranking | 对检索结果重新排序提升精度 |
总结
向量检索解决的核心问题是:让机器理解你的意图,而不是死板地匹配文字。
它的工作流程:
- 转成数字 - 把文字变成向量
- 找相似的 - 在数字空间里找"距离近"的
- 返回结果 - 把最相关的返回
什么时候用它?当你需要语义搜索而不是关键词搜索的时候。
扩展阅读
想深入了解?这些资源可以帮助你:
入门系列
视频教程
实践教程
论文