5分钟理解 RAG:从零开始构建你的第一个检索增强生成系统

用 130 行代码,带你快速理解 RAG 的核心原理


前言:什么是 RAG?

你有没有遇到过这种情况:问 ChatGPT 一些新技术或最新动态时,它回答得头头是道,但仔细一看全是错的?

这就是大语言模型的"幻觉"问题——模型可能编造不存在的信息来回答问题。

RAG(Retrieval Augmented Generation,检索增强生成) 提供了一个解决方案:让 AI 先检索相关文档,再基于这些准确的信息回答问题。在较早之前LLM模型还没有大规模应用时,一些研究,如Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks 就通过这种方案解决模型生成文本偏离实际较大的问题。

打个比方:

  • 普通 LLM:闭卷考试,只能靠记忆回答,容易出错
  • RAG:开卷考试,可以查阅资料,答案更可靠

RAG 的核心就是两件事:

  1. 从知识库中检索相关文档
  2. 基于检索到的文档生成答案

为什么需要 RAG?

传统大语言模型面临几个实际问题:

问题 传统 LLM RAG
知识过时 无法获取训练后的新知识 更新知识库即可
幻觉问题 可能编造信息 基于文档事实回答
私有数据 无法访问企业内部数据 可以接入内部文档
可追溯性 无法说明答案来源 可以展示参考文档

本文目标

通过这篇文章,你将:

  • 理解 RAG 的完整工作流程
  • 掌握文本嵌入和向量搜索的基本概念
  • 动手实现一个可运行的 RAG 系统
  • 为后续深入学习打下基础

不用担心,只有 130 行代码,我们一步步来。


项目准备:极简 RAG Demo

为了帮助你快速理解 RAG,我们准备了一个超简单的 Demo 项目:

项目结构

demo1/
├── docs/
│   └── knowledge.txt       # 知识库文档
├── rag_demo.py            # 主程序(所有逻辑,约130行)
├── requirements.txt       # 依赖包列表
└── README.md             # 项目说明

设计理念

这个 Demo 遵循以下设计原则:

  • 极简:所有逻辑在一个文件中,代码约 130 行
  • 清晰:每一步都有详细注释
  • 可运行:复制代码、配置环境变量即可运行
  • 教学友好:适合 5 分钟快速理解

技术栈

