欢迎光临
我们一直在努力

Spring AI RAG 生产级实战:从 PDF 解析到精准回答的完整流水线指南

引言

大模型的知识截止日期和幻觉问题,使得 RAG(检索增强生成)成为企业落地 AI 应用的标配架构。然而,从"能跑"到"好用",中间横亘着无数细节:文档如何解析?分段策略怎么选?检索和生成如何无缝衔接?

本文将以一个基于 Spring AI 1.1.2 + Redis + DashScope 的生产级 RAG 项目为例,手把手拆解完整流水线,涵盖从 PDF 上传、云端解析、分层分段、向量化存储,到查询扩展、多库并发检索、上下文增强、精准回答的全链路实战经验。


1. 整体架构概览

整个 RAG 流水线分为两大阶段:

image.png


2. 文档解析:从 PDF 到结构化内容

2.1 挑战

PDF 是一种呈现格式而非数据结构。直接提取文本会丢失:

  • 表格结构:行列关系、表头、合并单元格
  • 图片信息:图表、截图、流程图
  • 版面布局:多栏、页眉页脚、标题层级
  • 公式:数学符号、化学方程式

2.2 方案:MinerU 云端解析

image.png

我们采用 MinerU API 进行云端解析,分两步:

Step 1 — 上传 + 获取解析任务 ID:

// MinerUtil.uploadAndParse()
// 1. 获取预签名上传URL
// 2. PUT上传PDF文件
// 3. 返回batchId用于轮询

Step 2 — 轮询获取结果:

// MinerUtil.getZipUrl()
// 轮询直到状态为 DONE,下载结果ZIP包

返回的 _content_list.json 包含结构化内容项,每个项有 type 字段:

类型处理策略
TEXT / EQUATION 截断至2000字符,相邻小段合并
TABLE 保留表题和表注,内容截断至2000字符
LIST 列表项拼接,遇2000字符拆分
IMAGE 调用 VLM(qwen3.5-flash)生成结构化描述
PAGE_NUMBER / HEADER 直接过滤

2.3 图片理解

对于文档中的图片,单纯 OCR 提取文字会丢失视觉信息(图表趋势、流程图走向)。我们用多模态大模型描述图片:

// 使用线程池 parallelProcessPool (CPU核心数*2)
// 超时10分钟,每张图片调用 VLM 生成描述

2.4 经验总结

✅ PDF 超过 200 页时预处理拆分,避免超时
✅ 表格和图片是 RAG 的薄弱环节,需专项处理
✅ 云端解析 vs 本地解析:云端精度高但依赖网络,本地速度快但版面分析能力有限


3. 分段策略:父子分层结构

3.1 为什么用父子模式?

LLM 的上下文窗口虽在增长,但实验表明检索分段远优于检索全文。关键在于找到精准度和上下文完整性的平衡点。

对标 Dify 的父子模式,我们实现了类似的 双层分段结构:

image.png

3.2 实现细节

父分段生成(TextUtil.textSegmentsFromMiner()):

  • MinerU 返回的每个 ContentItem 视为一个父分段
  • 同一页内相邻的小内容块(<500字符)合并
  • 每个父分段携带:id, bookId, page, content, bbox(边界框坐标)

子分段生成(TextUtil.strengthenWithSpilt()):

  • 使用 BreakIterator.getSentenceInstance(Locale.CHINESE) 按句切分
  • 中文场景必须指定中文Locale,否则分句不准
  • 过滤掉 < 10 字符的短句(通常是标点残留)
  • 子分段通过 parentId = "{bookId}_{parentId}" 链接回父分段

3.3 推荐配置

参数推荐值说明
父段最大长度 2000 chars 对应 MinerU 的 blockMaxSize
子段最小长度 10 chars 过滤无意义短句
相邻合并阈值 500 chars 同页相邻小段合并
重叠策略 无重叠 父子结构天然覆盖

3.4 为什么这不只是"按段落分段"?

传统"按段落分段"是扁平结构:每个分段独立存储、独立检索。缺点是:

  • 段落太短 → 上下文不够
  • 段落太长 → 噪音多、精准度下降

