1. 什么是RAG?

RAG(Retrieval-Augmented Generation):检索增强生成,是给 LLM 喂 “专属资料” 的一套标准化流程。

核心三要素:

  1. 检索(Retrieval):从海量资料里找和用户问题最相关的内容
  2. 增强(Augmented ):把找到的内容拼接上提示词喂给 LLM
  3. 生成(Generation):让LLM基于这个增强后的提示词给出答案

大白话: 以前LLM是“闭卷考试”,RAG让它变成了“开卷考试”——允许它先翻书,再答题。


2. 为什么需要RAG

大语言模型(LLM)很强大,但它们有一个致命弱点:知识是静态的

  • 训练时学习:把海量数据喂给模型,让它记住,但是缺点是:它不知道训练后发生的事,成本高。
  • 推理时学习:在回答问题时临时“查资料”。遇到没见过的专业领域,会“编造”答案(这叫幻觉)。

试想:现在公司有一个一百万页的PDF文档,里面的数据是绝密的,此时如果一次性丢给LLM会咋样?

  • Token消耗量爆炸!!!
  • 信息泄露!!!

RAG 的核心价值在于:让大模型可以访问私有化、实时化数据,同时减少Token消耗


3. 核心组件

  1. 嵌入模型(Embedding Model):把文字转换成一串数字(向量)
  2. 向量数据库:存储所有文档片段的向量,并支持快速相似度搜索
  3. 大语言模型(gpt、豆包…):根据增强后的提示词生成答案

4. RAG的工作原理

在这里插入图片描述

【前置知识】

  1. 什么是向量:就是一串数字,例如:[0.12, -0.45, 0.67, …]
  2. 向量化:将文字转化成向量,例如:你好! → [0.11, 0.33, …]
  3. 意义:计算机无法直接比较两句话的相似度,但可以比较数字。向量让语义变成了可计算的数学对象。
  4. 计算向量相似度的方法:余弦相似度(最常用)、欧式距离点积

【完整步骤】:

  1. 文档分片:把500页的PDF切成小段(比如按段落或500字一段)。因为大模型一次能处理的内容有限,分片后检索更精准。
  2. 文档向量化:每个小段通过嵌入模型转成一串数字(向量),存进向量数据库,方便后续搜索。
  3. 用户提问向量化:用户输入的问题也用同一个嵌入模型转成向量。
  4. 相似度计算:用余弦相似度公式算出【问题向量】和【每个文档向量】的相似度。
  5. 检索 Top-K:把相似度从高到低排序,挑出最相关的 K 个文档片段,这些就是给大模型参考的资料。
  6. 构建提示词:把挑出来的文档片段和用户问题拼成一个完整提示词
  7. 生成答案:把提示词发给大模型(如DeepSeek、GPT)。模型根据资料组织语言,生成自然、准确的回答。
  8. 返回答案:把最终答案展示给用户。整个过程用户感知不到背后检索,只觉得模型“懂”他的问题。

5. 手搓极简RAG系统

5.1文件结构:

文件结构目录

5.2 准备工作

  1. 初始化项目

    npm init
    
  2. 我们需要一个轻量级 embedding 模型库 @xenova/transformers

    npm install @xenova/transformers
    
  3. 准备知识库

    苹果是一种水果,富含维生素C。
    香蕉是一种热带水果,富含钾元素。
    北京是中国的首都,有故宫和长城。
    Python是一种编程语言,适合数据分析。
    JavaScript是一种前端编程语言。
    MCP是模型上下文协议,让LLM具备调用外部工具的能力。
    RAG是检索增强生成技术,让LLM能够查阅外部知识库。
    

5.3 工具函数

/**
 * ReadFile.js   -- 读取知识库文件
 * 这里只是简单的把文件内容读出来并直接返回去,一般来说正常情况下还需要对文件内容进行分片处理
 */
import fs from 'fs';
export function ReadFile() {
  const filePath = '../data/knowledge.txt';
  const content = fs.readFileSync(filePath, 'utf-8');
  const documents = content.split('\n').filter(line => line.trim());
  return documents;
}
/**
 * CalcCosinSimilarity.js  -- 余弦相似度计算函数。
 */
export function cosineSimilarity(a, b) {
  let dot = 0, normA = 0, 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));
}

5.4 RAG

  1. Embedding:加载嵌入模型,提供向量化函数

    import { pipeline } from '@xenova/transformers';
    let embedder = null;
    
    export async function initEmbedder() {
      if (!embedder) embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
      return embedder;
    }
    
    export async function getEmbedding(text) {
      const model = await initEmbedder();
      const result = await model(text, { pooling: 'mean', normalize: true });
      return Array.from(result.data);
    }
    
  2. Retrieve: 检索前 k 个最相似的结果

    import { getEmbedding } from './Embedding.js';
    import { cosineSimilarity } from '../utils/CalcCosinSimilarity.js';
    
    export async function retrieve(query, documents, docVectors, topK = 2) {
      const queryVector = await getEmbedding(query);
      const similarities = docVectors.map((vec, idx) => ({
        text: documents[idx],
        score: cosineSimilarity(queryVector, vec)
      }));
      similarities.sort((a, b) => b.score - a.score);
      return similarities.slice(0, topK);
    }
    
  3. Generate:拼接提示词,增强检索

    export function generatePrompt(question, context) {
      const contextText = context.map(doc => doc.text).join('\n');
      return `
        基于以下参考资料回答问题:
       【参考资料】
        ${contextText}
    
       【问题】
        ${question}
    
       【回答】
        ....
      `;
    }
    

