dify实现分析-rag-文档内容提取
dify实现分析-rag-文档内容提取
概述
在文章《dify实现原理分析-上传文件创建知识库总体流程》中已经介绍了,文件上传后索引构建的总体流程,本文介绍其中的“Extract: 提取文档内容:这里会按段落或整页来获取文档内容”步骤的实现。
这一步的主要功能是:从不同格式的文档中提取文本内容,这里的格式包括:pdf、word、csv、html、txt、markdown、ppt等等。不同格式的文本,需要使用的文本获取的类和对象不同。
文档的内容获取是在IndexingRunner._extract函数中实现。该函数的声明如下:
def _extract(
self, index_processor: BaseIndexProcessor, dataset_document: DatasetDocument, process_rule: dict
) -> list[Document]:
IndexingRunner._extract函数
详细的实现逻辑如下:
- 验证数据源类型,数据源类型必须是: {“upload_file”, “notion_import”, “website_crawl”}这三种中的一种。
- 根据不同数据源类型来设置索引构建的参数,并调用索引构建器来构建索引,分为两步:
(1)设置文件内容提取的参数:也就是构建ExtractSetting对象
(2)调用对应索引构建器的extract函数:index_processor.extract
来处理文档。
这一步从文档中获取段落或页数据,并把文档段落内容保存到Document对象中。
- 更新数据库中文档的处理状态为:splitting
- 为从文本中提取出来的每个段落内容添加元数据:添加文档id(document_id)和数据集id(dataset_id)。
索引处理器:index_processor.extract
有2种索引处理器(index_processor):
- ParagraphIndexProcessor:段落索引处理器。
- QAIndexProcessor:文档索引处理器。
两种索引处理器的对比
- 分段处理方式
- 主要特点对比
特性 | ParagraphIndexProcessor | QAIndexProcessor |
---|---|---|
分割粒度 | 段落级别 | 问答对级别 |
处理方式 | 直接分段 | 分段后转QA |
检索效果 | 适合段落匹配 | 适合问答匹配 |
使用场景 | 文档检索 | 问答系统 |
索引结构 | 段落向量 | QA对向量 |
处理开销 | 较低 | 较高(需LLM) |
- 适用场景
-
ParagraphIndexProcessor**
-
普通文档检索
-
相似段落查找
-
大规模文档索引
-
处理速度更快
-
-
QAIndexProcessor
-
问答系统构建
-
精确答案提取
-
专业领域QA
-
生成更精确的问答对
-
这两种索引处理器都会调用ExtractProcessor.extract
函数来进行处理。
ExtractProcessor.extract
在该函数中实现多种数据源和文件格式的统一内容提取处理。该函数的总体实现逻辑如下:
不同类型文档对应的文档内容提取器如下:
# ETL类型判断
if etl_type == "Unstructured":
# Unstructured提取器映射
extractors = {
".xlsx": ExcelExtractor,
".pdf": PdfExtractor,
".md": UnstructuredMarkdownExtractor if is_automatic else MarkdownExtractor,
".docx": WordExtractor,
".csv": CSVExtractor,
".msg": UnstructuredMsgExtractor,
# ...更多格式支持
}
else:
# 标准提取器映射
extractors = {
".xlsx": ExcelExtractor,
".pdf": PdfExtractor,
".md": MarkdownExtractor,
# ...默认提取器
}
说明:这些文档内容提取器按提取内容的粒度大部分是以页为单位来进行提取并保存,其中csv是以行为单位来提取。提取到的内容保存到Document.page_content变量中,然后返回一个Document对象的列表,供后面的切割使用。
举例:pdf文本内容提取
PdfExtractor
类的的主要功能是从 PDF 文件中提取文本内容,并将其转换为一系列 Document
对象。实现的主要功能如下:
- 加载文件:
- 使用
__init__
方法初始化,接受一个file_path
参数来指定 PDF 文件的路径,并可以接受一个可选的file_cache_key
来指定缓存键。
- 提取文本:
extract
方法首先尝试从缓存中加载文件,如果存在则直接返回文档;否则读取并解析 PDF 文件。- 使用
load
方法惰性加载 PDF 文件为页面流,并将每个页面传递给parse
方法进行解析。
- 解析文件内容:
parse
方法使用pypdfium2
库来惰性解析 PDF 页面,提取文本并将其包装成Document
对象。每个Document
对象包含一个页码和页面内容。
- 缓存处理:
- 如果文件未在缓存中找到,则将解析后的文本保存到缓存中以供后续使用。
- 代码结构说明
__init__
: 初始化file_path
和可选的file_cache_key
。extract
: 主要提取逻辑,优先尝试从缓存加载文件,如果没有则读取并解析。load
: 懒惰加载 PDF 文件为页面流。parse
: 解析每个 PDF 页面,生成包含文本和元数据的Document
对象。
示例:
# 初始化 PdfExtractor 并提取文档
pdf_extractor = PdfExtractor(file_path="example.pdf", file_cache_key="cache_key")
documents = pdf_extractor.extract()
# 打印所有文档的内容
for doc in documents:
print(doc.page_content)
这段代码展示如何初始化 PdfExtractor
类并从指定的 PDF 文件中提取文档内容。
总结
本文分析了索引构建的文件内容提取部分的实现逻辑。通过本文的分析可知,dify会先对文档内容进行提取,然后再对提取的内容进行处理,最后再构建索引。对文件内容进行提取时,会根据文件格式选择不同的文件内容提取器,提取文件内容时,一般是按页来读取然后保存到Document对象的page_content变量中。最后,返回Document的列表供后续对内容进行进一步处理。