父子分层的优势:

  • 检索粒度:子段(句子)匹配用户问题,精准定位
  • 回答粒度:父段(段落)提供给 LLM,上下文完整
  • 得分传递:子段的相似度得分传递给父段,父段按子段得分排序

  • 4. 向量化存储:Redis + HNSW 索引

    4.1 向量数据库选型

    我们选择 Redis(RediSearch 模块) 作为向量存储,原因:

    • 部署简单:无需额外组件,Redis 已是基础设施
    • 性能优秀:HNSW 算法,COSINE 距离度量
    • Spring AI 原生支持:spring-ai-redis-store 开箱即用

    4.2 索引配置

    // FT.CREATE 索引参数
    HNSW: M=16, EF_CONSTRUCTION=200
    Metric: COSINE
    Type: FLOAT32
    Dimensions: 5122048(按向量库配置)

    4.3 Embedding 模型

    使用 DashScope 的 text-embedding-v4,向量维度可根据需要调整。

    注意: 同一索引的向量维度必须一致。如果修改维度,需重建索引。

    4.4 元数据索引

    除了向量,我们还索引了以下 TAG 字段用于过滤:

    • bookId:文档来源
    • page:页码(支持定位到原文)
    • parentId:父子分段关联
    • id, text, bbox

    5. 检索增强生成(在线阶段)

    这一阶段是 RAG 的核心,直接决定回答质量。

    image.png

    5.1 查询扩展(Query Expansion)

    痛点:用户的问题通常简短、模糊,直接检索向量库效果差。

    方案:在检索之前,先用 LLM 对问题进行扩展。

    // MyQueryExpander.expand()
    // 1. 截取最近10条对话历史
    // 2. 调用LLM理解完整意图
    // 3. 解析代词指代(那、这、它、其等)
    // 4. 生成4个完整通顺的查询句子

    为什么是 4 个? 实验表明 3-5 个扩展查询能在召回率和计算成本之间取得最佳平衡。太少则扩展不充分,太多则引入噪声且耗时线性增长。

    5.2 多库并发检索

    痛点:一个智能体可能关联多个知识库。

    方案:自定义 MultiVectorStoreDocumentRetriever,针对每个扩展查询变体,并发查询所有关联的向量库。

    CompletableFuture.supplyAsync(() ->
    vectorStore.similaritySearch(queryVariant)
    )

    并发度:N 个向量库 × 5 个查询变体 = 5N 个并发任务。使用 CompletableFuture 实现,无需额外线程池配置。

    5.3 子句检索 → 父段召回

    这是我们的核心创新之一,也是区别于普通 RAG 的关键。

    image.png

    为什么这么做?

    • 直接检索父段:句子级的精确匹配被段落中的噪声稀释
    • 直接返回子段:LLM 上下文不足,且无法引用完整来源
    • 子段查 + 父段回:两全其美

    5.4 排序与截断

    MyDocumentPostProcessor 对所有召回文档排序并截取 TopK:

  • 按相似度得分降序排列
  • 截取前 topK 个文档(来自智能体配置)
  • 注意:我们没有引入独立的 Reranker 模型。如果你的场景对精度要求更高(如法律、医疗),建议在排序后、截断前加入一个 cross-encoder reranker。

    5.5 上下文增强(Query Augmentation)

    这是 RAG 生成的最后一道关口。

    Spring AI 的一个坑:原版 ContextualQueryAugmenter.augment() 会创建新的 Query 对象,丢失对话历史。

    我们的修复:自定义 MyContextualQueryAugmenter,用 query.mutate().text(augmentedText).build() 保留历史。

    增强后的查询格式:

    用户问题:{query}

    相关上下文(每段内容开头 <ID>数字</ID> 是文档唯一ID):
    ———————
    <ID>1</ID>段落内容…
    <ID>2</ID>段落内容…
    ———————

    回答规则:
    1. 严格基于上下文回答
    2. 每句话如果使用了上下文,必须在句尾标注对应的文档ID
    3. 如果上下文不足以回答问题,如实说明

    5.6 文档来源追溯

    回答中的 <ID>xxx</ID> 标签让前端可以:

    • 高亮回答中每个句子对应的原文区域
    • 用户点击 ID 跳转到文档对应位置
    • 实现可溯源的可信 AI 回答

    6. 智能体架构设计

    6.1 可配置的智能体

    每个 Agent 通过 t_agent 表配置,支持运行时调整:

    字段范围说明
    temperature 0.0 – 1.0 回答创造力
    topK 0 – 100 召回文档数
    similarity 0.0 – 1.0 相似度阈值
    maxMessage 0 – 100 历史对话窗口
    prompt 系统提示词

    6.2 多知识库关联

    一个 Agent 可以关联多个 Vector Store(通过 t_agent_vector 关联表)。这意味着:

    • 可以给一个 Agent 同时挂载"产品文档"+“技术手册”+“FAQ”
    • 检索时自动并行查询所有库,结果合并

    6.3 缓存策略

    我们使用 Caffeine 缓存,避免重复初始化开销:

    缓存对象容量过期策略
    Agent 实例 1000 30分钟访问过期
    Vector Store 实例 1000 30分钟访问过期
    异步任务状态 60分钟写入过期

    7. 性能优化实战

    7.1 数据 ingestion 优化

    • 全局信号量(Semaphore=1):同一时间只处理一个文件,避免 MinerU API 过载
    • 批量入库:每 10 个 Document 一批写入 Redis
    • 异步处理:上传后立即返回 taskId,前端轮询进度

    7.2 在线检索优化

    • 并发检索:多个查询变体 × 多个向量库同时搜索
    • 历史截断:只保留最近 10 条对话记录(可配置)
    • Streaming 响应:Flux 实现流式输出,用户无需等待全量生成

    7.3 脏数据处理

    • 过滤页眉页脚、页码
    • 替换连续空格和制表符
    • 短句过滤(< 10 字符)
    • URL 和邮箱可选删除

    8. 通用模式 vs 父子模式对比

    维度通用模式(扁平分段)父子模式(分层分段)
    分段形式 独立分段 父子双层嵌套
    检索粒度 段落级 句子级检索 + 段落级回答
    上下文完整度 取决于段落长度 高(父段提供完整上下文)
    精准度 一般(长段落噪声多) 高(精确匹配子段)
    实现复杂度 简单 中等
    推荐场景 QA 对、短文本 长文档、技术手册、医疗报告

    10. 总结与展望

    生产级 RAG 的关键要点

  • 文档解析是地基:PDF 排版复杂,建议使用专业解析服务(MinerU 等)
  • 分段策略决定上限:父子分层结构经实践验证是兼顾精度和上下文的最佳方案
  • 查询扩展弥补用户输入稀疏性:LLM 扩写比传统词向量扩展效果显著更好
  • 并发检索是性能保障:多变体 × 多库并发,充分利用计算资源
  • 可溯源的回答建立信任:文档 ID 引用让 AI 回答可验证、可追溯
  • 后续优化方向

    • 引入 cross-encoder Reranker 提升排序精度
    • 支持更多文档格式(Word、Markdown、HTML)
    • 混合检索(向量 + 关键词 BM25)
    • 增量更新(文档更新后只重建变化部分)
    • Graph RAG 处理多文档间的知识关联

    写在最后

    RAG 系统的落地远不止"文档分段 + 向量检索"这么简单。从本文的实践可以看到,一个生产级的 RAG 流水线需要在文档解析、分段策略、检索增强、生成控制四个环节上精雕细琢。其中,父子分层分段、LLM 查询扩展、并发多库检索、文档 ID 溯源等策略,是我们在实际业务中验证过的有效手段。

    如果你的项目也正从"Demo 阶段"走向"生产阶段",希望本文的架构设计与踩坑经验能为你提供一些参考。RAG 技术仍在快速演进,Graph RAG、Agentic RAG 等新范式正在涌现,保持学习,持续迭代。


    赞(0)
    未经允许不得转载:171主机测评 » Spring AI RAG 生产级实战:从 PDF 解析到精准回答的完整流水线指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址