跳到主要内容

Doc Extractor 文档解析 ​

Doc Extractor 是 Agents-Flex 提供的统一文档内容提取能力。它可以从本地文件、HTTP URL、输入流或字节数组中识别文档类型,并将正文、表格和图片转换为适合大模型、RAG、全文检索和内容审查使用的 Markdown 风格文本。

无论输入是 Office 文档、PDF、网页、邮件、电子书、OpenDocument、代码文件,还是包含多个文档的压缩包,调用方都可以使用同一套 API。

Maven 依赖 ​

Doc Extractor 已从 agents-flex-core 中拆分为独立模块,需要显式引入:

xml
<dependency>
    <groupId>com.agentsflex</groupId>
    <artifactId>agents-flex-doc-extractor</artifactId>
    <version>最新版本</version>
</dependency>

公共 API 位于 com.agentsflex.doc 包中。

核心能力 ​

  • 统一输入接口:支持文件、HTTP、InputStream、byte[] 和自定义数据源。
  • 丰富的格式覆盖:覆盖 Microsoft Office、OpenDocument、PDF、RTF、HTML、邮件、EPUB、Apple iWork、压缩包、代码和配置文件。
  • Markdown 风格输出:保留标题、段落、表格、分页和幻灯片结构,内嵌图片可输出为 Base64 Data URI 或自定义存储 URL。
  • 自动类型识别:综合文件名、扩展名和 MIME 类型选择解析器,并支持候选解析器降级重试。
  • 复杂文档支持:可处理 Office 宏格式、模板格式、旧版二进制 Office 文件以及压缩包中的嵌套文档。
  • 面向大文件设计:HTTP 大文件自动落盘,PDF 使用内存与临时文件混合模式。
  • 可扩展架构:可以注册自定义 DocumentExtractor,也可以实现新的 DocumentSource。

快速开始 ​

使用便捷工具类 ​

java
String text = DocumentExtractors.extract(new File("/path/to/report.docx"));
System.out.println(text);

从 HTTP 地址读取:

java
String text = DocumentExtractors.extractFromUrl(
    "https://example.com/files/report.pdf"
);

当 URL 中没有可靠的文件名时,建议显式传入文件名和 MIME:

java
String text = DocumentExtractors.extractFromUrl(
    "https://example.com/download?id=1001",
    "report.docx",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);

使用服务实例 ​

需要注册自定义解析器或控制解析器集合时,使用 DocumentExtractionService:

java
DocumentExtractionService service = new DocumentExtractionService();

String fromFile = service.extract(new File("/path/to/report.pdf"));
String fromUrl = service.extractFromUrl("https://example.com/report.pdf");

从流和字节数组读取 ​

java
try (InputStream input = new FileInputStream("/path/to/data.tsv")) {
    String text = service.extract(
        input,
        "data.tsv",
        "text/tab-separated-values"
    );
}
java
byte[] bytes = Files.readAllBytes(Paths.get("/path/to/slides.pptx"));
String text = service.extract(
    bytes,
    "slides.pptx",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation"
);

文件名和 MIME 会参与解析器选择。处理流、字节数组或无扩展名下载地址时,应尽量提供准确的 fileName 和 mimeType。

支持格式 ​

Microsoft Word ​

类型扩展名解析能力
Word 97-2003 文档.doc段落、图片、Markdown 表格
Word 97-2003 模板.dot段落、图片、Markdown 表格
Word OOXML 文档.docx段落、图片、Markdown 表格
Word OOXML 模板.dotx段落、图片、Markdown 表格
Word 宏文档.docm与 DOCX 相同,宏不会执行
Word 宏模板.dotm与 DOCX 相同,宏不会执行

Microsoft Excel ​

类型扩展名解析能力
Excel 97-2003 工作簿.xls多 Sheet、公式计算、Markdown 表格
Excel 97-2003 模板.xlt多 Sheet、公式计算、Markdown 表格
Excel OOXML 工作簿.xlsx多 Sheet、公式计算、Markdown 表格
Excel 宏工作簿.xlsm与 XLSX 相同,宏不会执行
Excel OOXML 模板.xltx多 Sheet、公式计算、Markdown 表格
Excel 宏模板.xltm与 XLSX 相同,宏不会执行
Excel 二进制工作簿.xlsb通过 Apache Tika 提取可读内容

Microsoft PowerPoint ​

