RAGFlow 文档切分逻辑详解 整体架构 文档切分是 RAGFlow 检索管线中最核心的环节。RAGFlow 中有两套切分实现,共享底层算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 用户上传文档 │ ▼ ┌─ rag/app/naive.py::chunk() ◄── 经典路径,文档首次上传 │ │ │ ▼ │ deepdoc/parser/* 解析文档 → sections 列表 │ │ │ ▼ │ rag/nlp/__init__.py::naive_merge() ← 核心合并算法 │ │ │ ▼ │ rag/nlp/__init__.py::tokenize_chunks() ← 最终 token 化 │ └─ rag/flow/chunker/token_chunker.py::TokenChunker ◄── Pipeline 组件路径 │ ▼ _build_json_chunks() → _merge_text_chunks_by_token_size() → _finalize_json_chunks()
两条路径的底层合并逻辑完全相同 ,区别在于:
维度
naive.py::chunk()
TokenChunker
定位
一体式函数
Pipeline 组件
流程
解析 + 合并 + token 化一把梭
拆成多个阶段,可被 Agent 画布编排
适用
文档首次上传
Agent 工作流中的切分节点
完整数据流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 原始文档 (PDF/Word/Markdown/...) │ ▼ deepdoc 解析器 │ 输出: sections = [(text, position_tag), ...] │ ▼ ┌── naive_merge() ──────────────────────────────────┐ │ │ │ 遍历 sections,逐段累计 token 数 │ │ 到达 chunk_token_num 阈值时切出新 chunk │ │ 如有 overlap,新 chunk 头部带上旧 chunk 的尾部 │ │ 如有自定义分隔符,直接按分隔符切分(跳过合并) │ │ │ │ 输出: chunks = ["文本块1", "文本块2", ...] │ └────────────────────────────────────────────────────┘ │ ▼ tokenize_chunks() │ - 去掉 PDF 位置标签 @@...## │ - 调用 rag_tokenizer.tokenize() 做分词 │ - 生成 coarse tokens (content_ltks) 和 fine-grained tokens (content_sm_ltks) │ - 如有 child_delimiters,对每个 chunk 做二次切分 │ ▼ 最终输出 (存入 ES/Infinity): [ { "content_with_weight": "文本块1", "content_ltks": ["token1", "token2", ...], ← 粗粒度 token,用于检索 "content_sm_ltks": ["t", "to", "tok", ...], ← 细粒度 token,用于模糊匹配 "doc_type_kwd": "text", ← 类型标记 "position_int": [(pn, left, right, top, bottom)], ← PDF 坐标 "image": PIL.Image | None, ← 关联图片 }, ... ]
第 1 层:解析 → sections 不同文件类型产生不同格式的 sections,最终统一成 [(text, position_info), ...]。
PDF 1 2 sections = [(b["text" ], f"@@{page} \t{left} \t{right} \t{top} \t{bottom} ##" ) for b in boxes]
sections 是 PDF 中每个文本框的内容,附带页码和坐标信息。
Word (DOCX) 1 2 sections = [(line.get("text" ), line.get("image" ), line.get("table" )) for line in lines]
Word 的 sections 是文档中的段落、图片和表格的混合序列。
其他格式
格式
解析器
输出
Excel
ExcelParser
[(cell_text, ""), ...]
Markdown
MarkdownParser
按标题/分隔符切分的 blocks
HTML
HtmlParser
文本段落列表
TXT
TxtParser
按分隔符切分的文本段
JSON
JsonParser
结构化 key-value 文本段
第 2 层:naive_merge — 核心合并算法 文件位置: rag/nlp/__init__.py:1070
1 def naive_merge (sections, chunk_token_num=128 , delimiter="\n。;!?" , overlapped_percent=0 ):
核心思路 逐段叠加,超限则切:
1 2 3 4 5 6 7 8 sections: [sec1, sec2, sec3, sec4, ...] │ ▼ 遍历每个 section: 1. 计算这个 section 的 token 数 2. 当前 chunk 是否已超过阈值? → Yes: 起新 chunk(带上 overlap 尾巴) → No: 追加到当前 chunk
切分条件判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def add_chunk (t, pos ): tnum = num_tokens_from_string(t) if tnum < 8 : pos = "" if cks[-1 ] == "" or tk_nums[-1 ] > chunk_token_num * (100 - overlapped_percent) / 100. : if cks: overlapped = remove_tag(cks[-1 ]) t = overlapped[int (len (overlapped) * (100 - overlapped_percent) / 100. ):] + t if t.find(pos) < 0 : t += pos cks.append(t) tk_nums.append(tnum) else : if cks[-1 ].find(pos) < 0 : t += pos cks[-1 ] += t tk_nums[-1 ] += tnum
关键设计:
切分阈值 = chunk_token_num * (100 - overlapped_percent) / 100
chunk_token_num=512, overlapped_percent=0 → 阈值 = 512
chunk_token_num=512, overlapped_percent=10 → 阈值 = 460(提前触发,为 overlap 留空间)
Overlap(重叠)机制 相邻 chunk 之间有文本重叠,避免关键信息在 chunk 边界被截断:
1 2 3 chunk1: "ABCDEFGHIJKLMNOPQRST" (20 chars, overlapped_percent=20%) ↓ 到达阈值,起新 chunk chunk2: "QRST" + new_text ← 把 chunk1 后 20% 的内容带过来
实现:
1 2 3 overlapped = remove_tag(cks[-1 ]) t = overlapped[int (len (overlapped) * 0.8 ):] + t
自定义分隔符模式 1 2 delimiter = "\n。;!?" ← 默认:换行 + 中文断句符号 delimiter = "\n`##``---`" ← 支持 backtick 包裹的自定义分隔符
当存在 `分隔符` 时,走完全不同的切分逻辑 ——不再逐段累积合并:
1 2 3 4 5 6 custom_pattern = "##|---" for sec, pos in sections: split_sec = re.split(r"(%s)" % custom_pattern, sec) for sub_sec in split_sec: cks.append("\n" + sub_sec)
即:自定义分隔符模式下,按分隔符精确切分,每个段都是独立 chunk,不合并。
第 3 层:DOCX 特殊处理 — naive_merge_docx 文件位置: rag/nlp/__init__.py:1463
Word 文档走这条路径,因为其解析结果是图片、表格和文本的混合序列 ,需要区分 chunk 类型。
3.1 构建类型化 chunks — _build_cks() 1 2 3 4 5 6 7 8 9 10 sections = [(text, image, table), ...] │ ▼ cks = [ {"text" : "..." , "ck_type" : "text" , "tk_nums" : 50 }, {"text" : "..." , "ck_type" : "image" , "tk_nums" : 0 }, ← 图片不参与 token 合并 {"text" : "..." , "ck_type" : "table" , "tk_nums" : 120 }, {"text" : "..." , "ck_type" : "text" , "tk_nums" : 80 }, ... ]
三种 chunk 类型:text(可合并)、image(独立保留)、table(独立保留)。
3.2 附加上下文 — _add_context() 为表格/图片 chunks 附加周围的文本上下文,提升检索体验:
1 2 ...text chunk... | [IMAGE chunk] | ...text chunk... 上下文上限 ↑ 图片本身 上下文下限 ↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def _add_context (cks, idx, context_size ): while prev >= 0 and remain_above > 0 : if cks[prev]["ck_type" ] == "text" : piece = take_sentences_from_end(cks[prev]["text" ], remain_above) parts_above.insert(0 , piece) while after < len (cks) and remain_below > 0 : if cks[after]["ck_type" ] == "text" : piece = take_sentences_from_start(cks[after]["text" ], remain_below) parts_below.append(piece) cks[idx]["context_above" ] = "" .join(parts_above) cks[idx]["context_below" ] = "" .join(parts_below)
上下文收集是按 token 预算精确控制的,从最近的文本 chunk 开始逐块收集,直到满足 context_size 指定的 token 数。
3.3 合并文本 chunks — _merge_cks() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def _merge_cks (cks, chunk_token_num, has_custom ): for i in range (len (cks)): ck_type = cks[i]["ck_type" ] if ck_type == "text" : if prev_text_ck >= 0 \ and merged[prev_text_ck]["tk_nums" ] < chunk_token_num \ and not has_custom: merged[prev_text_ck]["text" ] += cks[i]["text" ] merged[prev_text_ck]["tk_nums" ] += cks[i]["tk_nums" ] else : merged.append(cks[i]) else : merged.append(cks[i])
图片和表格永远不参与文本合并 ,它们作为独立 chunk 保留。
第 4 层:TokenChunker — Pipeline 组件版本 文件位置: rag/flow/chunker/token_chunker.py
这是 Agent 画布中的切分组件,参数更丰富,流程更结构化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 _upstream JSON │ ▼ _build_json_chunks() ← 解析上游 JSON,按类型构建内部 chunk │ ▼ _attach_context_to_media_chunks() ← 给 image/table 附加上下文文本 │ ▼ _merge_text_chunks_by_token_size() ← token_size 模式下合并小文本 chunks │ ▼ _split_chunk_docs_by_children() ← 用 children_delimiters 二次切分 │ ▼ _finalize_json_chunks() ← 转成最终输出,写入 PDF 坐标等元数据
三种切分模式 1 2 3 self .delimiter_mode = "token_size" = "delimiter" = "one"
模式
行为
token_size
逐段累计 token,超限起新块(与 naive_merge 一致)
delimiter
用正则按分隔符切分,不合并
one
整个文档一个 chunk
children_delimiters 二次切分 一次切分后,可以用子分隔符对每个 chunk 做更细粒度的切分:
1 2 3 4 5 6 7 8 9 chunks = _split_text_by_pattern(payload, delimiter_pattern) if custom_pattern: for c in cks: for text in _split_text_by_pattern(c, custom_pattern): docs.append({"text" : text, "mom" : c})
mom 字段的作用 :存切分前的父块原文,检索时可以提供原始段落的完整上下文。
媒体上下文附加(新版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def _attach_context_to_media_chunks (chunks, table_context_size, image_context_size ): for i, chunk in enumerate (chunks): if chunk["ck_type" ] not in {"table" , "image" }: continue context_size = image_context_size if chunk["ck_type" ] == "image" else table_context_size prev = i - 1 while prev >= 0 and remain_above > 0 : if prev_chunk["ck_type" ] == "text" : if prev_chunk["tk_nums" ] >= remain_above: parts_above.insert(0 , _take_sentences(prev_chunk["text" ], remain_above, from_end=True )) else : parts_above.insert(0 , prev_chunk["text" ]) after = i + 1 while after < len (chunks) and remain_below > 0 : chunk["context_above" ] = "" .join(parts_above) chunk["context_below" ] = "" .join(parts_below)
最终输出:tokenize_chunks / tokenize 所有切分路径最终都通过 tokenize() 函数生成可检索的数据结构:
1 2 3 4 5 6 7 def tokenize (d, txt, eng ): d["content_with_weight" ] = txt t = re.sub(r"</?(table|td|caption|tr|th)( [^<>]{0,12})?>" , " " , txt) d["content_ltks" ] = rag_tokenizer.tokenize(t) d["content_sm_ltks" ] = rag_tokenizer.fine_grained_tokenize(d["content_ltks" ])
两种 token:
content_ltks:粗粒度分词,用于精确检索
content_sm_ltks:细粒度分词(字/子词级别),用于模糊匹配和拼写容错
参数总览
参数
默认值
作用
使用位置
chunk_token_num
128 (naive) / 512 (pipeline)
每个 chunk 的最大 token 数
naive_merge, TokenChunker
delimiter
\n。;!?
断句分隔符。用 ` 包裹自定义分隔符
naive_merge, _build_cks
overlapped_percent
0
chunk 之间的重叠比例 (0–100)
naive_merge, _merge_text_chunks_by_token_size
children_delimiters
[]
二次切分分隔符列表
tokenize_chunks, _split_chunk_docs_by_children
table_context_size
0
表格附近附带的文本 token 数
naive_merge_docx, _attach_context_to_media_chunks
image_context_size
0
图片附近附带的文本 token 数
naive_merge_docx, _attach_context_to_media_chunks
delimiter_mode
token_size
切分模式:token_size / delimiter / one
TokenChunker
核心文件索引
文件
核心内容
行数
rag/nlp/__init__.py
naive_merge(), naive_merge_docx(), tokenize_chunks(), tokenize(), split_with_pattern()
~1600
rag/app/naive.py
chunk() — 文档上传入口,路由解析器 + 调用 merge
~1150
rag/flow/chunker/token_chunker.py
TokenChunker — Pipeline 组件版切分器
~370
rag/flow/base.py
ProcessBase — 流水线组件基类
~60
deepdoc/parser/pdf_parser.py
PDF 解析器,产出 sections
~2500+
common/token_utils.py
num_tokens_from_string() — token 计数
—
一句话总结
文档先被 deepdoc 解析成最小 text sections,然后逐段累加 token 数,超过 chunk_token_num 阈值就切一个新 chunk。同时支持 overlap 避免信息被截断、自定义分隔符精确控制切分边界、以及图片/表格附加上下文增强检索质量。