乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • Spring AI 学习路径
  • 第1章 Spring AI 概述与核心理念

    • 章节导读
    • Spring AI 概述与可移植 API
    • 核心模块与依赖关系
  • 第2章 快速入门与第一个AI应用

    • 章节导读
    • 环境准备与配置
    • 第一个 AI 对话应用
  • 第3章 聊天模型与ChatClient

    • 章节导读
    • ChatClient 详解
    • ChatModel 底层抽象
    • 多轮对话与 ChatMemory
  • 第4章 提示词管理与模板

    • 章节导读
    • Prompt 与 Message 体系
    • 提示词模板与动态构建
  • 第5章 输出解析与结构化响应

    • 章节导读
    • 输出解析器与 BeanOutputConverter
  • 第6章 嵌入模型与向量存储

    • 章节导读
    • EmbeddingModel 与文本向量化
    • ETL 数据注入流水线
    • 向量存储抽象与配置
  • 第7章 检索增强生成(RAG)

    • 章节导读
    • RAG 核心机制与流程
    • QuestionAnswerAdvisor 详解
    • RAG 实战案例
  • 第8章 函数调用(Function Calling)

    • 章节导读
    • Tool 注解与函数声明
    • 函数调用实战
  • 第9章 多模态

    • 章节导读
    • 图像输入与视觉模型
    • 图像生成
  • 第10章 Advisor拦截器链

    • 章节导读
    • Advisor 链与内置拦截器
  • 第11章 MCP 协议与跨语言工具集成

    • 章节导读
    • MCP 协议深入
  • 第12章 可观测性测试与最佳实践

    • 章节导读
    • 可观测性与指标监控
    • 测试策略与 Mock 实践
    • 最佳实践与面试考点汇总

黄俪把飞翔科技的员工手册 PDF 拖进工程文件夹:"这个 200 页的 PDF,怎么让 AI 读懂?"小崔头也不抬:"E-T-L——Extract 读出来,Transform 切成块,Load 写进向量库。三部曲,我帮你写个 Pipeline。"

ETL 数据注入流水线

定义与作用

ETL Pipeline 是 RAG 系统的数据准备流水线,将原始文档(PDF / TXT / JSON / Markdown)处理为向量库中的可检索片段:

Extract(提取)→ Transform(转换)→ Load(加载)
   原始文档         文本分块           向量库
阶段Spring AI 组件职责
ExtractDocumentReader读取各种格式,产出 List<Document>
TransformDocumentTransformer文本分割、元数据增强
LoadVectorStore向量化 + 存储

核心原理:完整的 ETL 数据注入流程

图释:200 页 PDF 被拆分为 375 个文本块存入向量库。每个 Block 约 500 Token,加上前后 50 Token 的重叠区保证语义连续性。

DocumentReader 选型表

Reader输入格式典型使用场景
TextReader.txt纯文本文件
PagePdfDocumentReader.pdfPDF 文档(按页分割)
JsonReader.json结构化 JSON 数据
MarkdownDocumentReader.mdMarkdown 文档
TikaDocumentReader任意格式Tika 支持的格式(Word、HTML 等)

TokenTextSplitter 参数详解

TokenTextSplitter splitter = new TokenTextSplitter();

// 核心参数
splitter.setDefaultChunkSize(500);      // 每块 500 Token
splitter.setMinChunkSizeChars(200);     // 最小 200 字符(防止过小的 Block)
splitter.setMaxNumChunks(10000);        // 最大块数(防止超大文档 OOM)
splitter.setKeepSeparator(true);        // 保留分隔符(换行/句号)
参数推荐值说明
chunkSize500太小丢失上下文,太大搜索不精确
overlap50相邻块重叠 Token 数,防止关键信息被截断
minChunkSizeChars200过滤过小的分段

完整示例一:将飞翔科技员工手册注入向量库

场景说明

大翔要求:"客服 AI 必须知道我们的请假流程、报销制度和会议室使用规则。"这正是 ETL Pipeline 的经典场景。

依赖引入

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-transformers</artifactId>
</dependency>

关键代码

@Service
public class EmployeeManualIngestion {

    private final VectorStore vectorStore;

