news 2026/5/1 8:02:23

文档处理能力分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
文档处理能力分析

1. 工程结构概览

Spring AI 提供了完整的文档处理能力,包括文档读取、文本分块和预处理。这些能力是 RAG 应用的基础。

document-readers/ # 文档读取器

├── pdf-reader/ # PDF 读取器

│ ├── PagePdfDocumentReader.java # 按页读取

│ └── ParagraphPdfDocumentReader.java # 按段落读取

├── markdown-reader/ # Markdown 读取器

│ └── MarkdownDocumentReader.java

├── tika-reader/ # 通用文档读取器(Tika)

│ └── TikaDocumentReader.java

└── jsoup-reader/ # HTML 读取器

└── JsoupDocumentReader.java

spring-ai-commons/ # 核心处理能力

├── document/

│ └── Document.java # 文档对象

└── transformer/

└── splitter/ # 文本分块

├── TextSplitter.java

├── TokenTextSplitter.java

└── CharacterTextSplitter.java

2. 技术体系与模块关系

文档处理流程:读取 → 分块 → 嵌入 → 存储

image.png

3. 关键场景示例代码

3.1 PDF 文档读取

PDF 读取支持按页和按段落两种方式:

// 按页读取

Resource pdfResource = new ClassPathResource("document.pdf");

PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(pdfResource);

List<Document> documents = pdfReader.get();

// 按段落读取(更智能)

ParagraphPdfDocumentReader paragraphReader =

new ParagraphPdfDocumentReader(pdfResource, config);

List<Document> documents = paragraphReader.get();

3.2 Markdown 文档读取

Markdown 读取器可以按标题、段落或水平线分组:

MarkdownDocumentReader markdownReader =

new MarkdownDocumentReader("classpath:docs/*.md", config);

List<Document> documents = markdownReader.get();

3.3 Tika 通用读取

Tika 可以读取多种格式(PDF、Word、PPT 等):

TikaDocumentReader tikaReader =

new TikaDocumentReader("classpath:document.docx");

List<Document> documents = tikaReader.get();

3.4 文档分块

将长文档分割成适合嵌入的小块:

// Token 分块(推荐)

TokenTextSplitter splitter = TokenTextSplitter.builder()

.chunkSize(800) // 目标 token 数

.minChunkSizeChars(350) // 最小字符数

.build();

List<Document> chunks = splitter.split(documents);

// 字符分块

CharacterTextSplitter charSplitter = new CharacterTextSplitter(1000, 200);

List<Document> chunks = charSplitter.split(documents);

3.5 完整流程

文档处理的完整流程:

// 1. 读取文档

TikaDocumentReader reader = new TikaDocumentReader("document.pdf");

List<Document> documents = reader.get();

// 2. 分块

TokenTextSplitter splitter = new TokenTextSplitter();

List<Document> chunks = splitter.split(documents);

// 3. 嵌入并存储

vectorStore.add(chunks);

4. 核心实现图

4.1 文档处理流程

image.png

5. 入口类与关键类关系

image.png

6. 关键实现逻辑分析

6.1 PDF 读取实现

PDF 读取有两种方式:

方式一:按页读取

public class PagePdfDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

List<Document> documents = new ArrayList<>();

int pageCount = document.getNumberOfPages();

for (int i = 0; i < pageCount; i++) {

String pageText = extractTextFromPage(i);

Document doc = new Document(pageText);

doc.getMetadata().put("page", i);

documents.add(doc);

}

return documents;

}

}

方式二:按段落读取(更智能)

public class ParagraphPdfDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

// 1. 提取段落

List<Paragraph> paragraphs = paragraphManager.flatten();

// 2. 将相邻段落合并为文档

List<Document> documents = new ArrayList<>();

for (int i = 0; i < paragraphs.size(); i++) {

Paragraph from = paragraphs.get(i);

Paragraph to = (i + 1 < paragraphs.size())

? paragraphs.get(i + 1)

: from;

String text = getTextBetweenParagraphs(from, to);

Document doc = new Document(text);

addMetadata(from, to, doc);

documents.add(doc);

}

return documents;

}

}

按段落读取的优势:

保持语义完整性:段落是自然的语义单元

更好的检索效果:段落级别的文档更适合向量搜索

