为啥需要RAG
LLM本质上是一个基于海量数据训练的概率模型。目前其本身存在一定的局限性:
-
时效性。训练是有成本的,不可能随时更新训练。所以可能有些最近的知识可能其并不知道。
-
专业性。LLM训练集虽然已经很大,但依然可能无法涵盖所有领域的知识或特定领域的深度信息。
-
幻觉性。在某些情况(提问方式不对、模型知识欠缺)下给出的回答很可能错误的,或者是涉及虚构甚至是故意欺骗的信息,这种回答被称之为AI幻觉,亦即大模型一本正经的胡说八道。
目前,解决以上问题的主流手段主要有:
-
【模型微调】 在通用大模型底座基础上,通过额外的训练数据,直接修改大模型的内部参数,使其行为、风格或特定领域的知识内化到模型“大脑”中,从而提升其在该特定领域或任务中的适用性和性能表现。它解决的是模型本身能力与偏好的问题。核心是“因材施教,精雕细琢”。
-
【RAG】 模型微调是解决最直接的方式,但是其实现成本和复杂度都较高。无论是算力还是数据,都是一笔不菲的代价。所以RAG技术应运而生。
什么是RAG
RAG (Retrieval Augmented Generation/检索增强生成) 是一种结合了检索和生成两种方法的技术。它通过先检索相关的文档,用检索出来的信息对提示词增强,再使用大模型生成答案。
RAG不改变大模型本身(不修改其参数),而是为其配备一个强大的、可实时更新的“外部知识库”,让模型学会在需要时去精准“检索”并“参考”这些信息来生成答案。它解决的是知识获取渠道的问题。
【本质】 RAG = 大模型LLM + 外部数据
FastGPT上一个简单的🌰
FastGPT本质上是一个基于大语言模型(LLM)的快速应用开发框架或平台。 使用 FastGPT 核心流程为:
-
在可视化界面拖入一个“用户输入”模块。
-
连接一个“知识库检索”模块,并选择你之前上传的《公司员工手册》、《财务报销政策》等文档。
-
连接一个“大模型调用”模块,编写提示词如:“请根据以下知识库资料,友好地回答新员工的问题:{{问题}}。参考资料:{{检索结果}}”。
-
最后连接一个“回复输出”模块。
-
点击发布,一个专属的、懂公司所有规章的AI助手就上线了,全程可能只需十几分钟。
以上是我建好的一个简单的应用:人事咨询助手。我们通过简单的一次提问来看看RAG知识库是如何发挥作用的?
我们简单分析下:
-
原始Prompt:候选人通过面试后,哪些条件达不到不会录用?
-
根据图看到思考过程是引用了我上传的RAG知识库:人事管理流程
-
然后我们点开详细的上下文,发现实际喂给AI的提示词已经变了:
- ”...可以使用 /Cites 中的内容作为你本次回答的参考...“
- 而/Cites内容正是 从RAG知识库关联到的知识
-
如果没有这个RAG知识库,AI将不知道到底问的是哪个公司,哪个版本的人事管理流程。然后就可能会乱回答。
原理
-
索引化:文档 -》分块 -》向量化 -》存储
-
检索:用户提问 -》向量化 -》检索向量数据库 -》得到top-k个相关块
-
增强生成:增强提示词 = 原始问题+相关文档块 -》 LLM -》生成答案
分块
上面索引化里提到了分块,那么RAG是怎么将文档进行分块的?目前主流的方式主要有:
- 定长。就是按固定长度分割,是最简单的分块方式。
# 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的学习记录就到这,下次继续学习怎么把文档向量化。