组件 技术选择 原因
编程语言 Python 3.10+ 简单易学,生态丰富
嵌入模型 OpenAI API (BAAI/bge-large-zh-v1.5) 易于调用
向量计算 numpy 高效的数值计算
LLM OpenAI API (任意 稳定可靠,易于调用

RAG 核心流程详解

完整流程图

用户输入问题

问题转为向量
Embedding

向量相似度搜索
Cosine Similarity

找到最相关文本
Top-K Retrieval

构造上下文
Build Context

调用 LLM
Generate Answer

返回答案

流程步骤详解

我们将 RAG 流程分为 7 个步骤,每个步骤都有明确的目标和实现方法。


步骤一:准备知识库

概念解释

知识库是 RAG 系统的基础,包含了我们要检索的所有文档。在本 Demo 中,知识库是一个简单的文本文件。

实现代码

def load_knowledge(filepath):
    """
    读取知识库文件

    Args:
        filepath: 知识库文件路径

    Returns:
        list: 文档块列表
    """
    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()

    # 按空行切分成段落,每个段落作为一个文档块
    chunks = [chunk.strip() for chunk in content.split('\n\n') if chunk.strip()]

    return chunks

实践示例

我们的知识库文件 docs/knowledge.txt 包含:

RabbitMQ 是一个消息队列系统。
它用于系统之间的异步通信。
RabbitMQ 支持 AMQP 协议。
RabbitMQ 支持多种消息模型,包括点对点和发布订阅。
RabbitMQ 提供消息持久化和确认机制。

Kafka 是一个分布式流处理平台。
它适合处理高吞吐量的数据流。
Kafka 基于日志存储模型。
Kafka 支持多生产者和多消费者。

RAG 是检索增强生成技术。
它结合信息检索和大语言模型生成。
RAG 可以减少模型幻觉。
RAG 提供更准确的答案。

文档切分结果

按空行切分后,得到 3 个文档块:

块编号 内容概要
1 RabbitMQ 相关知识
2 Kafka 相关知识
3 RAG 相关知识

为什么要切分?

  • 精细粒度:更小的片段更容易精确匹配
  • 上下文控制:避免一次性输入过多内容
  • 灵活检索:可以组合多个相关片段

步骤二:文本嵌入(Embedding)

概念解释

计算机无法直接理解文本,只能处理数字。嵌入(Embedding) 就是将文本转换为数字向量的技术。

什么是向量?

向量是一个有序的数字列表,例如:

"RabbitMQ 是消息队列"[0.23, -0.15, 0.34, 0.12, -0.08, ...]
"Kafka 是流处理平台"[0.18, -0.22, 0.41, 0.19, -0.05, ...]

这个向量有成百上千个数字,每个数字都代表了文本的某种语义特征。

为什么向量能表示语义?

神经网络通过在海量文本上训练,学会了将语义相似的文本映射到相似的向量空间位置:

向量空间(简化为2维示意)

距离近
语义相关

距离近
语义相关

距离远
语义不相关

消息队列
[0.9, 0.2]

异步通信
[0.85, 0.25]

流处理
[0.2, 0.8]

分布式
[0.25, 0.85]

实现代码

def embed(text, client):
    """将文本转换为向量"""
    # return model.encode(text, convert_to_numpy=True)
    """调用 OpenAI Embedding API"""
    response = client.embeddings.create(
        model="BAAI/bge-large-zh-v1.5",
        input=text
    )
    return np.array(response.data[0].embedding)

实践示例

# 加载预训练模型
api_key = os.getenv("OPENAI_API_KEY")
base_url = os.getenv("BASE_URL")
client = OpenAI(api_key=api_key, base_url=base_url)

# 文本嵌入
text = "RabbitMQ 是一个消息队列系统"
vector = embed(text, client)

print(f"文本: {text}")
print(f"向量维度: {vector.shape}")  # (1024,)
print(f"向量前5个值: {vector[:5]}")
# 输出: [ 0.02450614 -0.00108401 -0.00753182  0.03007796 -0.00668125]

步骤三:计算相似度

概念解释

有了向量后,如何判断两个文本是否相似?答案是余弦相似度(Cosine Similarity)

余弦相似度公式

Cosine Similarity = A ⋅ B ∥ A ∥ × ∥ B ∥ \text{Cosine Similarity} = \frac{A \cdot B}{\|A\| \times \|B\|} Cosine Similarity=A×BAB

其中:

  • A ⋅ B A \cdot B AB 是两个向量的点积
  • ∥ A ∥ \|A\| A 是向量 A 的范数(长度)
  • ∥ B ∥ \|B\| B 是向量 B 的范数
相似度值的含义
相似度 含义
1.0 完全相同
0.7-1.0 高度相关
0.4-0.7 中度相关
0.1-0.4 低度相关
0.0 或负值 基本无关

实现代码

import numpy as np

def cosine_similarity(a, b):
    """
    计算两个向量的余弦相似度

    Args:
        a: 向量 A
        b: 向量 B

    Returns:
        float: 相似度分数,范围 [-1, 1]
    """
    # 计算点积
    dot_product = np.dot(a, b)

    # 计算向量范数
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)

    # 计算余弦相似度
    return dot_product / (norm_a * norm_b)

实践示例

# 示例向量
v1 = np.array([0.9, 0.2])  # "消息队列"
v2 = np.array([0.85, 0.25])  # "异步通信"
v3 = np.array([0.2, 0.8])   # "流处理"

# 计算相似度
sim_12 = cosine_similarity(v1, v2)  # 0.9989(高度相关)
sim_13 = cosine_similarity(v1, v3)  # 0.5664(中度相关)

print(f"消息队列 vs 异步通信: {sim_12:.4f}")
print(f"消息队列 vs 流处理: {sim_13:.4f}")

步骤四:向量搜索

概念解释

向量搜索的目标是:在知识库中找到与用户问题最相关的文档

搜索流程:

用户问题

问题向量化

与所有文档向量计算相似度

排序

返回Top-K最相关文档