类型扩展名解析能力
PowerPoint 97-2003 演示文稿.ppt幻灯片文本、图片、Markdown 表格
PowerPoint 97-2003 放映文件.pps幻灯片文本、图片、Markdown 表格
PowerPoint 97-2003 模板.pot幻灯片文本、图片、Markdown 表格
PowerPoint OOXML 演示文稿.pptx幻灯片文本、图片、Markdown 表格
PowerPoint OOXML 放映文件.ppsx幻灯片文本、图片、Markdown 表格
PowerPoint OOXML 模板.potx幻灯片文本、图片、Markdown 表格
PowerPoint 宏演示文稿.pptm与 PPTX 相同,宏不会执行
PowerPoint 宏放映文件.ppsm与 PPTX 相同,宏不会执行
PowerPoint 宏模板.potm与 PPTX 相同,宏不会执行

PDF ​

类型扩展名解析能力
PDF 文档.pdf分页文本、实际绘制的图片、同页图片去重

PDF 输出包含 --- Page N --- 分页标记。图片统一转换为 PNG 后交给 DocumentImagePublisher 处理。纯扫描 PDF 可以提取页面中的图片,但当前不包含 OCR 文字识别。

OpenDocument ​

分类扩展名
文本文档.odt、.fodt
电子表格.ods、.fods
演示文稿.odp、.fodp
绘图文档.odg、.fodg
文本模板.ott
表格模板.ots
演示模板.otp
绘图模板.otg

OpenDocument 通过 Apache Tika 提取为 XHTML,再统一转换为 Markdown 风格文本。

富文本、网页和电子书 ​

分类扩展名说明
Rich Text Format.rtf提取富文本正文
HTML.html、.htm、.xhtml提取标题、段落、列表、链接和表格,并过滤常见网页噪音
MHTML.mhtml支持常见 HTML 内容,复杂 multipart 资源取决于文档结构
EPUB.epub按电子书内容顺序提取章节正文

邮件 ​

类型扩展名说明
RFC 822 邮件.eml提取主题、正文及可解析的嵌入内容
Outlook 邮件.msg提取 Outlook 消息正文和可解析内容

Apple iWork ​

类型扩展名
Pages 文档.pages
Numbers 表格.numbers
Keynote 演示文稿.key

iWork 通过 Apache Tika 的 Apple parser 处理。Apple 多次调整 iWork 内部 IWA 格式,因此不同应用版本的提取结果可能不同;部分现代文件可能只能获得元数据和包内资源信息。

压缩包 ​

类型扩展名
ZIP.zip
TAR.tar
GZip/TGZ.gz、.tgz
BZip2.bz2
XZ.xz
7-Zip.7z
RAR.rar

压缩包会递归解析其中受支持的文档,并在输出中保留条目文件名。为防止压缩炸弹,默认安全限制如下:

限制项默认值
最大嵌套深度3 层
最大条目数100
单个解压条目20 MB
累计解压内容100 MB

超过限制的条目会被跳过。加密压缩包需要外部提供密码,目前不会自动解密。

基础文本和结构化文本 ​

分类扩展名
基础文本.txt、.text、.log
Markdown.md、.markdown
表格文本.csv、.tsv
数据交换.json、.xml、.yml、.yaml
Java 配置.properties
通用配置.conf、.cfg、.config、.ini、.toml、.env
工程配置.editorconfig、.gitignore、.gitattributes