保留布局信息:可以保留 PDF 的布局结构

6.2 Markdown 读取实现

Markdown 读取器使用 CommonMark 解析器:

public class MarkdownDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

List<Document> documents = new ArrayList<>();

for (Resource resource : markdownResources) {

// 1. 解析 Markdown

Node document = parser.parse(loadContent(resource));

// 2. 访问文档节点

DocumentVisitor visitor = new DocumentVisitor(config);

document.accept(visitor);

// 3. 收集文档

documents.addAll(visitor.getDocuments());

}

return documents;

}

}

Markdown 读取器可以按以下方式分组:

按标题分组:每个标题及其内容成为一个文档

按段落分组:每个段落成为一个文档

按水平线分组:水平线分隔的内容成为独立文档

6.3 Tika 通用读取实现

Tika 使用自动检测解析器:

public class TikaDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

try (InputStream stream = resource.getInputStream()) {

// 1. 自动检测文档类型并解析

parser.parse(stream, handler, metadata, context);

// 2. 提取文本

String text = handler.toString();

// 3. 格式化文本

text = textFormatter.format(text);

// 4. 创建文档

Document doc = new Document(text);

doc.getMetadata().put(METADATA_SOURCE, resourceName());

return List.of(doc);

}

}

}

Tika 的优势:

支持多种格式:PDF、Word、PPT、Excel、HTML 等

自动检测:无需指定文档类型

提取元数据:自动提取文档的元数据

6.4 文本分块实现

文本分块是 RAG 应用的关键步骤:

public abstract class TextSplitter implements DocumentTransformer {

@Override

public List<Document> apply(List<Document> documents) {

List<Document> chunks = new ArrayList<>();

for (Document doc : documents) {

// 1. 分割文本

List<String> textChunks = splitText(doc.getText());

// 2. 为每个分块创建文档

for (int i = 0; i < textChunks.size(); i++) {

Map<String, Object> metadata = new HashMap<>(doc.getMetadata());

// 3. 添加分块元数据

metadata.put("parent_document_id", doc.getId());

metadata.put("chunk_index", i);

metadata.put("total_chunks", textChunks.size());

Document chunk = Document.builder()

.text(textChunks.get(i))

.metadata(metadata)

.score(doc.getScore())

.build();

chunks.add(chunk);

}

}

return chunks;

}

protected abstract List<String> splitText(String text);

}

6.5 Token 分块实现

Token 分块使用编码器计算 token 数:

public class TokenTextSplitter extends TextSplitter {

@Override

protected List<String> splitText(String text) {

// 1. 编码为 tokens

List<Integer> tokens = encoding.encode(text).boxed();

List<String> chunks = new ArrayList<>();

while (!tokens.isEmpty() && chunks.size() < maxNumChunks) {

// 2. 取目标大小的 tokens

List<Integer> chunk = tokens.subList(0,

Math.min(chunkSize, tokens.size()));

String chunkText = decodeTokens(chunk);

// 3. 在标点符号处截断(保持语义)

int lastPunctuation = findLastPunctuation(chunkText);

if (lastPunctuation > minChunkSizeChars) {

chunkText = chunkText.substring(0, lastPunctuation + 1);

}

// 4. 过滤太短的分块

if (chunkText.length() > minChunkLengthToEmbed) {

chunks.add(chunkText.trim());

}

// 5. 移除已处理的 tokens

tokens = tokens.subList(getEncodedTokens(chunkText).size(),

tokens.size());

}

return chunks;

}

}

Token 分块的优势:

精确控制大小:按 token 数分割,而不是字符数

保持语义:在标点符号处截断

适合嵌入模型:token 数是嵌入模型的输入单位

7. 文档分块策略

7.1 Token 分块(推荐)

适合大多数场景,特别是使用 OpenAI 等基于 token 的模型:

TokenTextSplitter splitter = TokenTextSplitter.builder()

.chunkSize(800) // 目标 token 数

.minChunkSizeChars(350) // 最小字符数(避免过小)

.minChunkLengthToEmbed(5) // 最小嵌入长度

.maxNumChunks(10000) // 最大分块数

.keepSeparator(true) // 保留分隔符

.build();

7.2 字符分块

适合固定大小的分块需求:

