从零学RAG0x00之什么是RAG?

0 阅读5分钟

为啥需要RAG

LLM本质上是一个基于海量数据训练的概率模型。目前其本身存在一定的局限性:

  1. 时效性。训练是有成本的,不可能随时更新训练。所以可能有些最近的知识可能其并不知道。

  2. 专业性。LLM训练集虽然已经很大,但依然可能无法涵盖所有领域的知识或特定领域的深度信息。

  3. 幻觉性。在某些情况(提问方式不对、模型知识欠缺)下给出的回答很可能错误的,或者是涉及虚构甚至是故意欺骗的信息,这种回答被称之为AI幻觉,亦即大模型一本正经的胡说八道。

目前,解决以上问题的主流手段主要有:

  1. 【模型微调】 在通用大模型底座基础上,通过额外的训练数据,直接修改大模型的内部参数,使其行为、风格或特定领域的知识内化到模型“大脑”中,从而提升其在该特定领域或任务中的适用性和性能表现。它解决的是模型本身能力与偏好的问题。核心是“因材施教,精雕细琢”。

  2. 【RAG】 模型微调是解决最直接的方式,但是其实现成本和复杂度都较高。无论是算力还是数据,都是一笔不菲的代价。所以RAG技术应运而生。

什么是RAG

RAG (Retrieval Augmented Generation/检索增强生成) 是一种结合了检索和生成两种方法的技术。它通过先检索相关的文档,用检索出来的信息对提示词增强,再使用大模型生成答案。

RAG不改变大模型本身(不修改其参数),而是为其配备一个强大的、可实时更新的“外部知识库”,让模型学会在需要时去精准“检索”并“参考”这些信息来生成答案。它解决的是知识获取渠道的问题。

【本质】 RAG = 大模型LLM + 外部数据

image.png

FastGPT上一个简单的🌰

FastGPT本质上是一个基于大语言模型(LLM)的快速应用开发框架或平台。 使用 FastGPT 核心流程为:

  1. 在可视化界面拖入一个“用户输入”模块。

  2. 连接一个“知识库检索”模块,并选择你之前上传的《公司员工手册》、《财务报销政策》等文档。

  3. 连接一个“大模型调用”模块,编写提示词如:“请根据以下知识库资料,友好地回答新员工的问题:{{问题}}。参考资料:{{检索结果}}”。

  4. 最后连接一个“回复输出”模块。

  5. 点击发布,一个专属的、懂公司所有规章的AI助手就上线了,全程可能只需十几分钟。

image.png

以上是我建好的一个简单的应用:人事咨询助手。我们通过简单的一次提问来看看RAG知识库是如何发挥作用的?

image.png

image.png

image.png

我们简单分析下:

  1. 原始Prompt:候选人通过面试后,哪些条件达不到不会录用?

  2. 根据图看到思考过程是引用了我上传的RAG知识库:人事管理流程

  3. 然后我们点开详细的上下文,发现实际喂给AI的提示词已经变了:

    • ”...可以使用 /Cites 中的内容作为你本次回答的参考...“
    • 而/Cites内容正是 从RAG知识库关联到的知识
  4. 如果没有这个RAG知识库,AI将不知道到底问的是哪个公司,哪个版本的人事管理流程。然后就可能会乱回答。

原理

image.png

  1. 索引化:文档 -》分块 -》向量化 -》存储

  2. 检索:用户提问 -》向量化 -》检索向量数据库 -》得到top-k个相关块

  3. 增强生成:增强提示词 = 原始问题+相关文档块 -》 LLM -》生成答案

分块

上面索引化里提到了分块,那么RAG是怎么将文档进行分块的?目前主流的方式主要有:

  1. 定长。就是按固定长度分割,是最简单的分块方式。
# count:定长
# 将输入的字符串 text 按照指定的字符数 count 进行分割,返回一个列表,其中每个元素是长度为 count 的子字符串。
如果最后一个子字符串不足 count 长度,则保持原样。
使用列表推导式遍历字符串。每次从索引 i 开始截取长度为 count 的子字符串。
索引步长为 count,确保每次跳过已处理的部分

[text[i: i+count] for i in range(0, len(text), count)]

2. 定长重叠。定长分割就很容易出现块与块之间的语义分割,可能导致出现理解问题。于是出现了定长重叠,每个块都含有和上个块重复的一部分。

#stride:每个块重叠部分的大小

[text[i:i + count] for i in range(0, len(text), count - stride)]

3. 句子分割。直接按句子的标点符号进行切割。

re.split(r'(。|?|!|....)', text)

4. 递归切割 RecursiveCharacterTextSpliter,也是我们在后续RAG开发中用的最多的方式。 * chunk_size 块大小,300-500 * chunk_size,分割长度 * chunk_overlap,重叠长度。通常为 chunk_size*10%-20% * separators 切割符,依次按数组符号切割。先按第一个切割后,若不满足块大小再按第二个字符。依此类推。

# RecursiveCharacterTextSplitter 是一个用于将文本分割成较小块的工具。
# 核心思想是根据一组分隔符(separators)逐步分割文本,
# 直到每个块的大小都符合预设的chunk_size。如果某个块仍然过大,它会继续递归地分割,直到满足条件为止。
# 其默认字符列表为 `["\n\n", "\n", " ", ""]`,这种设置首先尝试保持段落、句子和单词的完整性。
# 它特别适用于需要递归地按字符拆分文本的场景,例如处理超长文档或嵌套结构的文本
   
splitter = RecursiveCharacterTextSplitter(
   chunk_size=20,  # 分割长度
   chunk_overlap=5,  # 重叠长度 /重叠窗口大小
   separators=["\n\n", "\n", "。", ",", ""],
)
chunks = splitter.split_text(text)

今天关于RAG的学习记录就到这,下次继续学习怎么把文档向量化。