5.5 极简 RAG系统

  • 加载Embedding模型:使用 Xenova/all-MiniLM-L6-v2(一个轻量级的嵌入模型)
  • 用户提问Question
  • 对Question向量化:将用户的问题文本转换为数字向量(embedding)
  • 构建向量数据库:读取知识库文件,并将知识库中的所有文档也转换为向量
  • 检索:计算问题向量与每个文档向量的相似度,找出前三条最相似的向量
  • 增强:用找到的三条最相似的文档,拼接出完整的提示词
  • 生成:将提示词交给大模型,生成最终答案
import readline from 'readline';
import { ReadFile } from '../utils/ReadFile.js';
import { getEmbedding, initEmbedder } from './Embedding.js';
import { retrieve } from './Retrieve.js';
import { generatePrompt } from './Generate.js';

// 延迟函数,模拟处理过程
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function main() {
  // 1. 初始化嵌入模型
  console.log('\n[步骤 1/7] 初始化嵌入模型...');
  await delay(500);
  await initEmbedder();
  await delay(300);

  // 2. 用户输入问题
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  const question = await new Promise((resolve) => {
    rl.question('[步骤 2/7] 请输入您的问题: ', (answer) => {
      rl.close();
      resolve(answer);
    });
  });
  await delay(500);

  // 3. 计算问题向量
  console.log('[步骤 3/7] 将问题转换为向量...');
  await delay(800);
  const queryVector = await getEmbedding(question);
  console.log(`✓ 向量维度: ${queryVector.length}`);
  console.log(`✓ 向量值: [${queryVector.slice(0, 5).map(v => v.toFixed(3)).join(', ')}...]\n\n`);
  await delay(500);

  // 4. 加载知识库并构建向量数据库
  console.log('[步骤 4/7] 构建向量数据库...');
  await delay(500);
  const documents = ReadFile();
  console.log('');
  
  // 构建向量数据库
  const vectorDatabase = [];
  const docVectors = [];
  
  for (let i = 0; i < documents.length; i++) {
    const doc = documents[i];
    const vec = await getEmbedding(doc);
    docVectors.push(vec);
    
    // 添加到向量数据库
    vectorDatabase.push({
      doc: doc,
      vec: vec.slice(0, 8).map(v => parseFloat(v.toFixed(3))) // 只保留前8维用于展示
    });
    
    await delay(300);
  }
  
  // 打印整个向量数据库
  console.log('📊 向量数据库构建完成:\n');
  vectorDatabase.forEach((item, index) => {
    const vecStr = `[${item.vec.join(', ')}...]`;
    console.log(`doc: ${item.doc}   vec: ${vecStr}\n`);
  });
  console.log('');
  await delay(500);

  // 5. 语义检索
  console.log('[步骤 5/7] 语义检索最相关的文档...');
  await delay(800);
  const topDocs = await retrieve(question, documents, docVectors, 3);
  topDocs.forEach((doc, i) => {
    console.log(`  ${i + 1}. [相似度 ${(doc.score * 100).toFixed(1)}%] ${doc.text}`);
  });
  console.log('');
  await delay(800);

  // 6. 拼接提示词
  console.log('\n[步骤 6/7] 生成提示词...');
  await delay(600);
  const prompt = generatePrompt(question, topDocs);
  console.log('─'.repeat(30),'prompt', '─'.repeat(30));
  console.log(prompt);
  console.log('─'.repeat(66));
  console.log('');
  await delay(500);

  // 7. 生成回答
  console.log('[步骤 7/7] GPT模型回答中...');
  await delay(800);
  
  // 模拟大模型回答,这里直接返回相似度最高的那句话
  const answer = topDocs[0].text;
  const similarity = (topDocs[0].score * 100).toFixed(1);
  console.log(`\n💡 最终回答:${answer},相似度:${similarity}%`);
}

main();

【控制台输出】
在这里插入图片描述


6. RAG的常见优化点

虽然上面的流程能跑通,但实际生产中有很多优化手段:

问题优化方法
文档太长合理分块(chunking),比如按段落、按500字切分
检索不准改用多路召回(关键词+向量),或使用重排模型(reranker)
提示词太乱对检索结果做结构化处理,控制长度
答案无来源在答案中标注引用文档编号
实时性要求高缓存常见问题,使用更快的嵌入模型

7. 总结

RAG的本质,就是给大语言模型配一个外部知识库,让它不再依赖训练时的记忆,而是实时查阅资料后作答。现在市面上所有主流AI应用(如企业知识库、智能客服、AI搜索)背后几乎都有RAG的影子。

  • 核心流程:检索 → 增强 → 生成
  • 核心组件:嵌入模型 + 向量数据库 + LLM
  • 核心价值:Token消耗减少、实时更新、可解释

RAG的架构深处,藏着比技术更深的生存智慧——检索、增强、生成,这何尝不是我们面对人生时的那套隐秘算法?

检索不是盲目翻书,而是灵魂在记忆碎片中的自我辨认:在万千回响中找到最贴近当下的那一页,知道该翻开哪一本,更知道该合上哪一本。增强不是堆砌,而是认知边界的温柔拓荒——把外来的智慧拼接进既有框架,不吞没、不僭越,只为新的理解留出恰好容身的缝隙。

而生成,是所有准备之后的克制:引用而不抄袭,借鉴而不失独立。我们穷尽一生检索前人的智慧、增强自己的认知,不过是为了在开口的那一刻,说出一句既带着世界的温度、又印着自己指纹的话。


Logo

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

更多推荐