    public EmployeeManualIngestion(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    public void ingest() {
        // ==================== E 阶段:提取 ====================
        Resource pdfResource = new FileSystemResource(
            "C:/docs/飞翔科技员工手册.pdf"
        );
        PagePdfDocumentReader reader = new PagePdfDocumentReader(pdfResource);
        List<Document> documents = reader.get();
        System.out.println("提取完成:" + documents.size() + " 页");

        // ==================== T 阶段:分割 ====================
        TokenTextSplitter splitter = new TokenTextSplitter();
        splitter.setDefaultChunkSize(500);
        splitter.setMinChunkSizeChars(200);
        List<Document> chunks = splitter.apply(documents);
        System.out.println("分割完成:" + chunks.size() + " 个文本块");

        // ==================== L 阶段:加载 ====================
        vectorStore.add(chunks);
        System.out.println("注入完成!共 " + chunks.size() + " 条记录写入向量库");
    }
}

运行结果

提取完成:200 页
分割完成:375 个文本块
注入完成!共 375 条记录写入向量库

完整示例二:带元数据增强的高级 Pipeline

场景说明

高英要求搜索结果不仅能按内容匹配,还能按"部门"和"主题"过滤——例如只查技术部的请假流程。

关键代码

@Service
public class AdvancedIngestion {

    private final VectorStore vectorStore;

    public AdvancedIngestion(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    public void ingestWithMetadata() {
        Resource pdfResource = new FileSystemResource(
            "C:/docs/飞翔科技制度汇编.pdf"
        );

        // E: 提取
        PagePdfDocumentReader reader = new PagePdfDocumentReader(pdfResource);
        List<Document> docs = reader.get();

        // 手动添加元数据(根据页码范围)
        for (Document doc : docs) {
            int pageNum = (int) doc.getMetadata().getOrDefault("page_number", 0);
            if (pageNum <= 20) {
                doc.getMetadata().put("department", "全公司");
                doc.getMetadata().put("topic", "考勤与请假");
            } else if (pageNum <= 50) {
                doc.getMetadata().put("department", "技术部");
                doc.getMetadata().put("topic", "报销与采购");
            } else {
                doc.getMetadata().put("department", "全公司");
                doc.getMetadata().put("topic", "会议室使用");
            }
        }

        // T: 分割
        TokenTextSplitter splitter = new TokenTextSplitter();
        splitter.setDefaultChunkSize(500);
        List<Document> chunks = splitter.apply(docs);

        // L: 加载
        vectorStore.add(chunks);
        System.out.println("带元数据的 ETL 完成,共 " + chunks.size() + " 块");
    }

    /**
     * 按部门 + 主题精确检索
     */
    public List<Document> searchByDepartment(String question, String department) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(question)
                .topK(5)
                .similarityThreshold(0.7)
                .filterExpression("department == '" + department + "'")
                .build()
        );
    }
}

易错场景与面试考点

易错场景一:chunkSize 过大导致检索失败

// ❌ 错误:chunkSize=5000,一个 Block 包含 5 页内容
TokenTextSplitter splitter = new TokenTextSplitter();
splitter.setDefaultChunkSize(5000);   // 太大!
// 问题:搜索"请假流程"可能匹配到包含"请假"Block,但 Block 中 95% 内容不相关
// 结果:AI 被大量无关上下文淹没,回答质量下降

问题分析

chunkSize 过大→一个 Block 包含太多无关信息→搜索结果的"信噪比"低→AI 回答不准确。

// ✅ 正确:chunkSize=500,overlap=50
splitter.setDefaultChunkSize(500);  // 约 300 个汉字
splitter.setMinChunkSizeChars(200);
// 同时设置 overlap 防止关键段落在边界被截断

面试高频题

Q1:ETL Pipeline 的三个阶段各自承担什么职责?

Extract(DocumentReader):从 PDF/文本/JSON 中读取原始内容,产出 Document 列表。Transform(DocumentTransformer):把长文档切成小块(TokenTextSplitter),增强元数据(KeywordMetadataEnricher)。Load(VectorStore.add()):对每个 Document 调用 Embedding 模型生成向量,写入向量数据库。

Q2:为什么需要 overlap?

文本分割是盲切的——可能把一句完整的话切成两段,分属两个 Block。overlap 让相邻 Block 共享部分内容,确保没有关键信息在边界丢失。推荐的 overlap 值通常是 chunkSize 的 10%。

本章小结

  • ETL Pipeline = Extract(DocumentReader) + Transform(DocumentTransformer) + Load(VectorStore)
  • TokenTextSplitter 的核心参数:chunkSize(500 推荐)、overlap(50 推荐)
  • 元数据增强(metadata)支持按部门、分类等条件过滤搜索
  • chunkSize 的选择是精度和召回率的权衡:太小丢失上下文,太大影响搜索精度
上一页
EmbeddingModel 与文本向量化
下一页
向量存储抽象与配置