前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >大模型RAG:文档分块方案与RAG全流程

大模型RAG:文档分块方案与RAG全流程

原创
作者头像
程序员架构进阶
发布2025-02-28 21:56:23
发布2025-02-28 21:56:23
11000
代码可运行
举报
文章被收录于专栏:架构进阶
运行总次数:0
代码可运行

一 RAG与文本分块

1.1 为什么要文档分块

在上一篇文章《大模型RAG:基于PgSql的向量检索》中,简单介绍了RAG概念和简要实现。在实际的应用中,技术方案远不会这样简单。

我们知道,大模型在预训练阶段获取的知识是有限的,一般需要数据增强模块引入外部知识库,通过知识检索的方式搜索于用户提问相关的知识,这也是RAG相关应用架构出现的原因。但这又引申出另一个问题,外部知识文档往往比较长,可能是包含几十页甚至数百页的内容,如果直接使用会存在以下问题

  • 1.大模型处理的上下文长度有限:大模型在预训练过程都有上下文长度限制,如果超过长度限制大模型会将超出部分丢弃,从而影响回答的性能表现。(注:目前很多大模型已经支持192K甚至更大的超长上下文窗口+搜索增强知识库,但基于成本和性能考虑,大文档分chunk依然是RAG方案必须包含的环节)。
  • 2.语义杂揉不利于任务检索:长文档中各个片段的语义之前可能存在较大的差异,如果当成一个整体来做知识检索会存在语义的杂揉,应当将长文档切分成更多的小块,促使每个小块内部表意一致,块之间表意存在多样性,从而更充分的发挥知识检索的作用

所以我们需要根据一定策略将文本切分为小块,以便适应大模型的上下文窗口,同时提高知识检索的精度

1.2 分块的目标

文本分块不是盲目的切分,而是必须在不影响或尽量降低对整体效果影响的前提下进行。所以文本分块(chunk最核心的目的就是把相同语义的token聚集在一起,不同语义的token互相分开,利于后续的retrieve和rerank。举个例子:我们有一个word文档,分为多个段落,每个段落都是一个问题的问答对。那么显然把一个问答对作为一个chunk划分是最理想的结果。

二 常用的文本分块技术方案

常见chunk方案有按照字符chunk、按token、按段落、递归划分、语义划分、代理划分等。Langchain作为一个 LLM 协调框架,内置了一些用于分块以及加载文档的工具,提供了很多可以开箱即用的chunk方法:

CharacterTextSplitter

RecursiveCharacterTextSplitter

Split by tokens

Semantic Chunking

HTMLHeaderTextSplitter

MarkdownHeaderTextSplitter

RecursiveJsonSplitter

Split Cod

由于相关的代码解说文章已经很多,这里不再赘述,只以RecursiveCharacterTextSplitter、SentenceSplitter和HTMLHeaderTextSplitter为例:

2.1 RecursiveCharacterTextSplitter

基于字符数来递归地分割文本。每个块都保持在指定的长度以下,这对于具有自然段落或句子间断的文档特别有用,确保了块的可管理性和易于处理性,而不会丢失文档的固有结构。

Langchain中的递归字符文本分割器方法根据字符数将文本分割成块,以确保每个块低于指定的长度。这种方法有助于保持文档中段落或句子的自然断开。

代码语言:javascript
代码运行次数:0
复制
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    # 这里做初始化,块大小1k以及50个跨文本字符
    separators=["\n\n", "\n", " ", ""],
    chunk_size=1000,
    chunk_overlap=50,
    length_function=len,
    is_separator_regex=False,
)
chunks = text_splitter.split_text(pages[0].page_content)
print(len(chunks))

# 打印输出
for chunk in chunks:
    print(chunk)

2.2 SentenceSplitter

SentenceSplitter是在句子边界上分割文本,这种方法能够保持文本的上下文完整性。句子通常代表完整的思想,这使得这种方法非常适合那些对内容有连贯理解的场景。

代码语言:javascript
代码运行次数:0
复制
from langchain.text_splitter import SentenceSplitter
 
text = "long document text ..."
 