实现代码

def search(query_vector, doc_vectors, docs, top_k=1):
    """
    搜索最相关的文档

    Args:
        query_vector: 问题的向量表示
        doc_vectors: 文档向量列表
        docs: 文档文本列表
        top_k: 返回最相关的前 k 个文档

    Returns:
        list: 最相关的文档列表
    """
    # 计算问题与每个文档的相似度
    similarities = [cosine_similarity(query_vector, v) for v in doc_vectors]

    # 获取相似度最高的 top_k 个文档索引
    # argsort 按升序排序,所以取后 top_k 个并反转
    top_indices = np.argsort(similarities)[-top_k:][::-1]

    # 返回对应的文档
    return [docs[i] for i in top_indices]

实践示例

# 假设有 3 个文档及其向量
docs = [
    "RabbitMQ 是消息队列系统",
    "Kafka 是流处理平台",
    "RAG 是检索增强技术"
]

doc_vectors = [embed(doc, model) for doc in docs]

# 用户问题
question = "什么是消息队列?"
query_vector = embed(question, model)

# 搜索最相关的文档
top_docs = search(query_vector, doc_vectors, docs, top_k=2)

print("最相关的文档:")
for i, doc in enumerate(top_docs, 1):
    print(f"{i}. {doc}")

# 输出:
# 最相关的文档:
# 1. RabbitMQ 是消息队列系统
# 2. Kafka 是流处理平台

Top-K 的选择

Top-K 值 适用场景
1 简单事实性问题
2-3 一般问答场景
5+ 复杂分析类问题

本 Demo 使用 top_k=2,平衡了相关性和上下文完整性。


步骤五:构造上下文

概念解释

上下文(Context)是传递给 LLM 的文档信息,它告诉模型"请基于这些信息回答问题"。

实现代码

# 检索到的文档
top_docs = search(query_vector, doc_vectors, docs, top_k=2)

# 构造上下文
context = "\n\n".join([
    f"Document {i+1}: {doc}"
    for i, doc in enumerate(top_docs)
])

实践示例

# 检索到的文档
top_docs = [
    "RabbitMQ 是一个消息队列系统。",
    "它用于系统之间的异步通信。"
]

# 构造的上下文
context = """Document 1: RabbitMQ 是一个消息队列系统。

Document 2: 它用于系统之间的异步通信。"""

提示词咋写才好

好的提示词要做到:

  • 把文档分清楚(Document 1, Document 2…)
  • 别太长,不然 AI 看不完
  • 格式清晰,AI 好理解

第六步:让 AI 生成答案

找到相关文档后,让大模型(LLM)根据这些资料生成答案。关键是提示词要写好。

代码示例

import os
from openai import OpenAI

# 初始化 OpenAI 客户端
api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)

# 让 AI 生成答案
    response = client.chat.completions.create(
        model="deepseek-ai/DeepSeek-V3",
        messages=[
            {
                "role": "system",
                "content": """你是一个基于知识库的问答助手。

        回答规则:
        1. 只能根据提供的上下文回答问题
        2. 如果上下文中没有答案,请回答:无法从提供的资料中找到答案
        3. 不要编造事实
        4. 回答尽量简洁清晰
        """
            },
            {
                "role": "user",
                "content": f"""知识库内容:

        {context}

        用户问题:
        {question}

        请根据知识库内容回答问题。"""
            }
        ],
        temperature=0.2,
    )

    answer = response.choices[0].message.content

提示词是啥

系统提示词(System Prompt)
你是一个基于知识库的问答助手。

        回答规则:
        1. 只能根据提供的上下文回答问题
        2. 如果上下文中没有答案,请回答:无法从提供的资料中找到答案
        3. 不要编造事实
        4. 回答尽量简洁清晰

这告诉 AI:

  • 你的角色是啥
  • 要根据给的资料回答
  • 别瞎编
用户提示词(User Prompt)
知识库内容:

        {context}

        用户问题:
        {question}

        请根据知识库内容回答问题。

关键是:

  • Context 部分要清楚
  • Question 部分要明确
  • 强调"根据资料回答"

Temperature 是个啥

