type
Post
status
Published
date
Dec 4, 2024
slug
summary
深入剖析Qanything是如何拆解PDF的,核心是pdf转markdown
tags
category
技术分享
icon
password
前言:
在前面的文章中探究了图片是怎么进行解析的,这篇文章对rag中最常见的pdf文件进行剖析,看看Qanything是如何将pdf中的内容进行向量化的。
- 调用sanic_api的接口,只是将文件上传到服务所在的服务器以及增加了一条记录在数据库中,设置status:gray(File表),会有一个额外的文件解析服务,服务地址:
qanything_kernel.dependent_server.insert_files_serve.insert_files_server这个服务负责轮训数据库,每次拿到一条gray状态的数据,File表更新这条记录状态成yellow,然后开始处理这个pdf文件,执行process_data方法。
- 根据user_id,kb_id,file_id这些信息创建一个LocalFileForInsert对象:
local_file = LocalFileForInsert(user_id, kb_id, file_id, file_location, file_name, file_url, chunk_size, mysql_client)process_data 的两个核心方法:核心方法一:local_file.split_file_to_docs
- 判断下是什么文件,这里自然是pdf文件了,然后走pdf解析服务的逻辑。
以这个文件为例,这个文件有14页,有多个表格,但是没有图片:
markdown_file是一个类似于的文件路径。如下:/tmp/pycharm_project_419/QANY_DB/content/feifeixu__1234/KBb17bd2d168604a84a59abe24e855d574_240625/07a07daec2ac42d497164f7644b7a7fb/九方财富员工考勤与假期管理制度-九方财富管理202412号_md/九方财富员工考勤与假期管理制度-九方财富管理202412号.md
打开这个文件看到如下内容:
上面的pdf抽成的mardown内容如下:
docs内容 = convert_markdown_to_langchaindoc(markdown_file) docs类型列表,长度30
docs内容 = self.markdown_process(docs) docs类型列表,长度30,这一步就是将每个doc前面加上了一级标题、二级标题这些。

pdf转markdown之后得到了30个docs,接下来会执行
self.inject_metadata(docs) 方法,故名思义这个方法就是给每个doc注入一些元数据,比如user_id、file_id这些,得到一个new_docs,相比与docs,page_content的内容都没有变,就是多了很多属性。
但是注意这个方法内部有一个merge_doc的操作(合并doc),具体逻辑如下:
代码截图:

逐行解释
下面是合并完的merged_docs结果,docs的长度变成了18
这18个docs进入下面的核心方法二进行处理
核心方法二:将doc编码成向量存入milvus向量数据库中retriever.insert_documents(local_file.docs, chunk_size)
retriever是个啥
retriever = ParentRetriever(milvus_kb, mysql_client, es_client)ParentRetriever类定义在qanything_kernel/core/retriever/parent_retriever.py中,这个类有两个核心方法一个是
insert_documents,另一个是:get_retrieved_documents这里执行的就是
insert_documents 在
insert_documents 中最终通过这个方法将向量插入milvus中return await self.retriever.aadd_documents(docs, parent_chunk_size=parent_chunk_size, es_store=self.es_store, ids=ids, single_parent=single_parent)self.retriever.aadd_documents 方法介绍
self.retriever 是SelfParentRetriever的类对象,这个类也在qanything_kernel/core/retriever/parent_retriever.py中aadd_documents方法介绍
方法截图:

+逐行解析
24个docs如下
self.vectorstore.aadd_documents方法内部解析
24个docs插入了向量数据库中,并且那18个doc在mysql File表中留下了一份记录。
到这里两个核心方法就介绍完了
done!
📝 PDF解析服务
pdf解析可选两种方式:
- 启动pdf解析服务,启动服务之前需要将用到的模型文件存放到相应的位置。
模型文件下载地址:
QAnything PDF解析相关模型
,将模型加载到如下图所示文件夹中。
QAnything PDF解析相关模型
QAnythingPDF解析相关的模型,包含OCR、版式分析、表格解析等

启动命令:
debug启动方式(pycharm中配置):

这个接口接收一个pdf文件路径,返回一个处理好的md文件路径
本来想详细介绍一下pdf-to-markdown的内部逻辑的,但是太复杂了,提取标题大小位置,提取表格信息等,涉及到的逻辑稍显复杂,我们就知道通过这一套逻辑,能将一个pdf转成md格式就行了
github上有一个marker项目,也是做这个工作的,应该会比qanything这套逻辑效果更好
还是上面那个pdf,看一下转换后的markdown内容
- 快速pdgf解析,使用
fitz库进行解析(不详细介绍了,效果不好)
会优先选择使用pdf解析服务解析pdf文档,如果pdf解析服务没有启动成功,那么会采用快速pdf解析的方式,比较粗暴。就是一行一行的文字,没有标题的概念,表格的格式也是乱七八糟的。这种方式肯定没有转markdown的检索效果好。在1.x版本中使用的是这种方式。
🤗 总结归纳
- sanic_api upload_files接口将上传一个pdf文件,文件保存在服务器,并且在mysql File表中增加一条记录
- 启动文件解析服务,debug的方式启动看变量过程

- 文件解析服务从File表轮训status=gray类型的数据,执行process_data方法。process_data内部包含两个核心方法。
split_file_to_docs- 拿到这个pdf文件,调用pdf解析服务的接口
- md内容转doc,得到了30个docs
- docs合并,将一些比较短的doc和之前的doc进行合并,长度在400左右,如果超过400的doc比如800,就不合并了,合并完变成了18个docs
insert_documents对docs进行embedding并存入milvus向量数据库中- 遍历18个doc,对长度太长的doc(这个doc称为父doc)进行子doc拆分,子doc和子doc之间有100的overlap,拆分后的子doc长度接近400,拆分成了24个docs,这24个docs的长度都不到400
- 起embedding服务
- 对24个docs进行embedding,得到24个768维的向量
- 24个子docs存milvus数据库
- 18个父docs存mysql Documents表
docs = convert_markdown_to_langchaindoc(markdown_file)
docs = self.markdown_process(docs)
done!
📎 参考文章
- github tag2.0源码
有问题,欢迎您在底部评论区留言,一起交流~