深入探索RAGFlow分片引擎的切片实现
本文旨在深入剖析RAGFlow分片引擎的核心切片机制与策略注册原理,通过逐步拆解代码实现,帮助你系统理解其如何灵活应对各类文档处理需求。
1. 切片方法的选择与 parser_id 参数的作用
在创建知识库时,你可以为数据集指定一种切片策略。这个配置参数定义在 parser_id 中。前端界面会呈现多种切片策略选项,并在右侧区域显示对应的描述信息,辅助你完成选择。
关键点:parser_id 是串联前端配置与后端处理逻辑的桥梁。
2. 代码实现中的 chunker.chunk 方法调用细节
当处理请求时,系统会调用一个核心函数。我们来看一下具体的代码片段:
async def build_chunks(task, progress_callback):
# 根据配置获取到切片实现(策略)
chunker = FACTORY[task["parser_id"].lower()]
async with chunk_limiter:
cks = await trio.to_thread.run_sync(lambda: chunker.chunk(task["name"], binary=binary, from_page=task["from_page"],
to_page=task["to_page"], lang=task["language"], callback=progress_callback,
kb_id=task["kb_id"], parser_config=task["parser_config"], tenant_id=task["tenant_id"]))
- 动态获取策略:代码首先从
FACTORY字典中,根据parser_id的小写形式,获取对应的处理模块(例如naive,paper等)。 - 调用统一接口:接着,代码会调用该模块的
chunk方法。需要注意的是,所有模块都遵循隐式接口,都拥有一个名为chunk的方法,这正是策略模式的核心体现。
3. 策略注册过程及各类处理器的应用场景
策略注册通过一个名为 FACTORY 的映射字典实现。这个字典本质上是一个配置映射,它根据前端传入的 parser_id,选择并实例化对应的处理器。其注册代码如下:
from rag.app import laws, paper, presentation, manual, qa, table, book, resume, picture, naive, one, audio, email, tag
# 策略注册(隐式接口)
FACTORY = {
"general": naive, # 基础文本处理器
ParserType.NAIVE.value: naive,
ParserType.PAPER.value: paper, # 学术论文处理器
ParserType.BOOK.value: book,
ParserType.PRESENTATION.value: presentation,
ParserType.MANUAL.value: manual,
ParserType.LAWS.value: laws,
ParserType.QA.value: qa,
ParserType.TABLE.value: table, # 表格专用处理器
ParserType.RESUME.value: resume,
ParserType.PICTURE.value: picture,
ParserType.ONE.value: one,
ParserType.AUDIO.value: audio,
ParserType.EMAIL.value: email,
ParserType.KG.value: naive,
ParserType.TAG.value: tag
}
- 核心机制:
FACTORY字典的键对应着不同的ParserType枚举值或字符串(如"general"),值则是从rag.app模块导入的类文件。 - 隐式接口的提醒:这是典型的 策略模式实现。但值得注意的是,它是一种 Python 隐式接口,所有处理器都暴露相同的
chunk方法。虽然灵活,但新接触代码时,如果不熟悉,可能需要反复翻阅才能确定具体哪个处理器被调用。未来的优化方向可能是显式化这个接口。
整体代码逻辑流程
整个切片请求的处理流程可以用以下流程图概括:
策略工厂FACTORY general/naive paper table ... 分片请求 parser_id参数 naive.py paper.py table.py ... 其他处理器 统一chunk方法接口 执行具体分片逻辑 返回结构化分片数据
通用方法 (naive) 与文件类型:在通用处理方法(对应 general 选项)中,系统会根据不同的文件类型(如 DOCX, PDF, Excel 等)执行对应的解析逻辑。
4. 拆解几个定向的分片实现
4.1 Manual 处理器
Manual 处理器专门用于处理操作手册类文档。
- 支持格式:虽然前端显示仅支持 PDF,但后端代码实际上支持 PDF 和 DOCX 两种格式。
- 处理逻辑:其处理流程如下:
PDFDOCX 文件输入 文件类型判断 PDF解析器 DOCX解析器 OCR+布局分析 表格识别 段落结构解析 表格HTML转换 分块处理 Token化输出
- 关键特点:
- 专注于文档的结构化,特别是 表格提取。
- 与
naive模式相比,不会抽取图片,也没有使用视觉模型对图片进行增强。 - 因此,Manual 模式最适合处理没有图片、只有表格内容的 PDF 文档。
- 代码细节:在
manual处理器的代码中,对 PDF 和 DOCX 的处理逻辑有些重复,且注重表格的结构化。
小提示:如果你的文档包含复杂的图片图表,建议使用
General模式,因为它内置了视觉模型增强功能。
常见问题:为什么 Manual 模式不处理图片?
答:因为操作手册类文档的图片通常只是对文字说明的辅助,其核心信息在于文字和表格。所以,Manual 模式的设计重点是高效提取表格和文本结构,而非图片的内容。
4.2 Laws 处理器
Laws 处理器用于处理法律文本。
- 核心差异:与通用 PDF 处理相比,它唯一的特殊之处在于实现了 垂直合并逻辑。
- 垂直合并逻辑:
是否是否是否 排序文本块 遍历相邻块 是否跨页且无意义文本? 删除当前块 是否空文本块? 计算合并特征 满足禁止合并条件? 跳过合并 执行垂直合并
这种合并方式确保了法律条文、条款之间的关联性和完整性,避免被生硬地切断。
4.3 Book 处理器
与我们看到的其他处理器类似,Book 处理器也是通过 针对 PDF 的差异化处理 实现的。它在从 PDF 中提取文本后,会进行特定的段落合并和章节识别,以保持书籍的叙事结构。
4.4 Resume 处理器
简历处理器的处理方式非常独特。
- 处理流程:
- 首先,系统会调用一个 内部服务 将简历进行结构化处理,提取出姓名、联系方式、教育背景、工作经历等关键字段。
- 然后,基于这些结构化关键词,构建出分片的数据结构。
- 需要留意的坑:如果你进行源码部署,一定要确认这个“内部服务”是否已经正确配置和启动,否则简历解析功能可能无法正常工作。
4.5 QA 处理器
QA 处理器专门用于处理问答对格式的文档。
- 核心实现:它通过
beAdoc、beAdocPdf、beAdocDocx等辅助函数,根据文档的不同结构(PDF、DOCX或纯文本),解析出问题和答案。 - 关键代码:其中的
rmPrefix函数用于移除常见的问题/答案前缀(如“问题:”、“Answer:” “Q”、“A”等),确保解析出的内容干净。 - 结信息构:最终,每个分片都是一个结构化的问答对,包含问题(
Question:)和答案(Answer:)两部分。
def rmPrefix(txt):
return re.sub(
r"^(问题|答案|回答|user|assistant|Q|A|Question|Answer|问|答)[t:: ]+", "", txt.strip(), flags=re.IGNORECASE)
def beAdocPdf(d, q, a, eng, image, poss):
qprefix = "Question: " if eng else "问题:"
aprefix = "Answer: " if eng else "回答:"
d["content_with_weight"] = "t".join(
[qprefix + rmPrefix(q), aprefix + rmPrefix(a)])
d["content_ltks"] = rag_tokenizer.tokenize(q)
d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"])
d["image"] = image
add_positions(d, poss)
return d
def beAdocDocx(d, q, a, eng, image, row_num=-1):
qprefix = "Question: " if eng else "问题:"
aprefix = "Answer: " if eng else "回答:"
d["content_with_weight"] = "t".join(
[qprefix + rmPrefix(q), aprefix + rmPrefix(a)])
d["content_ltks"] = rag_tokenizer.tokenize(q)
d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"])
d["image"] = image
if row_num >= 0:
d["top_int"] = [row_num]
return d
def beAdoc(d, q, a, eng, row_num=-1):
qprefix = "Question: " if eng else "问题:"
aprefix = "Answer: " if eng else "回答:"
d["content_with_weight"] = "t".join(
[qprefix + rmPrefix(q), aprefix + rmPrefix(a)])
d["content_ltks"] = rag_tokenizer.tokenize(q)
d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"])
if row_num >= 0:
d["top_int"] = [row_num]
return d
4.6 Audio 处理器
音频处理器的实现非常简洁。
def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs):
doc = {
"docnm_kwd": filename,
"title_tks": rag_tokenizer.tokenize(re.sub(r".[a-zA-Z]+$", "", filename))
}
doc["title_sm_tks"] = rag_tokenizer.fine_grained_tokenize(doc["title_tks"])
eng = lang.lower() == "english"
try:
callback(0.1, "USE Sequence2Txt LLM to transcription the audio")
seq2txt_mdl = LLMBundle(tenant_id, LLMType.SPEECH2TEXT, lang=lang)
ans = seq2txt_mdl.transcription(binary)
callback(0.8, "Sequence2Txt LLM respond: %s ..." % ans[:32])
tokenize(doc, ans, eng)
return [doc]
except Exception as e:
callback(prog=-1, msg=str(e))
return []
- 工作流:首先,使用 语音模型(LLM) 将音频转录为文本。然后,将这个文本像普通文本一样进行分词和处理。
- 优点:这使得音频内容也能与系统中的其他文本数据一样,用于检索和分析。
4.7 Picture 处理器
图片处理器使用 OCR 技术 从图片中提取文本内容。
- 技术背景:它使用的是
deepdoc库。 - 识别效果评估:根据测试,其文本识别效果一般。
- 扩展思路:你可以考虑两种方案来提升图片处理能力:
- 方案一(改造源码):直接修改
picture.py的源码,在OCR识别后,增加一个 图片理解(Image Captioning) 模型,让模型描述“这个图片是什么”或“图片里表达的主要内容”,从而构建更丰富的检索信息。 - 方案二(外部预处理):在将图片上传到 RAGFlow 之前,在外部先进行图片描述(使用其他模型),然后将图片和描述文案一起作为输入,让系统将其视为图文结合的内容。
- 方案一(改造源码):直接修改
小提示:通过 OCR + 图片描述的方式,你可以构建一个强大的“图片检索系统”。
常见问题:我能否让 Picture 处理器同时输出 OCR 文本和图片描述?
答:可以,但需要通过修改源码
picture.py来实现。你可以注册一个图片理解模型,在提取 OCR 文本后,调用它生成图片描述,并将两者一起作为分片内容存入数据库。
5. 总结与展望
通过本次拆解,我们可以清晰地看到,RAGFlow 的切片引擎通过 策略模式,为不同类型的文档提供了差异化的分区方案。
- 通用处理器的优势:许多专用处理器(如 Manual、Laws、Book)的核心逻辑最后还是回归到对 PDF 的特定处理上。这意味着,如果我们在外部先将文档进行充分的结构化处理,很多专用类型其实可以被通用的
General模式所取代。 - 核心在于理解文档:RAG 系统的核心玩法在于对文档的深刻理解,而如何从文档中拆解出最合适、最精准的分片,是决定检索增强生成效果的关键。
- 底层的依赖:几乎所有处理器的底层实现最终都指向
deepdoc库中的几个核心文件。未来,我们可以针对这个底层库进行更细致的源码分析。
最后,值得一提的是,市面上也出现了一些专门用于文档理解与处理的专用模型,或许能为分片策略带来新的可能性,值得我们持续关注。