值大小 效果 啥时候用
0.0-0.3 稳定,不变来变去 问答类
0.4-0.6 刚好 一般对话
0.7-1.0+ 随机,爱发挥 写东西

咱们用 0.2,因为:

  • RAG 要按事实回答
  • 不想让 AI 乱发挥
  • 答案要稳定点

完整代码解析

主程序流程

"""
极简 RAG Demo - 5分钟理解RAG核心流程
"""
import os
import numpy as np
from openai import OpenAI


def load_knowledge(filepath):
    """读取知识库文件"""
    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()
    # 按空行切分成段落
    chunks = [chunk.strip() for chunk in content.split('\n\n') if chunk.strip()]
    return chunks


def embed(text, client):
    """将文本转换为向量"""
    # return model.encode(text, convert_to_numpy=True)
    """调用 OpenAI Embedding API"""
    response = client.embeddings.create(
        model="BAAI/bge-large-zh-v1.5",
        input=text
    )
    return np.array(response.data[0].embedding)


def cosine_similarity(a, b):
    """计算余弦相似度: (A·B) / (||A|| * ||B||)"""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))


def search(query_vector, doc_vectors, docs, top_k=1):
    """搜索最相关的文档"""
    similarities = [cosine_similarity(query_vector, v) for v in doc_vectors]
    # 获取相似度最高的 top_k 个文档索引
    top_indices = np.argsort(similarities)[-top_k:][::-1]
    return [docs[i] for i in top_indices]


def main():
    print("=" * 60)
    print("极简 RAG Demo - 检索增强生成")
    print("=" * 60)
    # 初始化 环境变量
    print("\n[1/6] 初始化 环境变量...")
    api_key = os.getenv("OPENAI_API_KEY")
    base_url = os.getenv("BASE_URL")
    if not api_key:
        print("警告: 未设置 OPENAI_API_KEY 环境变量")
        return
    if not base_url:
        print("警告: 未设置 BASE_URL 环境变量")
        return
    print("配置初始化完成")

    # 初始化 LLM 客户端
    print("\n[2/6] 初始化 LLM 客户端...")
    client = OpenAI(api_key=api_key, base_url=base_url)
    print("LLM 客户端初始化完成")

    # 读取知识库
    print("\n[3/6] 读取知识库...")
    docs_path = os.path.join(os.path.dirname(__file__), "docs", "knowledge.txt")
    docs = load_knowledge(docs_path)
    print(f"已加载 {len(docs)} 个文档块")

    # 文档向量化
    print("\n[4/6] 文档向量化...")
    doc_vectors = [embed(doc, client) for doc in docs]
    print(f"已生成 {len(doc_vectors)} 个向量,维度: {doc_vectors[0].shape[0]}")



    #  用户输入问题
    print("\n[5/6] 等待用户输入...")
    print("=" * 60)
    question = input("\n请输入你的问题(输入 'q' 退出): ")

    if question.lower() == 'q':
        print("\n再见!")
        return

    # 问题向量化
    print("\n[6/6] 执行 RAG 查询...")
    query_vector = embed(question, client)

    # 向量相似度搜索
    top_docs = search(query_vector, doc_vectors, docs, top_k=2)

    # 构造上下文
    context = "\n\n".join([f"Document {i+1}: {doc}" for i, doc in enumerate(top_docs)])

    # 调用 LLM 生成答案
    print("\n检索到的文档:")
    print("-" * 60)
    for i, doc in enumerate(top_docs, 1):
        print(f"\n[{i}] {doc}")

    print("\n" + "-" * 60)
    print("正在生成答案...\n")

    response = client.chat.completions.create(
        model="deepseek-ai/DeepSeek-V3",
        messages=[
            {
                "role": "system",
                "content": """你是一个基于知识库的问答助手。

        回答规则:
        1. 只能根据提供的上下文回答问题
        2. 如果上下文中没有答案,请回答:无法从提供的资料中找到答案
        3. 不要编造事实
        4. 回答尽量简洁清晰
        """
            },
            {
                "role": "user",
                "content": f"""知识库内容:

        {context}

        用户问题:
        {question}

        请根据知识库内容回答问题。"""
            }
        ],
        temperature=0.2,
    )

    answer = response.choices[0].message.content

    # 打印答案
    print("-" * 60)
    print("生成的答案:")
    print("-" * 60)
    print(f"\n{answer}")
    print("\n" + "=" * 60)


