你是怎么做分块的?块大小怎么选的?

#RAG#Agent#面试八股

你是怎么做分块的?块大小怎么选的?

结论:分块(Chunking)没有全局最优参数,只有"先按文档结构切,再按检索粒度定大小"这套流程。 默认从 300600 token(中文约 200400 字)起步,重叠 10%~15%,然后用评估集验证;有标题结构的文档, 一定要把标题路径带上,它往往比正文更能决定召回。

1. 决策顺序

文档进来
  ├─ 有标题层级(Markdown/HTML/Word 大纲)?──是──▶ 按标题切,超长再二次切分
  ├─ 是表格?──────────────────────是──▶ 整表保留,转 Markdown,禁止按行切
  ├─ 是代码?──────────────────────是──▶ 按函数/类切,保留签名与注释
  └─ 纯文本、无结构 ──────────────────▶ 递归分隔符(段落→句号→空格)
                                        └─ 仍超长 ──▶ 固定窗口 + 重叠

顺序很重要:结构优先于长度。为了凑 500 字把一个"总则"和一个"例外条款"切到两个块里,等于亲手制造矛盾答案。

2. 块大小怎么权衡

块大小(token)召回精度上下文完整度噪声适用场景
128~256高,几乎只命中相关句低,易断章取义少FAQ、参数表、客服话术
300~600中高中,够装一个完整论述中通用默认值
800~1200中高,能覆盖流程步骤多制度文件、操作手册
整篇(>2000)低,向量被"平均"掉最高极多只适合"总结这份文档"类问题

块越大,一个向量要概括的信息越多,语义被稀释:标题讲"A 流程",正文却在讲"B 例外",合在一起的向量谁也代表不了。

3. 重叠(Overlap)解决什么

交界处的句子一定会被切断。重叠 10%~15% 保证"上一块的结尾"能在"下一块的开头"再出现一次。

重叠比例效果
0边界信息必丢,跨块问题答不上
10%~15%推荐,成本可接受
>30%top-k 里出现大量重复内容,白占预算

4. 代码:标题感知 + 窗口兜底

import re

HEAD = re.compile(r"^(#{1,3})\s+(.+)$", re.M)

def split_sections(md: str) -> list[str]:
    """按 1~3 级标题切段,标题留在段首当面包屑。"""
    marks = list(HEAD.finditer(md))
    if not marks:
        return [md]
    return [md[m.start(): marks[i + 1].start() if i + 1 < len(marks) else len(md)].strip()
            for i, m in enumerate(marks)]

def window(text: str, target: int, overlap: int) -> list[str]:
    """无结构文本兜底:定长窗口 + 重叠。target/overlap 单位是字符。"""
    step = max(1, target - overlap)
    return [text[i:i + target] for i in range(0, len(text), step)]

def chunk_markdown(md: str, target: int = 500, overlap: int = 60) -> list[str]:
    chunks: list[str] = []
    for sec in split_sections(md):
        if len(sec) <= int(target * 1.5):   # 一个自然段装得下就不切
            chunks.append(sec)
        else:
            chunks.extend(window(sec, target, overlap))
    return [c for c in chunks if len(c.strip()) >= 50]   # 丢掉标题残片

# 输出:每个块形如 "## 差旅报销\n住宿标准为……"

入库时把 h1/h2 也写成元数据字段,过滤与展示都用得上;后面做父文档检索(Parent Document Retriever)时, 还要额外记录 parent_id。

5. 固定大小分块的三个硬伤

问题现象后果
无视句子边界"逾期未提交的,视为" / "放弃报销权利" 分属两块单块语义残缺,模型答非所问
无视结构切点落在两个章节中间两个主题混进一个向量
表格/代码被拦腰截断表头在第一块,数据行在第二块数值失去列名,检索到也没法用

它也有存在价值:实现简单、可对齐、适合日志或聊天记录这种本来就无结构的文本。不要因为简单就默认用它。

6. 怎么验证块大小选对了

造 30~50 条"问题 → 应命中的原文片段"标注,跑两件事:

指标含义观察什么
答案完整率top-k 中存在完整包含答案的块偏低说明切太碎,答案跨块
召回率 recall@5正确块是否进前 5偏低说明切太大,向量被稀释
平均块 token成本与噪声每块都顶到上限,通常意味着该加结构切分了

7. 常见追问

Q:按字符还是按 token 算? 按 token 算才准,中文 1 字约 0.6~0.8 token,用估算值起步没问题,但上线前要用分词器实测。

Q:要不要给每块加标题? 要。标题 + 正文 一起向量化,能显著改善"这段在讲什么"的召回;只存正文往往丢掉最强信号。

Q:代码块怎么切? 按函数或类切,保留 imports 和注释;别用固定窗口,函数头身分离后基本不可用。

小结

分块是 RAG 里性价比最高的一步:改两行切分逻辑,往往比换一个更贵的模型提升更大。结构优先、宁小勿碎、留重叠,然后拿数据验证。

笔记加载中…