Spring AI 实战:深入解析RAG流程中的文档ETL与文本分割技术

Spring AI 实战:深入解析RAG流程中的文档ETL与文本分割技术大家好 我是冰点 今天我们继续聊 SpringAI 的基本用法和特性 建议先阅读第五篇 RAG 核心原理 了解文档 ETL 在 RAG 流程中的定位 第二阶段 RAG 检索增强 文章目录 Spring AI 实战 六 文档 ETL 实战 PDF Word Markdown 解析与文本分割 一 文档 ETL 在 RAG 中的角色 二 Spring AI 文档读取体系 三 PDF 解析

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



大家好,我是冰点,今天我们继续聊SpringAI的基本用法和特性

建议先阅读第五篇《RAG 核心原理》,了解文档 ETL 在 RAG 流程中的定位。

第二阶段·RAG 检索增强

文章目录

  • 【Spring AI 实战】六、文档 ETL 实战:PDF/Word/Markdown 解析与文本分割
  • 一、文档 ETL 在 RAG 中的角色
  • 二、Spring AI 文档读取体系
  • 三、PDF 解析:PdfReader 详解
  • 3.1 依赖
  • 3.2 基本用法
  • 3.3 带分页和元数据的读取
  • 3.4 提取元数据
  • 四、Word 文档解析:WordDocumentReader
  • 4.1 依赖
  • 4.2 基本用法
  • 4.3 读取表格内容
  • 五、Markdown 解析:MarkdownReader
  • 六、HTML 网页解析
  • 七、JSON 文档解析
  • 八、批量文档读取工厂
  • 九、文本分割:TextSplitter 核心原理
  • 9.1 为什么不能直接喂整篇文档?
  • 9.2 Spring AI 内置分割器
  • 9.3 语义感知分割:RecursiveCharacterTextSplitter
  • 9.4 Markdown 语义分割:保留标题层级
  • 十、自定义分割器:SentenceTransformers 语义分割
  • 十一、分割策略实战参数建议
  • 十二、完整 ETL 流程示例
  • 十三、本章小结

Spring AI 实战:深入解析RAG流程中的文档ETL与文本分割技术_人工智能

一个完整的 RAG 问答系统,数据处理流程如下:

原始文档(PDF/Word/HTML/TXT)

读取(Read) ← ETL 的 E(Extract)

解析(Parse) ← ETL 的 T(Transform)

分割(Split) ← ETL 的 L(Load 前置步骤)

向量化(Embed) → 存入 VectorStore

ETL = Extract(抽取) + Transform(转换) + Load(加载),其中”Load”在 RAG 语境下就是向量化存储。本篇文章重点覆盖前三步。


Spring AI 实战:深入解析RAG流程中的文档ETL与文本分割技术_etl_02

Spring AI 提供了一套统一的 DocumentReader 接口,所有文档源都实现这个接口:

// 核心接口 public interface DocumentReader {

List 
   
    
     
       read(); 
     

}

// 常用实现 // - PdfReader PDF 文件 // - WordDocumentReader Word/ DOCX 文件 // - MarkdownReader Markdown 文件 // - TxtReader 纯文本文件 // - HtmlDocumentReader HTML 网页 // - JsonReader JSON 文件 // - ApachePoiDocumentReader Apache POI 读取 Word/Excel


3.1 依赖

 
     
     
       

 
    
    
      
        org.springframework.ai 
       
    
    
      
        spring-ai-readers-spring-boot-starter 
       

 
    
    
      
        org.apache.pdfbox 
       
    
    
      
        pdfbox 
       
    
    
      
        3.0.1 
       

3.2 基本用法

@Bean public DocumentReader pdfReader() {

return new org.springframework.ai.reader.pdf.PdfReader( "classpath:/docs/product-manual.pdf" ); 

}

// 读取并打印 List docs = pdfReader().read(); docs.forEach(doc -> System.out.println(doc.getContent()));

3.3 带分页和元数据的读取

@Bean public DocumentReader pdfReaderWithMetadata() of {total}“, “”)

 ) .build(); return new org.springframework.ai.reader.pdf.PdfReader( PathUtils.getResourceURL("classpath:/docs/product-manual.pdf"), config ); 

}

3.4 提取元数据

PDF 的元数据(页码、章节标题、文件路径)会自动注入到 Document 的 metadata 中:

List 
     
     
       
         docs = pdfReader().read(); 
       

Document firstPage = docs.get(0); // Spring AI 会自动填充以下元数据 firstPage.getMetadata().forEach((k, v) -> System.out.println(k + “: ” + v)); // 输出示例: // source: classpath:/docs/product-manual.pdf // page-number: 1 // total-pages: 42


4.1 依赖

 
      
     
        

 
     
    
       
         org.apache.poi 
        
     
    
       
         poi-ooxml 
        
     
    
       
         5.2.5 
        

4.2 基本用法

@Bean public DocumentReader wordReader() 

4.3 读取表格内容

Word 中的表格默认不自动提取,需要配合自定义处理器:

public class TableAwareWordReader extends WordDocumentReader {

public TableAwareWordReader(Resource resource) { super(resource, WordDocumentReaderConfig.builder().build()); } @Override public List 
     
    
       