# 初始化SentenceSplitter ,每个块最多5个句子
splitter = SentenceSplitter(max_length=5)
 
chunks = splitter.split_text(text)
 
for chunk in chunks:
    print(chunk)

2.3 HTMLHeaderTextSplitter

HTMLHeaderTextSplitter是一个网页代码分块器,它根据 HTML 元素拆分文本,并将相关元数据分配给分块内的每个标头。它可以返回单个分块或将具有相同元数据的元素组合在一起,以保持语义分组并保留文档的结构上下文。此拆分器可与分块管道中的其他文本拆分器结合使用。

代码语言:javascript
代码运行次数:0
复制
from langchain_text_splitters import HTMLHeaderTextSplitter
# 这里定义一段HTML网页内容代码
html_string = """
<!DOCTYPE html>
<html>
<body>
    <div>
        <h1>Foo</h1>
        <p>Some intro text about Foo.</p>
        <div>
            <h2>Bar main section</h2>
            <p>Some intro text about Bar.</p>
            <h3>Bar subsection 1</h3>
            <p>Some text about the first subtopic of Bar.</p>
            <h3>Bar subsection 2</h3>
            <p>Some text about the second subtopic of Bar.</p>
        </div>
        <div>
            <h2>Baz</h2>
            <p>Some text about Baz</p>
        </div>
        <br>
        <p>Some concluding text about Foo</p>
    </div>
</body>
</html>
"""

headers_to_split_on = [
    ("h1", "Header 1"),
    ("h2", "Header 2"),
    ("h3", "Header 3"),
]

html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
html_header_splits = html_splitter.split_text(html_string)
html_header_splits

"""
[Document(page_content='Foo'),
 Document(page_content='Some intro text about Foo.  \nBar main section Bar subsection 1 Bar subsection 2', metadata={'Header 1': 'Foo'}),
 Document(page_content='Some intro text about Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section'}),
 Document(page_content='Some text about the first subtopic of Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 1'}),
 Document(page_content='Some text about the second subtopic of Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 2'}),
 Document(page_content='Baz', metadata={'Header 1': 'Foo'}),
 Document(page_content='Some text about Baz', metadata={'Header 1': 'Foo', 'Header 2': 'Baz'}),
 Document(page_content='Some concluding text about Foo', metadata={'Header 1': 'Foo'})]
"""

三 自实现的文本chunk方法

尽管LangChain和llamaIndex都已经提供了文本Chunk方法,但不幸的是这两者提取语义embedding用的都是openAI的接口,要收费不说,大陆地区还面临被封API的风险,更关键的是在很多企业内根本不允许使用。所以我们只能参考LangChain中各方法的实现原理,自行开发和调试文本chunk方法。

四 从文档角度出发的RAG技术方案

从文档角度出发,RAG流程中的各个主要环节如下:

关键概念说明:

RAPTOR:RAPTOR模型提出了一种创新的策略。它通过递归地进行文本片段的向量化、聚类和摘要生成,构建了一个树状索引结构。这种结构不仅捕捉了文档的高层次主题,还保留了低层次的细节,允许基于语义相似性而非仅仅是文本顺序对节点进行分组。这样的树状结构使得RAPTOR能够在不同的抽象层次上加载文档的上下文片段,从而有效地回答不同层次的问题。

GraphRAG:2024年4月微软推出GraphRAG,并于7月2日开源。GraphRAG仍然沿袭了RAG的思路,即通过检索来增强模型的准确性。不过,与RAG不同的是,GraphRAG还引入了“知识图谱”(Knowledge Graph)技术,以增强模型的“检索”能力,以实现对复杂信息的高效和可靠检索,从而提高LLM问答系统对于复杂信息的答案生成的准确性。GraphRAG示意图如下:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 RAG与文本分块
    • 1.1 为什么要文档分块
    • 1.2 分块的目标
  • 二 常用的文本分块技术方案
    • 2.1 RecursiveCharacterTextSplitter
    • 2.2 SentenceSplitter
    • 2.3 HTMLHeaderTextSplitter
  • 三 自实现的文本chunk方法
  • 四 从文档角度出发的RAG技术方案
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档