if __name__ == "__main__":
    main()


运行示例和结果展示

安装依赖

pip install -r requirements.txt

设置 API Key

# Windows (PowerShell)
$env:OPENAI_API_KEY='sk-your-api-key-here'
$env:BASE_URL='your-base-url-here'



# Linux / Mac
export OPENAI_API_KEY='sk-your-api-key-here'
export BASE_URL='your-base-url-here'

运行程序

python rag_demo.py

运行示例 :询问 RAG

============================================================
极简 RAG Demo - 检索增强生成
============================================================

[1/6] 初始化 环境变量...
配置初始化完成

[2/6] 初始化 LLM 客户端...
LLM 客户端初始化完成

[3/6] 读取知识库...
已加载 3 个文档块

[4/6] 文档向量化...
已生成 3 个向量,维度: 1024

[5/6] 等待用户输入...
============================================================

请输入你的问题(输入 'q' 退出): 什么是RAG

[6/6] 执行 RAG 查询...

检索到的文档:
------------------------------------------------------------

[1] RAG 是检索增强生成技术。
它结合信息检索和大语言模型生成。
RAG 可以减少模型幻觉。
RAG 提供更准确的答案。

[2] RabbitMQ 是一个消息队列系统。
它用于系统之间的异步通信。
RabbitMQ 支持 AMQP 协议。
RabbitMQ 支持多种消息模型,包括点对点和发布订阅。
RabbitMQ 提供消息持久化和确认机制。

------------------------------------------------------------
正在生成答案...

------------------------------------------------------------
生成的答案:
------------------------------------------------------------

RAG 是检索增强生成技术,它结合信息检索和大语言模型生成,可以减少模型幻觉并提供更准确的答案。

============================================================

核心概念总结

通过这个 Demo,我们掌握了 RAG 的核心概念:

1. 文本嵌入(Embedding)

概念 说明
定义 将文本转换为数字向量
目的 让计算机"理解"文本语义
模型 BAAI/bge-large-zh-v1.5
维度 1024

2. 余弦相似度

概念 说明
定义 衡量两个向量相似度的方法
公式 (A·B) / (
范围 [-1, 1]
应用 文档相似度搜索

3. 向量搜索

概念 说明
目的 找到最相关的文档
方法 计算相似度 → 排序 → 取Top-K
输出 相似度分数排序的文档列表

4. 上下文构造

概念 说明
目的 为 LLM 提供准确信息
格式 清晰标注文档边界
限制 避免过长超出模型输入限制

5. LLM 生成

概念 说明
模型 deepseek-ai/DeepSeek-V3
Temperature 0.2(确定性)
输入 上下文 + 问题
输出 基于事实的答案

小结

本篇要点回顾

通过这篇文章,我们实现了:

步骤 实现内容 关键技术
1 读取知识库 文件 I/O
2 文本切分 按空行分割
3 文本嵌入 BAAI/bge-large-zh-v1.5
4 相似度计算 余弦相似度
5 向量搜索 Top-K 检索
6 上下文构造 文档拼接
7 LLM 生成 OpenAI API

核心收获

理解了 RAG 的完整流程:从问题到答案的 7 个步骤
掌握了文本嵌入原理:如何将文本转为向量
学会了相似度搜索:如何找到最相关文档
实现了可运行的 Demo:130 行代码理解 RAG

RAG 的优势

优势 说明
减少幻觉 基于事实回答,不编造信息
知识更新 无需重新训练,更新知识库即可
可追溯 可以查看答案来源
私有数据 可以使用企业内部文档

结语

通过这篇 5 分钟的教程,你应该不仅理解了 RAG 的核心原理,还亲手实现了一个可运行的系统。

记住:

  • RAG 的核心是"检索 + 生成"
  • 向量是连接文本和语义的桥梁
  • 好的上下文是准确回答的基础

在接下来的文章中,我们将深入探索文档向量化系统和更完整的 RAG 问答系统。


参考文章:

Logo

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

更多推荐