         read() { List 
        
          docs = super.read(); // 手动解析 Word 中的表格并追加到文本 // Apache POI 解析 XWPFTable 逻辑... return docs; } 
         
       

}


Markdown 是最干净的格式,解析时能保留结构化语义。

@Bean public DocumentReader markdownReader() 

Markdown 解析的额外优势

  • 标题层级自动映射到 Document metadata 的 headings 字段
  • 代码块自动识别并标记语言类型
  • 支持 YAML Front Matter 元数据提取

@Bean public DocumentReader webPageReader() 

适合处理结构化数据源,如日志、产品目录:

@Bean public DocumentReader jsonReader() 

生产环境中通常需要一次性读取整个目录的文档:

@Component public class DocumentLoaderFactory {

@Value("${spring.ai.rag.document.path}") private String documentPath; public List 
         
    
           
             loadAllDocuments() private void loadDirectory(String path, String glob, Supplier 
            
              readerFactory) } 
             
           

}


文档解析完成后,需要将长文本切分为适合检索的”块(Chunk)”。这是整个 ETL 流程中最关键的一步,切分策略直接影响检索质量。

9.1 为什么不能直接喂整篇文档?

9.2 Spring AI 内置分割器

// 按字符数分割(最简单) TextSplitter characterSplitter = new CharacterTextSplitter(

500, // 每块目标字符数 50, // 块间重叠字符数(避免割裂语义) true, // 是否保留分隔符 " " // 分隔符 

);

// 按 Token 数分割(更精确,基于 LLM tokenizer) TextSplitter tokenSplitter = new TokenTextSplitter(

300, // 每块目标 Token 数 50, // 重叠 Token 数 true, // 是否保留分隔符 true // 是否追加块序号 

);

9.3 语义感知分割:RecursiveCharacterTextSplitter

这是最推荐的生产级分割器,它按优先级尝试多种分隔符,逐层拆分:

尝试按 “

” (三级标题)分割

↓ 如果块太大 

尝试按 “

” (段落)分割

↓ 如果块太大 

尝试按 “ ” (换行)分割

↓ 如果块太大 

尝试按 “ ” (句子)分割

↓ 如果块太大 

按字符数硬截断(最后手段)

@Bean public DocumentReader semanticSplitterReader() {

// 推荐配置:重叠 20%,保留段落边界 return new RecursiveCharacterTextSplitterReader( pdfReader(), RecursiveCharacterTextSplitterReaderConfig.builder() .chunkSize(500) // 每块目标字符数 .chunkOverlap(100) // 重叠 20% .separators(List.of(" 

”, “ “, “。”, “!”, “?”, “ “))

 .keepSeparator(true) .build() ); 

}

9.4 Markdown 语义分割:保留标题层级

对于 Markdown 文档,用 MarkdownHeaderTextSplitter 可以在标题处自然断点:

List 
           
     
             
               docs = markdownReader().read(); 
             

MarkdownHeaderTextSplitter splitter = new MarkdownHeaderTextSplitter(

List.of( // 按 二级标题分割 new HeaderMetadata("#", 1), new HeaderMetadata("", 2), new HeaderMetadata("", 3) ), 300, // fallback chunk size 50 // overlap 

);

List chunks = splitter.split(docs); // 每个 chunk 自动带上 headings metadata: [“Spring AI”, “安装配置”]


当对语义完整性要求极高时,可以用 Embedding 模型做语义感知分割——只在新主题出现时才断点:

@Component public class SemanticChunker

 // 计算当前句子与 Chunk 最后一句的语义相似度 double similarity = computeSimilarity( currentChunk.get(currentChunk.size() - 1), sentence ); if (similarity < similarityThreshold) { // 低于阈值,创建新 Chunk chunks.add(new ArrayList<>(currentChunk)); currentChunk.clear(); } currentChunk.add(sentence); } if (!currentChunk.isEmpty()) { chunks.add(currentChunk); } return chunks.stream() .map(c -> new Document(String.join("", c))) .collect(Collectors.toList()); } private double computeSimilarity(String s1, String s2) { Embedding e1 = embeddingModel.embed(s1); Embedding e2 = embeddingModel.embed(s2); return cosineSimilarity(e1, e2); } private double cosineSimilarity(Embedding a, Embedding b) return dot / (Math.sqrt(normA) * Math.sqrt(normB)); } 

}



@Service public class DocumentETLService

 // Step 3: Load - 存入向量数据库 vectorStore.add(chunks); System.out.println("ETL 完成:处理文档 " + allDocs.size() + " 篇,生成 Chunk " + chunks.size() + " 个"); } 

}



下一篇预告:《七、Embedding 向量化与向量数据库选型对比》—— 深度对比 Milvus、Pinecone、Redis、Chroma、Elasticsearch 的适用场景与 Spring AI 集成方式。


📌 系列导航

  • ← 上一篇:【Spring AI 实战】五、RAG 核心原理
  • → 下一篇:【Spring AI 实战】七、Embedding 向量化与向量数据库选型对比
  • → 完整目录

📎 示例说明:本文侧重 ETL 管道与分割策略,若你准备做完整知识库系统,建议继续阅读第七、八篇。

小讯
上一篇 2026-04-26 18:35
下一篇 2026-04-26 18:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/281087.html