纯文本解析器支持 UTF-8、GBK、GB2312 等常见编码自动检测,并接受通用的 text/* MIME。

代码文件 ​

生态扩展名
JVM.java、.kt、.kts、.groovy、.scala、.gradle
Python/Ruby/PHP.py、.rb、.php
JavaScript/TypeScript.js、.mjs、.cjs、.jsx、.ts、.tsx
Web 框架.vue、.svelte
C/C++.c、.h、.cc、.cpp、.hpp
其他编译语言.cs、.go、.rs、.swift、.dart
脚本.sh、.bash、.zsh、.fish、.bat、.cmd、.ps1
数据库.sql
样式.css、.scss、.sass、.less
其他.lua、.r

同时支持常见的无扩展名工程文件:Dockerfile、Makefile、Jenkinsfile。

输出格式 ​

Doc Extractor 输出的是便于模型理解和后续切分的 Markdown 风格文本。

表格 ​

markdown
| Name | Score |
| --- | --- |
| Alice | 95 |

Word、PowerPoint、Excel、HTML 和部分 Tika 文档中的表格统一使用第一行作为表头,并输出 | --- | 分隔行。不同长度的行会补充空单元格,单元格内的反斜杠、管道符和换行会统一转义。

图片 ​

markdown
![Image](data:image/png;base64,iVBORw0KGgo...)

DOC、DOCX、PPT、PPTX、PDF,以及 Tika 支持的长尾文档中可识别的内嵌图片会转换为 Markdown 图片。默认使用 Data URI;生产环境通常通过 DocumentImagePublisher 替换为对象存储 URL。完整配置和扩展方式见下文“DocumentImagePublisher 图片处理”章节。

页面和幻灯片 ​

markdown
--- Page 1 ---
PDF page content

--- Slide 2 ---
Presentation content

分页和幻灯片边界可以直接用于 RAG 分块、引用定位和上下文展示。

Excel Sheet ​

markdown
### Sheet1

| Product | Amount |
| :--- | :--- |
| Service | 1000 |

DocumentImagePublisher 图片处理 ​

com.agentsflex.core.document.DocumentImagePublisher 是内嵌图片从“文档二进制数据”转换为“Markdown 可引用地址”的统一扩展点,Doc Extractor 和 OCR 共用该接口。解析器负责发现和读取图片,Publisher 负责 决定图片如何保存以及最终返回什么地址。

text
文档解析器 -> imageBytes + mimeType + fileName
          -> DocumentImagePublisher.publish(...)
          -> URL / Data URI / null
          -> ![Image](返回值)

适用场景 ​

场景建议处理方式
本地开发、小文档预览使用默认 DataUriDocumentImagePublisher,无需管理外部文件
RAG 入库、生产文档解析上传到 OSS、S3、COS 等对象存储并返回稳定 URL
不需要图片的纯文本检索返回 null,跳过所有图片,降低存储和 Token 开销
图片需要统一规范在 Publisher 中压缩、转码、生成缩略图或清理元数据
受控内容平台执行格式、大小、安全检查后再写入内部文件服务

默认构造的 DocumentExtractionService 使用 DataUriDocumentImagePublisher。它将图片编码为 data:<mimeType>;base64,...,适合直接预览,但会明显增大结果字符串和后续模型上下文。

接口契约 ​

java
package com.agentsflex.core.document;

@FunctionalInterface
public interface DocumentImagePublisher {
    String publish(byte[] imageBytes, String mimeType, String fileName)
        throws IOException;
}
参数或返回值说明
imageBytes从文档中提取的图片二进制数据
mimeType图片 MIME 类型;无法识别时为 application/octet-stream
fileName文档内的图片文件名;原格式不提供文件名时为 embedded-image
返回 URL 或 Data URI解析器将其输出为 ![Image](...)
返回 null 或空字符串跳过该图片,不输出 Markdown 图片标记
抛出 IOException当前候选提取器失败,服务记录日志并尝试下一个候选提取器

Publisher 只决定 Markdown 中的图片地址,不负责生成图片说明或 OCR 文本。

上传到对象存储 ​

java
DocumentImagePublisher imagePublisher = (imageBytes, mimeType, fileName) -> {
    String objectKey = imageKeyGenerator.fromContent(imageBytes, fileName);
    return objectStorage.upload(objectKey, imageBytes, mimeType);
};

DocumentExtractionService service = new DocumentExtractionService(imagePublisher);
String markdown = service.extract(new File("/path/to/report.docx"));

当 Publisher 返回 https://cdn.example.com/files/image-1.png 时,结果中会写入:

markdown
![Image](https://cdn.example.com/files/image-1.png)

建议根据图片内容摘要生成对象 Key。候选提取器降级、调用方重试或同一图片重复出现时,Publisher 可能处理相同图片多次;使用内容摘要可以让上传操作保持幂等。

跳过图片 ​

只需要正文和表格时,可以显式忽略图片:

java
DocumentExtractionService service = new DocumentExtractionService(
    (imageBytes, mimeType, fileName) -> null
);

配置范围 ​

构造服务实例时配置,适合不同租户或不同解析任务采用不同存储策略:

java
DocumentExtractionService service = new DocumentExtractionService(imagePublisher);

也可以在服务创建后替换:

java
service.setDocumentImagePublisher(imagePublisher);

自定义注册中心和 Publisher 可以同时传入:

java
DocumentExtractionService service =
    new DocumentExtractionService(registry, imagePublisher);

静态入口支持进程级默认配置:

java
DocumentExtractors.setDocumentImagePublisher(imagePublisher);
String markdown = DocumentExtractors.extract(file);

DocumentExtractors 的配置由整个进程共享,不适合保存租户级或请求级状态。存在多种图片策略时,应创建独立的 DocumentExtractionService。

自定义提取器接入 ​

自定义 DocumentExtractor 只有覆盖包含 Publisher 的重载,才能使用服务中配置的图片策略。图片应通过 MarkdownFormatter 处理,以获得一致的空值、默认 MIME、默认文件名和 Markdown 格式:

java
public class CustomExtractor implements DocumentExtractor {

    @Override
    public boolean supports(DocumentSource source) {
        return "custom".equalsIgnoreCase(getExtension(source.getFileName()));
    }

    @Override
    public String extractText(DocumentSource source) throws IOException {
        return extractText(source, new DataUriDocumentImagePublisher());
    }

    @Override
    public String extractText(DocumentSource source,
                              DocumentImagePublisher imagePublisher) throws IOException {
        byte[] image = readEmbeddedImage(source);
        StringBuilder output = new StringBuilder("Document content");
        MarkdownFormatter.appendImage(
            output, imagePublisher, image, "image/png", "figure-1.png"
        );
        return output.toString();
    }
}

不处理图片的自定义提取器只需实现单参数的 extractText(DocumentSource)。

生产环境注意事项 ​

  • Publisher 可能被多个解析请求并发调用,实现必须是线程安全的,或只使用方法内局部状态。
  • fileName 和 mimeType 来自文档元数据,不能直接作为可信文件路径或安全校验依据;应清理文件名并校验实际文件签名。
  • 图片以完整 byte[] 传入,应限制原始文档和单张图片大小,避免超大图片造成内存压力。
  • 返回值会直接进入 Markdown 图片地址,应返回经过正确编码的 URL 或合法 Data URI。
  • 上传失败可以抛出 IOException。由于服务随后可能尝试其他候选提取器,上传、计费和审计操作应设计为幂等。
  • Publisher 不会自动执行 OCR;扫描件文字识别需要单独的 OCR 流程。

工作原理 ​

一次解析请求会经过以下流程:

  1. DocumentSource 提供文件名、MIME 和可重复打开的输入流。
  2. ExtractorRegistry 根据文件名和 MIME 筛选候选解析器。
  3. 候选解析器按 getOrder() 从小到大依次执行。
  4. 解析器发现内嵌图片时,将图片数据交给 DocumentImagePublisher,并把返回地址写入 Markdown。
  5. 第一个返回非空内容的解析器结束本次处理。
  6. 所有解析器均失败或返回空内容时,服务返回 null。
  7. 请求结束后自动调用 DocumentSource.cleanup() 清理临时资源。

默认注册的解析器包括:

解析器主要职责
PdfTextExtractorPDF 文字、分页和图片
DocExtractorWord 97-2003
DocxExtractorWord OOXML、宏格式和模板
PptExtractorPowerPoint 97-2003
PptxExtractorPowerPoint OOXML、宏格式和模板
ExcelExtractorXLS/XLSX、宏格式和模板
TikaDocumentExtractorRTF、OpenDocument、邮件、EPUB、XLSB、iWork 和压缩包
HtmlExtractorHTML 内容清洗和 Markdown 转换
PlainTextExtractor文本、代码、配置和结构化文本

DocumentSource ​

内置数据源 ​

数据源用途
FileDocumentSource本地文件
HttpDocumentSourceHTTP/HTTPS 文件,支持缓存和临时文件
ByteArrayDocumentSource已加载到内存的字节数组
ByteStreamDocumentSource输入流,内部缓存后允许解析器重复打开
TemporaryFileStreamDocumentSource将大输入流保存为临时文件,默认最大 100 MB

HTTP 下载配置 ​

HttpDocumentSource 默认连接超时为 20 秒、读取超时为 60 秒。已知长度且不超过 10 MB 的文件使用内存缓存,其他文件使用临时文件,并在解析结束后清理。

java
HttpDocumentSource source = new HttpDocumentSource(
    "https://example.com/private/report.pdf",
    "report.pdf",
    "application/pdf",
    10_000,
    60_000,
    connection -> connection.setRequestProperty(
        "Authorization",
        "Bearer " + accessToken
    )
);

String text = service.extract(source);

获取已缓存的数据大小:

java
long size = source.getCachedSize();

自定义数据源 ​

可以通过实现 DocumentSource 接入对象存储、数据库、内部文件系统或其他数据平台:

java
public class ObjectStorageDocumentSource implements DocumentSource {

    private final String objectKey;

    public ObjectStorageDocumentSource(String objectKey) {
        this.objectKey = objectKey;
    }

    @Override
    public String getFileName() {
        return objectKey;
    }

    @Override
    public String getMimeType() {
        return "application/pdf";
    }

    @Override
    public InputStream openStream() {
        return objectStorageClient.openStream(objectKey);
    }

    @Override
    public void cleanup() {
        // 按需释放临时文件、连接或其他资源
    }
}

openStream() 可能被多个候选解析器调用,因此自定义数据源应返回新的、可独立读取的流。

自定义解析器 ​

实现 DocumentExtractor 并注册到 ExtractorRegistry:

java
public class CustomExtractor implements DocumentExtractor {

    @Override
    public boolean supports(DocumentSource source) {
        return "custom".equalsIgnoreCase(getExtension(source.getFileName()));
    }

    @Override
    public String extractText(DocumentSource source) throws IOException {
        try (InputStream input = source.openStream()) {
            byte[] bytes = IOUtils.toByteArray(input, 10 * 1024 * 1024);
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new IOException("Failed to parse custom document", e);
        }
    }

    @Override
    public int getOrder() {
        return 20;
    }
}

注册并创建服务:

java
ExtractorRegistry registry = new ExtractorRegistry();
registry.register(new CustomExtractor());

DocumentExtractionService service = new DocumentExtractionService(registry);

getOrder() 数值越小,尝试顺序越靠前。自定义解析器不应执行文档中的宏、脚本或外部命令。

自定义解析器需要输出表格或处理内嵌图片时,应复用 MarkdownFormatter.appendTable(...)、MarkdownFormatter.handleImage(...) 和 MarkdownFormatter.appendImage(...),以保持与内置解析器一致的 Markdown、空值和元数据处理规则。

错误处理 ​

DocumentExtractionService 会记录候选解析器及失败原因,并尝试下一个候选解析器。

场景结果
DocumentSource 为 null抛出 IllegalArgumentException
没有支持该格式的解析器返回 null
所有候选解析器失败返回 null,并记录 WARN 日志
解析器成功但没有可读内容继续尝试其他候选,最终可能返回 null

生产环境应检查返回值:

java
String text = service.extract(file);
if (text == null || text.trim().isEmpty()) {
    // 记录失败、进入人工处理或 OCR 流程
}

安全与性能 ​

  • Office 宏格式只读取文档内容,不会执行宏。
  • PDF 使用 32 MB 内存阈值和临时文件混合加载,降低大文件堆内存压力。
  • PDF 单张图片默认限制为 5000 万像素,异常超大图片会被跳过。
  • 压缩包限制嵌套深度、条目数量和解压大小,降低 ZIP bomb 风险。
  • HTTP 内容仅下载一次;小文件缓存在内存,大文件写入临时文件。
  • 处理不可信文件时,仍建议在隔离进程中设置请求超时、堆内存上限和文件大小上限。
  • ByteStreamDocumentSource 会将整个流缓存到内存;大流优先使用 TemporaryFileStreamDocumentSource。

已知边界 ​

  • 扫描 PDF 和普通图片尚未集成 OCR;PDF 中没有文本层时主要返回图片。
  • PDF 通常没有语义化表格结构,基础解析器不会对任意坐标文本强行推断表格。
  • 加密 Office、PDF、EPUB 或压缩包需要密码时,默认解析可能失败。
  • Apple iWork 内部格式随应用版本变化,部分现代文件只能提取元数据和资源列表。
  • MHTML 的复杂 multipart、内嵌脚本和动态网页行为不会完整还原。
  • Tika 长尾格式以内容提取为目标,不保证完全保留源文档视觉布局。
  • 默认的 Base64 图片会显著增加输出长度;生产环境建议配置 DocumentImagePublisher,在解析过程中直接替换为对象存储 URL。

推荐实践 ​

  1. 对流和 HTTP 下载尽量提供准确的文件名及 MIME。
  2. 大文件使用临时文件数据源,并在入口处限制原始文件大小。
  3. 将返回的分页、幻灯片或 Sheet 标记作为 RAG 分块边界。
  4. 使用 DocumentImagePublisher 在解析图片时直接上传到对象存储,避免在最终文本中保留 Base64 数据。
  5. 对扫描件配置独立 OCR 流程,并与 Doc Extractor 的文本结果合并。
  6. 对 null 结果保留原文件和解析日志,便于降级处理与格式补充。

从 File2Text 迁移 ​

Doc Extractor 是原 agents-flex-core 内 File2Text 功能的独立模块。升级时需要同时修改 Maven 依赖、包名和入口 API:

旧 API新 API
com.agentsflex.core.file2text.File2TextServicecom.agentsflex.doc.DocumentExtractionService
com.agentsflex.core.file2text.File2TextUtilcom.agentsflex.doc.DocumentExtractors
FileExtractorDocumentExtractor
readFromFile(...) / extractTextFromFile(...)extract(...)
readFromHttpUrl(...) / extractTextFromHttpUrl(...)extractFromUrl(...)
extractTextFromSource(...)extract(...)

旧包不再由 agents-flex-core 提供。只使用 Chat、Prompt、Tool、Memory 等核心能力的项目不再传递引入 POI、PDFBox、Tika、JSoup 和 ICU4J。