《60天AI学习计划启动 | Day 08: 向量数据库基础 - RAG 的核心技术》
Day 08: 向量数据库基础 - RAG 的核心技术
学习目标
核心学习内容
1. 向量和 Embedding
什么是向量?
- 数值数组,表示文本的数学表示
- 相似文本 → 相似向量
- 高维空间中的点(通常 1536 维)
什么是 Embedding?
- 将文本转换为向量的过程
- 保留语义信息
- 相似文本在向量空间中距离近
示例:
"前端开发" → [0.1, 0.2, -0.3, ..., 0.5] (1536维向量)
"JavaScript" → [0.12, 0.18, -0.28, ..., 0.48] (相似向量)
"后端开发" → [0.3, 0.1, -0.1, ..., 0.2] (不同向量)
2. 向量数据库
作用:
- 存储向量数据
- 快速相似度搜索
- 支持大规模数据
常见方案:
- Pinecone(云服务)
- Weaviate(开源)
- Chroma(轻量级)
- 自建(内存/文件)
3. 相似度计算
方法:
- 余弦相似度(常用)
- 欧氏距离
- 点积
余弦相似度公式:
similarity = (A · B) / (||A|| * ||B||)
范围:-1 到 1,1 表示完全相同
实践作业
作业1:实现 Embedding 生成
src/services/embedding.js:
import OpenAI from 'openai';
import { logger } from '../utils/logger.js';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
/**
* 生成文本的 Embedding
*/
export async function generateEmbedding(text) {
try {
const response = await openai.embeddings.create({
model: 'text-embedding-ada-002',
input: text
});
return response.data[0].embedding;
} catch (error) {
logger.error('生成 Embedding 失败:', error);
throw error;
}
}
/**
* 批量生成 Embedding
*/
export async function generateEmbeddings(texts) {
try {
const response = await openai.embeddings.create({
model: 'text-embedding-ada-002',
input: texts
});
return response.data.map(item => item.embedding);
} catch (error) {
logger.error('批量生成 Embedding 失败:', error);
throw error;
}
}
/**
* 文本分块(为长文本生成多个 Embedding)
*/
export function chunkText(text, chunkSize = 500, overlap = 50) {
const chunks = [];
let start = 0;
while (start < text.length) {
const end = Math.min(start + chunkSize, text.length);
const chunk = text.substring(start, end);
chunks.push({
text: chunk,
start: start,
end: end
});
start = end - overlap; // 重叠部分
}
return chunks;
}
作业2:实现向量相似度计算
src/utils/vector-math.js:
/**
* 计算两个向量的余弦相似度
*/
export function cosineSimilarity(vecA, vecB) {
if (vecA.length !== vecB.length) {
throw new Error('向量维度不匹配');
}
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dotProduct += vecA[i] * vecB[i];
normA += vecA[i] * vecA[i];
normB += vecB[i] * vecB[i];
}
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
if (magnitude === 0) {
return 0;
}
return dotProduct / magnitude;
}
/**
* 计算欧氏距离
*/
export function euclideanDistance(vecA, vecB) {
if (vecA.length !== vecB.length) {
throw new Error('向量维度不匹配');
}
let sum = 0;
for (let i = 0; i < vecA.length; i++) {
const diff = vecA[i] - vecB[i];
sum += diff * diff;
}
return Math.sqrt(sum);
}
/**
* 计算点积
*/
export function dotProduct(vecA, vecB) {
if (vecA.length !== vecB.length) {
throw new Error('向量维度不匹配');
}
let sum = 0;
for (let i = 0; i < vecA.length; i++) {
sum += vecA[i] * vecB[i];
}
return sum;
}
作业3:实现简单向量数据库
src/services/vector-db.js:
import fs from 'fs/promises';
import path from 'path';
import { generateEmbedding } from './embedding.js';
import { cosineSimilarity } from '../utils/vector-math.js';
import { logger } from '../utils/logger.js';
/**
* 简单的内存向量数据库
*/
export class VectorDB {
constructor() {
this.vectors = []; // [{ id, text, embedding, metadata }]
this.dimension = 1536; // OpenAI embedding 维度
}
/**
* 添加文档
*/
async addDocument(text, metadata = {}) {
try {
// 生成 Embedding
const embedding = await generateEmbedding(text);
const document = {
id: this.generateId(),
text: text,
embedding: embedding,
metadata: {
...metadata,
createdAt: new Date().toISOString()
}
};
this.vectors.push(document);
logger.info(`添加文档: ${document.id}`);
return document.id;
} catch (error) {
logger.error('添加文档失败:', error);
throw error;
}
}
/**
* 批量添加文档
*/
async addDocuments(texts, metadatas = []) {
const ids = [];
for (let i = 0; i < texts.length; i++) {
const id = await this.addDocument(texts[i], metadatas[i] || {});
ids.push(id);
}
return ids;
}
/**
* 相似度搜索
*/
async search(queryText, topK = 5) {
try {
// 生成查询向量
const queryEmbedding = await generateEmbedding(queryText);
// 计算相似度
const results = this.vectors.map(doc => ({
id: doc.id,
text: doc.text,
metadata: doc.metadata,
similarity: cosineSimilarity(queryEmbedding, doc.embedding)
}));
// 按相似度排序
results.sort((a, b) => b.similarity - a.similarity);
// 返回 topK
return results.slice(0, topK);
} catch (error) {
logger.error('搜索失败:', error);
throw error;
}
}
/**
* 根据 ID 获取文档
*/
getDocument(id) {
return this.vectors.find(doc => doc.id === id);
}
/**
* 删除文档
*/
deleteDocument(id) {
const index = this.vectors.findIndex(doc => doc.id === id);
if (index !== -1) {
this.vectors.splice(index, 1);
logger.info(`删除文档: ${id}`);
return true;
}
return false;
}
/**
* 获取所有文档
*/
getAllDocuments() {
return this.vectors.map(doc => ({
id: doc.id,
text: doc.text,
metadata: doc.metadata
}));
}
/**
* 清空数据库
*/
clear() {
this.vectors = [];
logger.info('数据库已清空');
}
/**
* 保存到文件
*/
async saveToFile(filePath) {
try {
const data = {
vectors: this.vectors.map(doc => ({
id: doc.id,
text: doc.text,
embedding: doc.embedding,
metadata: doc.metadata
}))
};
await fs.writeFile(filePath, JSON.stringify(data, null, 2));
logger.info(`数据已保存到: ${filePath}`);
} catch (error) {
logger.error('保存失败:', error);
throw error;
}
}
/**
* 从文件加载
*/
async loadFromFile(filePath) {
try {
const data = await fs.readFile(filePath, 'utf-8');
const parsed = JSON.parse(data);
this.vectors = parsed.vectors || [];
logger.info(`从文件加载了 ${this.vectors.length} 个文档`);
} catch (error) {
if (error.code === 'ENOENT') {
logger.warn('文件不存在,创建新数据库');
this.vectors = [];
} else {
logger.error('加载失败:', error);
throw error;
}
}
}
/**
* 生成唯一 ID
*/
generateId() {
return `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 获取统计信息
*/
getStats() {
return {
totalDocuments: this.vectors.length,
dimension: this.dimension
};
}
}
// 单例
export const vectorDB = new VectorDB();
作业4:实现文档处理服务
src/services/document-processor.js:
import { chunkText } from './embedding.js';
import { vectorDB } from './vector-db.js';
import { logger } from '../utils/logger.js';
/**
* 文档处理器
*/
export class DocumentProcessor {
/**
* 处理并存储文档
*/
async processDocument(text, metadata = {}) {
try {
// 文本分块
const chunks = chunkText(text, 500, 50);
logger.info(`文档分块: ${chunks.length} 个块`);
// 为每个块生成 Embedding 并存储
const ids = [];
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const id = await vectorDB.addDocument(chunk.text, {
...metadata,
chunkIndex: i,
totalChunks: chunks.length,
originalLength: text.length
});
ids.push(id);
}
return {
documentId: ids[0], // 返回第一个块的 ID 作为文档 ID
chunkIds: ids,
totalChunks: chunks.length
};
} catch (error) {
logger.error('处理文档失败:', error);
throw error;
}
}
/**
* 处理文件(支持文本文件)
*/
async processFile(filePath, metadata = {}) {
try {
const fs = await import('fs/promises');
const text = await fs.readFile(filePath, 'utf-8');
return await this.processDocument(text, {
...metadata,
source: filePath,
fileName: path.basename(filePath)
});
} catch (error) {
logger.error('处理文件失败:', error);
throw error;
}
}
/**
* 搜索相关文档
*/
async searchRelevantDocuments(query, topK = 5) {
try {
const results = await vectorDB.search(query, topK);
// 合并同一文档的多个块
const documentMap = new Map();
for (const result of results) {
const docId = result.metadata.source || result.id;
if (!documentMap.has(docId)) {
documentMap.set(docId, {
id: docId,
text: result.text,
similarity: result.similarity,
metadata: result.metadata,
chunks: [result]
});
} else {
const doc = documentMap.get(docId);
doc.chunks.push(result);
// 使用最高相似度
if (result.similarity > doc.similarity) {
doc.similarity = result.similarity;
}
}
}
return Array.from(documentMap.values())
.sort((a, b) => b.similarity - a.similarity);
} catch (error) {
logger.error('搜索文档失败:', error);
throw error;
}
}
}
export const documentProcessor = new DocumentProcessor();
作业5:创建向量数据库 API
src/routes/vector-db.js:
import express from 'express';
import { vectorDB } from '../services/vector-db.js';
import { documentProcessor } from '../services/document-processor.js';
import { logger } from '../utils/logger.js';
export const vectorDBRouter = express.Router();
// POST /api/vector-db/add - 添加文档
vectorDBRouter.post('/add', async (req, res) => {
try {
const { text, metadata = {} } = req.body;
if (!text) {
return res.status(400).json({
success: false,
error: '文本内容不能为空'
});
}
const id = await vectorDB.addDocument(text, metadata);
res.json({
success: true,
data: { id }
});
} catch (error) {
logger.error('添加文档错误:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// POST /api/vector-db/process - 处理文档(分块)
vectorDBRouter.post('/process', async (req, res) => {
try {
const { text, metadata = {} } = req.body;
if (!text) {
return res.status(400).json({
success: false,
error: '文本内容不能为空'
});
}
const result = await documentProcessor.processDocument(text, metadata);
res.json({
success: true,
data: result
});
} catch (error) {
logger.error('处理文档错误:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// POST /api/vector-db/search - 搜索
vectorDBRouter.post('/search', async (req, res) => {
try {
const { query, topK = 5 } = req.body;
if (!query) {
return res.status(400).json({
success: false,
error: '查询内容不能为空'
});
}
const results = await vectorDB.search(query, topK);
res.json({
success: true,
data: results
});
} catch (error) {
logger.error('搜索错误:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// GET /api/vector-db/stats - 获取统计信息
vectorDBRouter.get('/stats', (req, res) => {
const stats = vectorDB.getStats();
res.json({
success: true,
data: stats
});
});
// DELETE /api/vector-db/:id - 删除文档
vectorDBRouter.delete('/:id', (req, res) => {
const { id } = req.params;
const deleted = vectorDB.deleteDocument(id);
res.json({
success: deleted,
message: deleted ? '文档已删除' : '文档不存在'
});
});
// POST /api/vector-db/clear - 清空数据库
vectorDBRouter.post('/clear', (req, res) => {
vectorDB.clear();
res.json({
success: true,
message: '数据库已清空'
});
});
作业6:测试向量数据库
test-vector-db.js:
import { vectorDB } from './src/services/vector-db.js';
import { documentProcessor } from './src/services/document-processor.js';
async function testVectorDB() {
console.log('=== 测试向量数据库 ===\n');
// 1. 添加文档
console.log('1. 添加文档...');
const doc1 = await vectorDB.addDocument(
'前端开发是构建用户界面的技术,主要使用 HTML、CSS 和 JavaScript。',
{ category: 'frontend' }
);
console.log('文档1 ID:', doc1);
const doc2 = await vectorDB.addDocument(
'后端开发是服务器端编程,处理业务逻辑和数据库操作。',
{ category: 'backend' }
);
console.log('文档2 ID:', doc2);
const doc3 = await vectorDB.addDocument(
'JavaScript 是一种动态编程语言,用于前端和后端开发。',
{ category: 'javascript' }
);
console.log('文档3 ID:', doc3);
// 2. 搜索
console.log('\n2. 搜索 "前端技术"...');
const results1 = await vectorDB.search('前端技术', 3);
results1.forEach((result, index) => {
console.log(`${index + 1}. 相似度: ${result.similarity.toFixed(4)}`);
console.log(` 文本: ${result.text.substring(0, 50)}...`);
});
// 3. 处理长文档
console.log('\n3. 处理长文档...');
const longText = `
人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。
机器学习是 AI 的一个子集,它使计算机能够从数据中学习,而无需明确编程。
深度学习是机器学习的一个子集,使用神经网络来模拟人脑的工作方式。
自然语言处理(NLP)是 AI 的一个分支,专注于计算机与人类语言之间的交互。
计算机视觉是 AI 的另一个分支,使计算机能够理解和解释视觉信息。
`;
const processed = await documentProcessor.processDocument(
longText,
{ title: 'AI 基础知识' }
);
console.log(`文档ID: ${processed.documentId}`);
console.log(`分块数: ${processed.totalChunks}`);
// 4. 搜索长文档
console.log('\n4. 搜索 "机器学习"...');
const results2 = await documentProcessor.searchRelevantDocuments('机器学习', 2);
results2.forEach((result, index) => {
console.log(`${index + 1}. 相似度: ${result.similarity.toFixed(4)}`);
console.log(` 文本: ${result.text.substring(0, 100)}...`);
});
// 5. 统计信息
console.log('\n5. 统计信息:');
const stats = vectorDB.getStats();
console.log(stats);
}
testVectorDB().catch(console.error);
遇到的问题
问题1:Embedding 生成慢
解决方案:
// 批量生成,减少 API 调用
const embeddings = await generateEmbeddings([text1, text2, text3]);
// 缓存已生成的 Embedding
const embeddingCache = new Map();
if (embeddingCache.has(text)) {
return embeddingCache.get(text);
}
问题2:向量维度不匹配
解决方案:
// 检查维度
if (vecA.length !== vecB.length) {
throw new Error(`维度不匹配: ${vecA.length} vs ${vecB.length}`);
}
问题3:内存占用过大
解决方案:
// 使用文件存储
await vectorDB.saveToFile('./data/vectors.json');
// 定期清理旧数据
if (this.vectors.length > 10000) {
this.vectors = this.vectors.slice(-5000);
}
学习总结
今日收获
- ✅ 理解向量和 Embedding 概念
- ✅ 掌握文档向量化
- ✅ 实现相似度搜索
- ✅ 构建简单向量数据库
- ✅ 为 RAG 应用打基础
关键知识点
- Embedding 是文本的数学表示,保留语义信息
- 相似度搜索,找到最相关的文档
- 文本分块,处理长文档
- 向量数据库,存储和检索向量
- RAG 基础,检索增强生成的核心
技术要点
- 余弦相似度:常用相似度计算方法
- 文本分块:长文档需要分块处理
- 批量处理:提高效率
- 持久化存储:保存向量数据
明日计划
明天将学习:
期待明天的学习! 🚀
参考资源
代码仓库
项目已更新:
- ✅ Embedding 生成服务
- ✅ 向量相似度计算
- ✅ 简单向量数据库
- ✅ 文档处理服务
- ✅ API 接口
GitHub 提交: Day 08 - 向量数据库基础
标签: #AI学习 #向量数据库 #Embedding #RAG #学习笔记
写在最后
今天学习了向量数据库的基础知识,这是 RAG 应用的核心技术。
通过向量化文档和相似度搜索,可以让 AI 基于特定文档回答问题。
明天将学习完整的 RAG 实现,打造智能文档助手!
继续加油! 💪
快速检查清单
完成这些,第八天就达标了! ✅

浙公网安备 33010602011771号