返回顶部
热门问答 更多热门问答
技术文章 更多技术文章

深度拆解RAGFlow分片引擎之切片实现

[复制链接]
链载Ai 显示全部楼层 发表于 昨天 17:59 |阅读模式 打印 上一主题 下一主题

ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">上一篇深度拆解RAGFlow分片引擎!3大阶段+视觉增强,全网最硬核架构解析讲了切片的整体流程,今天我们来拆下切片的实现。

ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;color: rgb(63, 63, 63);">ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;margin: 0.1em auto 0.5em;border-radius: 8px;box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 8px;" title="null"/>

我们在设置的时候,可以选择切片方法。这个参数是ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-feature-settings: normal;font-variation-settings: normal;font-size: 12.6px;text-align: left;line-height: 1.75;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;">parser_id
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;color: rgb(63, 63, 63);">ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;margin: 0.1em auto 0.5em;border-radius: 8px;box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 8px;" title="null"/>

在创建知识库的时候,选择对应的切片方法以后,我们可以看到右侧的切片介绍。
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-feature-settings: normal;font-variation-settings: normal;font-size: 14px;margin: 10px 8px;color: rgb(224, 226, 228);background: rgb(40, 43, 46);text-align: left;line-height: 1.5;overflow-x: auto;border-radius: 8px;box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 10px inset;padding: 0px !important;">asyncdefbuild_chunks(task, progress_callback): 
# 根据配置获取到切片实现(策略)
chunker = FACTORY[task["parser_id"].lower()]
asyncwithchunk_limiter:
cks =awaittrio.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"]))
    ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;color: rgb(63, 63, 63);" class="list-paddingleft-1">
  • ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-indent: -1em;display: block;margin: 0.5em 8px;color: rgb(63, 63, 63);">
    • 在上面的代码里根据配置ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-feature-settings: normal;font-variation-settings: normal;font-size: 12.6px;text-align: left;line-height: 1.75;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;">parser_idFACTORY中获取到对应的实现文件
  • • 注意chunker.chunk调用对应实现文件中的chunk方法
fromrag.appimportlaws, 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对应的 实现,就是一个配置映射,根据前端的配置,然后映射到对应的方法
  • • 我们可以看到对应的是从rag.app导入的

    看下代码结构,都是对应的类文件。引入的类文件每个都有一个相同的chunk方法

这块代码就是一个典型的策略模式实现。

这里要吐槽下python的隐式接口,不是自己写的代码,一不小心得来回翻几遍代码。等我过两天给它接口显式实现。

整块代码逻辑如下:

策略工厂FACTORYgeneral/naivepapertable...分片请求parser_id参数naive.pypaper.pytable.py...其他处理器统一chunk方法接口执行具体分片逻辑返回结构化分片数据

在上一篇中我们简单的画了下naive的处理流程,也就是前端选择的general。我把流程复制过来。

DOCXPDFExcelTXT/CodeMarkdownHTML/JSON是否输入文件格式判断DOCX解析器PDF解析器+布局识别表格解析器文本分割器MD表格提取结构化解析原始分片生成是否视觉增强?视觉模型处理图表基础分片处理分片合并Token化处理输出结构化分片

通用方法里,针对不同的文件类型,有对应的实现。

接下来,我们拆解几个定向的分片实现。

Manual


前端显示仅支持pdf,后端代码支持pdfdocx。这块代码的整体处理逻辑如下
PDFDOCX文件输入文件类型判断PDF解析器DOCX解析器OCR+布局分析表格识别段落结构解析表格HTML转换分块处理Token化输出

manual中,并没有抽取图片,只抽取了表格,而且类似的代码写了两遍。

我又对比了manualnaive下pdf的处理代码。
  • manual中注重的是文档结构化,其他的并没有增强
  • • 反而在naive模式下,通过视觉模型对图片进行了增强
  • • 所以manual只适合没有图片的,有表格的pdf

laws

  • • 法律文本的处理,在pdf上 处理上,唯一特殊的地方只有一个垂直合并

合并逻辑如下:

是否是否是否排序文本块遍历相邻块是否跨页且无意义文本?删除当前块是否空文本块?计算合并特征满足禁止合并条件?跳过合并执行垂直合并

book

我们看了几个,特殊场景的处理,其实最后都是通过pdf的差异化处理实现的。

resume 简历

  • • 首先通过内部服务,会进行简历的处理,通过上下文,可以看到是对简历进行了结构化处理。

    这个需要注意下,如果你源码部署,一定要注意这个,否则就趟坑了。


然后通过结构化的关键词,构建一个分片的数据结构。

qa


defrmPrefix(txt):
returnre.sub(
r"^(问题|答案|回答|user|assistant|Q|A|Question|Answer|问|答)[\t:: ]+","", txt.strip(), flags=re.IGNORECASE)


defbeAdocPdf(d, q, a, eng, image, poss):
qprefix ="Question: "ifengelse"问题:"
aprefix ="Answer: "ifengelse"回答:"
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)
returnd


defbeAdocDocx(d, q, a, eng, image, row_num=-1):
qprefix ="Question: "ifengelse"问题:"
aprefix ="Answer: "ifengelse"回答:"
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
ifrow_num >=0:
d["top_int"] = [row_num]
returnd


defbeAdoc(d, q, a, eng, row_num=-1):
qprefix ="Question: "ifengelse"问题:"
aprefix ="Answer: "ifengelse"回答:"
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"])
ifrow_num >=0:
d["top_int"] = [row_num]
returnd

我们可以看到qa就是根据不同的结构解析出来问答对。

audio

defchunk(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"])

# is it English
eng = lang.lower() =="english"# is_english(sections)
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]
exceptExceptionase:
callback(prog=-1, msg=str(e))

return[]

这块的代码更简单,直接通过语音模型转成了文本,然后再进行处理。

picture

图片的解析是使用OCR处理,所以识别到的是图片上的文本内容。使用的是deepdoc之前测试,识别效果很一般。

图片识别有两种,一种是识别图片中的文本内容,一种是通过图片描述这个图片是什么。我们可以通过扩展,ocr+图片描述构建一个图片检索系统。

两种实现方案:

  • • 直接改这块的源码,添加图片理解反推
  • • 在外面将图片反推后,构建图片描述,后续我基于这个写个案例

后记

通过代码发现,专用处理有时候也蛮鸡肋的,如果我们在外面将文档都结构化了,很多通过一些分片策略,我们可以忽略一些专用类型。

底层的处理最后都是deepdoc中的几个文件。后续会针对这个再做一些源码分析。

rag玩的是对文档的了解,怎么能拆解出合适的分片,这个是关键。

市面上应该有一些处理文档的专有模型,到时候找下看看。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

链载AI是专业的生成式人工智能教程平台。提供Stable Diffusion、Midjourney AI绘画教程,Suno AI音乐生成指南,以及Runway、Pika等AI视频制作与动画生成实战案例。从提示词编写到参数调整,手把手助您从入门到精通。
  • 官方手机版

  • 微信公众号

  • 商务合作

  • Powered by Discuz! X3.5 | Copyright © 2025-2025. | 链载Ai
  • 桂ICP备2024021734号 | 营业执照 | |广西笔趣文化传媒有限公司|| QQ