第 14 章:Embedding 和向量数据库
本章目标
这一章讲清楚 Embedding 和向量库的作用,并完成一个教学版向量检索。
本章效果
右侧“引用来源”展示了向量检索命中的片段和相似度分数。

Embedding 是什么
Embedding 会把文本转换成一组数字向量。语义相近的文本,向量距离通常也更近。
比如:
“报销需要发票吗”
“费用申请要提供票据吗”
这两句话字面不一样,但语义接近,向量检索有机会把它们匹配到同一份制度。
向量库做什么
向量库存储:
- chunk 文本
- chunk metadata
- embedding 向量
提问时:
用户问题 -> embedding -> 和库里的向量比较 -> 返回最相似的 chunks
向量记录结构
export interface VectorRecord {
id: string;
content: string;
embedding: number[];
metadata: {
documentId: string;
title: string;
source: string;
chunkIndex: number;
};
}
余弦相似度
教学版可以自己写相似度:
export function cosineSimilarity(a: number[], b: number[]) {
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
内存向量库
const records: VectorRecord[] = [];
export function addRecords(nextRecords: VectorRecord[]) {
records.push(...nextRecords);
}
export function searchRecords(queryEmbedding: number[], topK = 5) {
return records
.map((record) => ({
record,
score: cosineSimilarity(queryEmbedding, record.embedding)
}))
.sort((a, b) => b.score - a.score)
.slice(0, topK);
}
注意:内存向量库只适合教学,不适合生产。服务重启数据就没了。
Embedding 模型
可以封装:
import { OpenAIEmbeddings } from "@langchain/openai";
export function createEmbeddings() {
return new OpenAIEmbeddings({
model: process.env.EMBEDDING_MODEL ?? "text-embedding-3-small"
});
}
生成向量:
const embeddings = createEmbeddings();
const vector = await embeddings.embedQuery("报销需要发票吗?");
批量处理 chunk:
const vectors = await embeddings.embedDocuments(chunks.map((chunk) => chunk.content));
实战任务
完成:
- Embedding 模型封装
VectorRecord类型- 内存向量库
- 文档 chunk 入库
- 问题向量检索
常见坑
Embedding 模型和聊天模型不是一回事。不要用聊天模型生成向量。
不同 Embedding 模型生成的向量维度可能不同,不能混用。
生产环境不要用内存向量库。后续可以替换成 pgvector、Qdrant、Milvus、Pinecone。
本章小结
Embedding 和向量库让文档具备语义检索能力。下一章会把检索结果交给模型,完成真正的知识库问答。