当前位置: 首页 > news >正文

文档处理能力分析

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 接口就行。

http://www.cnnetsun.cn/news/62411.html

相关文章:

  • 学校食堂出入库管理软件
  • 基于MATLAB的线性判别分析(LDA)降维算法实现方案
  • 【Java毕设源码分享】基于springboot+vue的线上高校奖助学金系统设计与实现(程序+文档+代码讲解+一条龙定制)
  • 【Java毕设源码分享】基于springboot+vue的高校教室资源管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 被裁后,我却更自由了:不同求职机构的冰与火
  • 从焦虑到从容:这款AI工具如何帮我高效完成5万字硕士论文
  • minio分片上传
  • 基于MATLAB实现深度学习图像分类
  • 9、UNIX/Linux 文件操作全解析
  • 企业SRC支付漏洞EDUSRC众测挖掘思路技巧操作分享
  • webpack输出代码报错到指定文本文件
  • 自然语言处理容易混淆知识点(二)BERT和BERTopic的区别
  • GPT-5.2与Gemini 3.0 Pro技术选型指南:核心能力对比+第三方API接入全攻略
  • LangSmith:AI Agent开发者的效率神器,从OpenAI到LangChain都在用!
  • 智能图表生成:当数据分析从“怎么做图”变成“问什么”——某平台新工作流的技术解构与应用前瞻
  • 4、VXLAN BGP EVPN基础解析
  • 22、VXLAN BGP EVPN 多 Pod 与多 Fabric 部署方案解析
  • 2025年十大项目管理系统排名:综合功能、场景与用户口碑的权威榜单
  • Docker部署前端项目,收藏这篇就够了
  • Python+Vue的大学生就业信息管理系统 Pycharm django flask
  • 15 分钟获现场信号,30 分钟建指挥链路!分布式系统赋能救援 “秒响应”
  • 自动化测试中的常见陷阱与规避
  • 6-4 WPS JS宏 不重复随机取值应用
  • 《余行论》第七篇:历史篇
  • 《余行论》第九篇:证验篇
  • 构建动态响应式动画架构:lottie-ios与现代数据流技术融合实践
  • 起薪 15K+!网安领跑 2025IT 转行 6 大榜,政策红利 + 百万缺口,路径直接抄
  • 小程序商城搭建 自带拼团砍价功能 快速引爆销量
  • 海外网红营销:超越促销,用“圣诞故事”绑定品牌情感
  • Qwen3-32B双模式大模型:重构企业AI效率的范式革命