Retrieval Augmented Generation(检索增强生成) 从一个小白的角度,从理论出发,一起构建一个MVP RAG 系统,最近以实战结尾,让我们一起踏进 RAG 的大门。
简易版:
向量是 RAG 中数据召回的基础,我们以狗🐶举例,来一起认识下,什么是向量?
欧几里得距离算法的优点是可以反映向量的绝对距离,适用于需要考虑向量长度的相似性计算。例如推荐系统中,需要根据用户的历史行为来推荐相似的商品,这时就需要考虑用户的历史行为的数量,而不仅仅是用户的历史行为的相似度。
余弦相似度对向量的长度不敏感,只关注向量的方向,因此适用于高维向量的相似性计算。 例如语义搜索和文档分类。
点积相似度算法的优点在于它简单易懂,计算速度快,并且兼顾了向量的长度和方向。它适用于许多实际场景,例如图像识别、语义搜索和文档分类等。但点积相似度算法对向量的长度敏感,因此在计算高维向量的相似性时可能会出现问题。
在大多数语义搜索和文档分类任务中,余弦相似度通常是更好的选择。
import { Configuration, OpenAIApi } from "openai";
import * as cosineSimilarity from 'cosine-similarity';
// 配置OpenAI API
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
// 使用 OpenAI 的 text-embedding-3-large 模型来将文本转为向量
async function getEmbedding(text: string): Promise<number[]> {
const response = await openai.createEmbedding({
model: "text-embedding-3-large",
input: text,
});
const embedding = response.data.data[0].embedding;
return embedding;
}
// 计算两个向量的余弦相似度
function calculateCosineSimilarity(vec1: number[], vec2: number[]): number {
return cosineSimilarity(vec1, vec2);
}
// 将多个文本转换为向量并存储
async function embedTexts(texts: string[]): Promise<number[][]> {
const embeddings = [];
for (const text of texts) {
const embedding = await getEmbedding(text);
embeddings.push(embedding);
}
return embeddings;
}
// 获取与问题最相似的前10条文本资料
async function findTopMatches(question: string, texts: string[], embeddings: number[][]): Promise<string[]> {
const questionEmbedding = await getEmbedding(question);
const similarities = embeddings.map((embedding, index) => ({
text: texts[index],
similarity: calculateCosineSimilarity(questionEmbedding, embedding),
}));
// 按相似度排序并返回前10个
similarities.sort((a, b) => b.similarity - a.similarity);
return similarities.slice(0, 10).map(item => item.text);
}
// 主逻辑:将用户问题与文本资料匹配
async function handleUserQuestion(question: string, texts: string[]): Promise<string[]> {
const embeddings = await embedTexts(texts);
return await findTopMatches(question, texts, embeddings);
}
// 示例使用
(async () => {
const texts = [
"这是关于人工智能的资料。",
"这是关于机器学习的资料。",
"这是关于自然语言处理的资料。",
];
const question = "什么是人工智能?";
const matches = await handleUserQuestion(question, texts);
console.log("匹配到的文本资料:", matches);
})();
这样一个最小的 demo 就跑通了,再实际生产中还要配合数据库存储。作为 jser,推荐一些 js 相关技术栈:
claude 上下文检索融合方案 「embedding + 词汇匹配」召回内容后,重新排名去重。以提高召回准确率。具体操作如下:
不同于向量 Embedding 的形式,Graph 对于结构复杂、经常改动的数据更友好
推荐阅读以下资料,都是系统性的讲解