CharacterTextSplitter splitter = new CharacterTextSplitter(

1000, // chunkSize

200 // chunkOverlap(重叠部分,保持上下文)

);

7.3 自定义分块

可以实现自己的分块策略:

public class CustomTextSplitter extends TextSplitter {

@Override

protected List<String> splitText(String text) {

// 自定义分块逻辑

// 例如:按句子、按段落、按章节等

return customSplit(text);

}

}

8. 外部依赖

不同读取器的依赖:

8.1 PDF Reader

PDFBox:Apache PDFBox,PDF 解析库

无其他依赖

8.2 Markdown Reader

CommonMark:Markdown 解析库

无其他依赖

8.3 Tika Reader

Apache Tika:通用文档解析库

支持 100+ 种格式

8.4 Text Splitter

tiktoken:Token 编码库(用于 TokenTextSplitter)

无其他依赖(CharacterTextSplitter)

9. 工程总结

Spring AI 的文档处理能力设计有几个亮点:

统一的 Document 抽象。所有读取器都返回 Document 对象,这让后续处理(分块、嵌入、存储)变得统一。不管是从 PDF 还是 Word 读取,出来的都是 Document,处理起来很方便。

灵活的读取策略。不同格式有不同的读取策略(按页、按段落、按标题),可以根据需求选择最合适的方式。PDF 可以按页读,也可以按段落读,看你的需求。

智能的分块机制。Token 分块不仅考虑大小,还考虑语义完整性(在标点符号处截断),这提高了检索效果。不会在句子中间截断,保持语义完整。

元数据保留。分块时会保留原始文档的元数据,并添加分块相关的元数据(parent_document_id、chunk_index 等),这有助于追踪和调试。想知道某个分块来自哪个文档?看元数据就行。

可扩展性。所有组件都通过接口定义,可以轻松实现自定义的读取器和分块器。想支持新的文档格式?实现 DocumentReader 接口就行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 6:50:16

GPT-5.2 极速接入指南

一、3步极速接入GPT-5.2&#xff0c;零门槛上手步骤1&#xff1a;获取GPT-5.2专属API Key完成平台注册登录后&#xff0c;系统将自动发放GPT-5.2免费体验额度&#xff0c;无需提交额外申请材料&#xff0c;即时到账可用&#xff1b;登录后台管理系统&#xff0c;进入「API令牌管…

作者头像 李华
网站建设 2026/4/22 22:37:04

代码之恋(第十一篇:周末的Bug与意外的Commit)

周六的武汉&#xff0c;阳光透过窗帘洒进李磊的公寓&#xff0c;他坐在电脑前&#xff0c;咳嗽声已经消失&#xff0c;但鼻音还在。屏幕上&#xff0c;一个GitHub仓库的页面闪烁着——那是他和艾丽共同维护的"Collaboration_v3.0"&#xff0c;一个基于微服务架构的协…

作者头像 李华
网站建设 2026/5/1 7:50:19

Python开发:从入门到资深

目录 第一部分&#xff1a;见道——Python基础与编程思想 第1章&#xff1a;缘起——初识Python与编程世界 1.1 万法皆有源&#xff1a;编程与计算机科学的简史。1.2 为何是Python&#xff1a;Python的哲学——“禅”与“道”。1.3 工欲善其事&#xff1a;搭建你的第一个Pyt…

作者头像 李华
网站建设 2026/3/14 1:05:15

19、深入了解SMB协议:实现Linux与Windows的集成

深入了解SMB协议:实现Linux与Windows的集成 1. Windows 98与Samba的性能问题 Windows 98的资源管理器(可能还有其他程序)在向网络共享发送写入请求时,会错误地设置 “sync” 位。这会导致严重的性能下降,因为Samba会在每次写入后正确地对文件执行 fsync() 操作。再加上…

作者头像 李华
网站建设 2026/5/1 4:25:59

24、私有 IP 网络地址分配解析

私有 IP 网络地址分配解析 1. 引言 在本文语境中,企业是指自主运营使用 TCP/IP 网络,并能自主决定网络内地址规划和分配的实体。本文着重探讨私有互联网的地址分配问题。这种分配方式既能保证企业内部所有主机间的网络层连通性,也能确保不同企业的公共主机间的连通性。不过…

作者头像 李华