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函数

上传文件
Notion导入
网站爬取
验证数据源类型
数据源分类处理
处理文件上传
处理Notion数据
处理网站数据
更新文档状态为: splitting
为获取的段落文档数据添加元数据

详细的实现逻辑如下:

  1. 验证数据源类型,数据源类型必须是: {“upload_file”, “notion_import”, “website_crawl”}这三种中的一种。
  2. 根据不同数据源类型来设置索引构建的参数,并调用索引构建器来构建索引,分为两步:

​ (1)设置文件内容提取的参数:也就是构建ExtractSetting对象

​ (2)调用对应索引构建器的extract函数:index_processor.extract来处理文档。

​ 这一步从文档中获取段落或页数据,并把文档段落内容保存到Document对象中。

  1. 更新数据库中文档的处理状态为:splitting
  2. 为从文本中提取出来的每个段落内容添加元数据:添加文档id(document_id)和数据集id(dataset_id)。

索引处理器:index_processor.extract

有2种索引处理器(index_processor):

  • ParagraphIndexProcessor:段落索引处理器。
  • QAIndexProcessor:文档索引处理器。
两种索引处理器的对比
  1. 分段处理方式
原始文档
ParagraphIndexProcessor
QAIndexProcessor
段落式分割
问答式分割
段落文档
QA对文档
  1. 主要特点对比
特性 ParagraphIndexProcessor QAIndexProcessor
分割粒度 段落级别 问答对级别
处理方式 直接分段 分段后转QA
检索效果 适合段落匹配 适合问答匹配
使用场景 文档检索 问答系统
索引结构 段落向量 QA对向量
处理开销 较低 较高(需LLM)
  1. 适用场景
  • ParagraphIndexProcessor**

    • 普通文档检索

    • 相似段落查找

    • 大规模文档索引

    • 处理速度更快

  • QAIndexProcessor

    • 问答系统构建

    • 精确答案提取

    • 专业领域QA

    • 生成更精确的问答对

这两种索引处理器都会调用ExtractProcessor.extract函数来进行处理。

ExtractProcessor.extract

在该函数中实现多种数据源和文件格式的统一内容提取处理。该函数的总体实现逻辑如下:

文件类型
Notion
网站
Unstructured
默认
接收提取设置
判断数据源类型
文件处理
Notion处理
网站处理
判断ETL类型
Unstructured文档处理
标准文档处理
选择文档内容提取器
执行提取

不同类型文档对应的文档内容提取器如下:

# 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 对象。实现的主要功能如下:

  1. 加载文件:
  • 使用 __init__ 方法初始化,接受一个 file_path 参数来指定 PDF 文件的路径,并可以接受一个可选的 file_cache_key 来指定缓存键。
  1. 提取文本:
  • extract 方法首先尝试从缓存中加载文件,如果存在则直接返回文档;否则读取并解析 PDF 文件。
  • 使用 load 方法惰性加载 PDF 文件为页面流,并将每个页面传递给 parse 方法进行解析。
  1. 解析文件内容:
  • parse 方法使用 pypdfium2 库来惰性解析 PDF 页面,提取文本并将其包装成 Document 对象。每个 Document 对象包含一个页码和页面内容
  1. 缓存处理:
  • 如果文件未在缓存中找到,则将解析后的文本保存到缓存中以供后续使用。
  1. 代码结构说明
  • __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的列表供后续对内容进行进一步处理。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