RAG语料清洗与Chunk策略

RAG 效果差?先看看你的语料清洗和 Chunk 做对了吗

很多人聊 RAG,张口就是向量模型、Embedding 精度、检索排序算法。诚然这些很重要,但还有一个被严重低估的环节——语料清洗与 Chunk

我见过太多项目,花了大价钱调优向量模型,结果召回的内容还是乱七八糟。问题往往不在检索端,而在最上游:脏数据、混乱的文档结构、拍脑袋定的 chunk 大小。

今天从实战角度,聊一套完整的高质量语料清洗与 Chunk 方案。

一、语料清洗:磨刀不误砍柴工

1. 去除”脏”内容

原始语料常见的噪音:

  • HTML 标签、Markdown 语法残留
  • 页眉页脚、版权声明、导航栏
  • 重复的标题、广告水印
  • 乱码、异常字符
  • 连续的空白符和无意义换行
import re

def clean_text(text: str) -> str:
    # 去除 HTML 标签
    text = re.sub(r'<[^>]+>', '', text)
    # 去除 Markdown 图片和链接
    text = re.sub(r'!\[.*?\]\(.*?\)', '', text)
    text = re.sub(r'\[.*?\]\(.*?\)', r'\1', text)
    # 去除多余空白
    text = re.sub(r'\n{3,}', '\n\n', text)
    text = re.sub(r'[ \t]+', ' ', text)
    return text.strip()

2. 结构识别与保留

清洗不是把所有格式抹平,文档结构信息同样重要。我们需要在清洗时保留:

  • 标题层级(H1/H2/H3)
  • 表格结构
  • 代码块的语法标记
  • 列表项的层级关系
from dataclasses import dataclass
from typing import List, Optional

@dataclass
class DocumentBlock:
    type: str  # 'heading', 'paragraph', 'table', 'code', 'list'
    level: int = 0  # 标题层级
    content: str
    metadata: dict = None

3. 内容质量过滤

不是所有文字都值得进入知识库:

def is_quality_content(text: str) -> bool:
    # 过滤纯数字、纯符号
    if re.match(r'^[\d\s\W]+$', text):
        return False
    # 过滤过短的内容
    if len(text) < 50:
        return False
    # 过滤代码行过多的情况(保留技术文档,但排除纯日志)
    code_ratio = text.count('`') / len(text)
    if code_ratio > 0.3:
        return False
    return True

二、Chunk 策略:不是切完就完事

1. Chunk 大小的选择

这是个技术活,没有银弹:

场景 推荐 Chunk Size 理由
问答系统 300-500 tokens 答案通常短小精悍
文档摘要 800-1200 tokens 需要足够的上下文
代码检索 50-200 tokens 函数/类级别,语义独立
复杂长文 1000-2000 tokens 需要段落完整性

2. 语义感知的切分策略

不要简单地按字符数切分,会切断语义。

推荐按自然语义单元切分:

from typing import Iterator

def semantic_chunk(text: str, max_tokens: int = 500) -> Iterator[str]:
    """
    按语义单元切分,优先保持段落完整
    """
    # 优先在 H2/H3 标题处切分
    sections = re.split(r'(?=\n##\s)', text)
    
    chunks = []
    current_chunk = []
    current_size = 0
    
    for section in sections:
        section_size = estimate_tokens(section)
        
        if current_size + section_size <= max_tokens:
            current_chunk.append(section)
            current_size += section_size
        else:
            if current_chunk:
                yield '\n'.join(current_chunk)
            # 如果单个 section 就超过限制,按段落切
            if section_size > max_tokens:
                for para in section.split('\n\n'):
                    if len(para) < max_tokens:
                        yield para
                    else:
                        yield para  # 仍需返回,让下游处理
            else:
                current_chunk = [section]
                current_size = section_size
    
    if current_chunk:
        yield '\n'.join(current_chunk)

3. 带上下文的 Chunk

纯粹的切分会导致边界信息丢失。重叠 Chunk元信息注入 是两个有效手段:

def chunk_with_context(
    text: str,
    max_tokens: int = 500,
    overlap_tokens: int = 100
) -> List[dict]:
    chunks = []
    start = 0
    
    while start < len(text):
        end = start + max_tokens
        chunk_text = text[start:end]
        
        # 提取该 Chunk 所在章节的标题,作为上下文前缀
        section_title = extract_nearest_heading(text, start)
        
        chunks.append({
            'content': chunk_text,
            'metadata': {
                'section': section_title,
                'position': start,
                'doc_title': get_doc_title(text)
            }
        })
        
        start += max_tokens - overlap_tokens
    
    return chunks

三、实战流程总结

原始文档
    ↓
HTML/ PDF 解析 → 结构化提取
    ↓
内容清洗 → 去噪 + 格式规范化
    ↓
质量过滤 → 短文本/噪音过滤
    ↓
语义 Chunk → 按语义单元切分
    ↓
上下文增强 → 添加章节标题/文档标题
    ↓
向量化 → 存入向量数据库

四、避坑指南

  1. 别贪大:很多人觉得 chunk 越大信息越全,实际上大 chunk 会稀释核心信息,召回精度反而下降。

  2. 保留表格:表格被转成纯文本后语义丢失严重。有条件的话,用 Markdown 表格格式保留结构,或者单独处理。

  3. 代码块单独存:代码和技术解释文字混在一起时,Embedding 容易被带偏。建议代码块独立存储,检索时可以单独召回。

  4. 中文分词注意:基于 token 的 chunk 要考虑中文分词器,Character-level 切分对中文不太友好。

  5. 建立评估集:用真实问答对测试不同 chunk 策略的效果,而不是拍脑袋选参数。

写在最后

RAG 是个系统工程,检索只是最后一环。高质量的语料处理,是一切的基础。

与其花时间调参,不如先把数据清洗干净、把 chunk 策略做对。这往往能带来 50% 以上的效果提升,比换什么向量模型都管用。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1056615746@qq.com

文章标题:RAG语料清洗与Chunk策略

字数:1.3k

本文作者:攀登

发布时间:2026-06-16, 22:00:00

最后更新:2026-06-17, 00:27:48

原始链接:http://jiafeimao-gjf.github.io/2026/06/16/RAG%E8%AF%AD%E6%96%99%E6%B8%85%E6%B4%97%E4%B8%8EChunk%E7%AD%96%E7%95%A5/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

Help us with donation