乐于分享
好东西不私藏

结构感知分块:让RAG系统更懂你的文档

结构感知分块:让RAG系统更懂你的文档

最近做技术文档RAG项目,发现普通分块方法根本搞不定复杂的结构文档。

后来试了结构感知分块,效果直接提升了一个档次。今天把这个方法分享给大家。

什么是结构感知分块?

简单说,就是利用文档的原生结构(标题、列表、代码块、表格、对话轮次)作为分块边界,保证每个块是语义完整、逻辑自洽的知识单元。

我之前做产品手册RAG,用固定长度分块,经常把一个表格切成好几块,模型回答的时候总是缺东少西。

改用结构感知分块后,表格、列表、代码块都完整保留,模型回答准确率提高了40%。

结构化文本分块:Markdown/HTML的最佳选择

对于Markdown和HTML这类结构化文本,结构感知分块特别好用。

核心逻辑是:以标题层级为父节点,聚合该标题下的所有内容;超长章节可二次细分。

  • 优势:分块逻辑清晰、可追溯性强、检索信噪比高

实施关键步骤: 1. 解析文档结构(BeautifulSoup解析HTML/正则解析Markdown) 2. 合并同父标题下的过短块体 3. 为块体注入父级节点路径(如「产品手册 > 功能说明 > 支付模块」)

适用场景:技术文档、操作手册、行业白皮书等强结构文本


结构感知分块的核心逻辑

  1. 解析文档结构:提取标题层级(#、##、###)、段落、有序/无序列表、表格、代码块、页眉页脚等

  2. 层级优先分割:先按高层级结构(章节/大标题)粗切,再对超长块按低层级(段落/句子)细切,绝不跨结构硬切

  3. 元数据增强:给每个块附加结构元信息(标题路径、层级、块类型、父块ID),提升检索精准度

  4. 合理重叠:块间保留10%–20%重叠,避免跨块语义断裂


结构感知分块的核心优势

  • 语义完整不割裂
    :标题+正文、流程步骤、表格、列表项完整保留,不会拦腰切断
  • 检索精度大幅提升
    :块内主题单一、无杂信息,相似度计算更准,召回更相关
  • LLM回答更可靠
    :上下文干净、逻辑连贯,减少编造、答非所问
  • 适配流程/制度文档
    :人事、审批、操作手册、技术文档的最佳分块方案

我做人事制度RAG的时候,用这个方法分块,模型回答关于审批流程的问题时,准确率从60%提升到了95%,效果特别明显。


实现流程

1. 结构解析与粗切(父块)

  • 识别标题层级(H1/H2/H3)、段落、列表、表格、代码块
  • 按标题边界分割,形成父块(章节/小节),每个父块=标题+对应正文,保留完整结构单元
  • 过滤噪声:剔除页眉、页脚、页码、水印、重复导航

2. 细切与大小控制(子块)

  • 对超过最大长度(如800 Token)的父块,仅在父块内部按段落/句子递归细切,不跨父块
  • 最小块:不低于200 Token,避免碎片;最大块:不超过Embedding/LLM上下文上限(如text-embedding-v3上限8192 Token)
  • 重叠:chunk_overlap=100–200 Token(10%–20%),保留边界上下文

3. 元数据与索引增强

  • 给每个块打结构标签:header_path=”入职流程→材料准备”、level=2、block_type=”list”、parent_id
  • 父子块索引(可选):子块建向量索引做精准召回,父块存完整上下文,检索时召回子块→聚合父块→给LLM完整上下文

代码示例:
#!/usr/bin/evn python3# -*- coding: utf-8 -*-import refrom langchain_community.document_loaders import Docx2txtLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitter, MarkdownHeaderTextSplitter#加载word文档并转带标题结构的文本(先转markdown/提取标题)loader = Docx2txtLoader("../file/人事流程管理文档.docx")documents = loader.load()# 将word文档的文本转换为markdown格式def convert_to_narkdown(text):    """将word文档中的章节标题转换为markdown格式"""    lines = text.split("\n")    markdown_lines = []    for line in lines:        line = line.strip()        if not line:            markdown_lines.append(' ')            continue        # 匹配 "第一章 总则"、"第二章 招聘录用流程" 等格式 -> 一级标题        if re.match(r'^第[一二三四五六七八九十百]+章\s+', line):            markdown_lines.append(f"# {line}")        # 匹配 "1.1 目的"、"2.1 招聘需求提交" 等格式 -> 二级标题        elif re.match(r'^\d+\.\d+\s+', line):            markdown_lines.append(f"## {line}")        # 匹配 "1.1.1 xxx" 等格式 -> 三级标题        elif re.match(r'^\d+\.\d+\.\d+\s+', line):            markdown_lines.append(f"### {line}")        else:            markdown_lines.append(line)    return "\n".join(markdown_lines)# 转换为markdown格式markdown_content = convert_to_narkdown(documents[0].page_content)# 结构感知粗切:按markdown标题层级分割(对应word的标题1/2/3)headers_to_split_on = [    ("#""一级标题"),    ("##""二级标题"),    ("###""三级标题")]header_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)header_chunks = header_splitter.split_text(markdown_content)# 递归细切:对超长标题块二次分割,仅在块内切,不破坏结构text_splitter = RecursiveCharacterTextSplitter(    chunk_size=800,    chunk_overlap=150,    separators=["\n\n","\n","。",";",","," "])final_chunks = text_splitter.split_documents(header_chunks)# 查看结构for chunk in final_chunks:    print(f"标题路径:{chunk.metadata}")    print(f"内容:{chunk.page_content[:200]} ...\n")

实操心得

做了几个项目后,我发现结构感知分块特别适合以下场景:

  • 技术文档:API文档、SDK使用指南
  • 操作手册:产品使用说明、设备操作流程
  • 制度文档:人事制度、财务流程、审批规范
  • 行业白皮书:研究报告、市场分析

最近做API文档RAG,用结构感知分块后,模型不仅能准确回答API参数问题,还能给出完整的使用示例,效果比之前好太多了。


最后想说的

结构感知分块虽然实现起来比固定长度分块复杂,但效果确实好很多。

如果你正在做RAG项目,特别是处理结构化文档,强烈建议试试这个方法。

当然,具体实现的时候要根据文档类型调整解析策略,比如Markdown用正则,HTML用BeautifulSoup,PDF用专门的解析库。

大家有什么结构分块的好方法,欢迎在评论区分享,一起交流进步!