一、前言:今天不是单独学一个知识点,而是串起了一条完整链路

今天继续分析 AI 项目中的 RAG 模块时,我发现自己之前对“文件上传”“文件切片”“向量化”“召回”“大模型回答”这些概念,虽然都单独听过,但真正放到项目代码里看时,还是容易混在一起。

尤其是看到项目里有:

  • 文件上传
  • 分片上传
  • MinIO 存储
  • Kafka 异步消费
  • Tika 文档解析
  • 文本切片
  • Elasticsearch 存储向量
  • 用户提问时向量召回
  • 最后交给大模型生成回答

一开始会觉得特别绕:
用户不是上传了一个完整文件吗?为什么代码里又有分片?文件内容不是已经向量化了吗?为什么数据库里还要存文件路径、文本内容、元数据?ES 不是倒排索引吗,怎么还能当向量数据库?

今天主要就是把这些问题拆开来看。


二、用户上传完整文件,为什么项目里还有“分片上传”?

一开始我有一个误解:
我以为所谓“文件分片”就是 RAG 里的文本切片。

但实际看代码后才发现,这两个“切片”完全不是一回事。

1. 上传分片:解决大文件上传问题

用户在页面上看起来上传的是一个完整文件,但前端可能会把这个大文件拆成很多小块,一块一块传给后端。

这叫:

文件上传分片

它解决的是 大文件传输问题

比如一个 200MB 的 PDF,如果一次性上传:

  • 网络中断就要全部重传;
  • 请求时间可能过长;
  • 后端接收压力比较大;
  • 前端也不好显示上传进度。

所以前端会把文件切成多个 chunk,例如:

文件A.pdf
  ├── chunk-1
  ├── chunk-2
  ├── chunk-3
  └── chunk-4

后端收到这些 chunk 后,先临时保存,等所有分片都上传完成后,再合并成完整文件。

所以这里的分片是:

完整文件 → 上传小块 → 后端合并成完整文件

它跟 RAG 的文本切片不是一个阶段。


2. RAG 文本切片:解决知识检索问题

RAG 里的切片发生在文件上传完成之后。

也就是说,后端已经拿到了完整文件,比如一个 PDF、Word、Markdown 或 txt 文件,然后才会开始解析文件内容。

流程大概是:

完整文件
  ↓
Tika 解析出文本
  ↓
把文本切成多个 chunk
  ↓
每个 chunk 向量化
  ↓
存入向量库 / ES

这个切片解决的是 知识检索问题

因为用户提问时,不可能把整篇文档都塞给大模型。
所以需要提前把文档切成一小段一小段,等用户提问时,只召回最相关的几段内容。


三、MinIO 是什么?它在这个项目里负责什么?

今天还分析到了 MinIO。

以前我更熟悉的是本地文件存储,或者阿里云 OSS 这种对象存储。
MinIO 本质上也是对象存储,可以理解为一个可以自己部署的 OSS。

它适合存:

  • PDF
  • Word
  • 图片
  • 视频
  • 日志文件
  • 上传的原始文档

在这个项目里,MinIO 主要负责保存用户上传的原始文件。

也就是说,用户上传的完整文件最终不会直接塞进 MySQL,而是放到 MinIO 里。

MySQL 或其他数据库里一般只保存:

文件ID
文件名
文件大小
文件类型
MinIO存储路径
上传用户
上传时间
处理状态

真正的文件内容在 MinIO。

这样做的好处是:

  • 数据库不会被大文件撑爆;
  • 文件存取更适合对象存储;
  • 后续解析、下载、预览都可以通过文件路径去 MinIO 取;
  • 方便做分布式部署。

所以 MinIO 在这个项目中的定位是:

原始文件仓库。


四、Kafka 为什么出现在文件处理流程里?

文件上传完成后,项目并没有直接在接口里完成所有解析、切片、向量化操作,而是通过 Kafka 发送了一个文件处理任务。

一开始我也会想:
为什么不直接上传完就解析?还要搞 Kafka?

后来理解是,因为文件解析和向量化属于比较耗时的任务。

比如一个用户上传了一个几十 MB 的 PDF,后端如果在上传接口里直接完成:


上传文件
解析文件
切片
向量化
写入 ES
返回结果

那这个接口可能会卡很久。

更合理的做法是:

上传接口只负责上传和保存文件
  ↓
发送 Kafka 消息
  ↓
后台消费者异步处理文件

这样用户上传完成后,接口可以快速返回。
真正耗时的文件解析和向量化交给后台慢慢处理。

所以 Kafka 在这里的作用是:

解耦上传流程和文件处理流程,让耗时任务异步执行。

这也是很多后端项目常见的设计思想。


五、Tika 是什么?它解决了什么问题?

今天还接触到了一个以前不太熟的工具:Tika。

Tika 的作用可以简单理解为:

从各种文件中提取文本内容。

比如用户上传的文件可能是:

  • PDF
  • Word
  • Excel
  • PPT
  • HTML
  • txt

这些文件格式不一样,直接读取肯定不方便。

Tika 就像一个统一的文本提取器,它可以帮我们把不同格式的文件转换成文本内容。

例如:

用户上传:Java并发编程.pdf
Tika 提取后:
“线程池的核心参数包括 corePoolSize、maximumPoolSize、keepAliveTime……”

RAG 关心的不是 PDF 文件本身,而是 PDF 里的文字内容。

所以 Tika 是 RAG 文件处理链路里非常关键的一步:

文件对象 → 文本内容

只有拿到了文本,后面才能做切片、向量化和召回。


六、文本切片到底是按什么切的?

今天分析代码时,我一直在纠结一个问题:

如果按照段落切,万一一段很短怎么办?
如果按照固定长度切,会不会把一句话切断?
为什么还要有 overlap?

后来我理解了,RAG 文本切片不是为了“切得好看”,而是为了让后续检索更稳定。

常见切法有几种:

1. 按固定字符数切

比如每 500 个字符切一段。

优点是简单稳定。
缺点是可能把一个完整语义切断。

比如:

Spring AI 中的 ChatClient 负责构建对话请求,
而 Advisor 可以在请求前后增强模型调用……

如果刚好从中间切开,就可能导致前后语义割裂。


2. 按段落切

按自然段切,更符合人的阅读习惯。

但问题是:

  • 有些段落很短;
  • 有些段落特别长;
  • 不同文档格式不统一。

如果一段只有几个字,也单独作为一个 chunk,就会浪费一次向量化和召回机会。


3. 按固定长度 + overlap 切

这是很多项目更常见的方式。

比如:

chunk size = 500
overlap = 100

意思是每个切片大概 500 个字符,但相邻切片之间保留 100 个字符重叠。

例如:

chunk1: 0   - 500
chunk2: 400 - 900
chunk3: 800 - 1300

中间重复的部分就是 overlap。

这样做的目的是:

防止重要语义刚好被切断。

如果一个关键答案刚好位于 chunk1 和 chunk2 的边界处,没有 overlap 的话,可能两个 chunk 都不完整。
有 overlap 后,至少有一个 chunk 能保留比较完整的上下文。

所以 overlap 不是重复浪费,而是为了提高召回质量。


七、数据库里到底保存了什么?

今天我一开始也有疑问:

用户提问的时候不是把问题向量化,然后去向量库召回吗?
那数据库里为什么还要保存文件信息、chunk 信息、路径这些东西?

后来理解了,RAG 不只是“向量召回”这么简单,它还需要管理知识库。

通常数据库里会保存几类东西。

1. 文件元数据

比如:

fileId
fileName
fileType
fileSize
minioPath
userId
uploadTime
status

这些字段用于管理用户上传过哪些文件。


2. 文本切片内容

也就是文件被解析、切片之后的 chunk 文本。

例如:

chunkId: 001
fileId: A1001
content: “Spring AI 的 ChatClient 是一个面向开发者的高级客户端……”
chunkIndex: 0

3. 向量数据

每个 chunk 会被 embedding 模型转换成向量。

比如:

[0.012, -0.431, 0.875, ...]

这个向量用于后续相似度搜索。


4. 元数据 metadata

例如:

source: Java并发编程.pdf
page: 12
chunkIndex: 5
userId: 10001

metadata 的作用是让系统知道:

  • 这段内容来自哪个文件;
  • 属于哪个用户;
  • 是第几个切片;
  • 以后回答时能不能标注来源;
  • 删除文件时要删除哪些 chunk。

所以数据库不是为了替代向量检索,而是为了管理整个知识库生命周期。


八、Elasticsearch 不是倒排索引吗?为什么还能做向量数据库?

今天也重新理解了 ES。

以前我对 ES 的印象主要是:

ES 擅长全文检索,核心是倒排索引。

倒排索引简单说就是:

关键词 → 出现在哪些文档中

比如有三篇文档:

doc1: Java 支持多线程
doc2: Spring AI 支持 RAG
doc3: Java 可以开发 AI 应用

倒排索引可能是:

Java   → doc1, doc3
AI     → doc2, doc3
RAG    → doc2
Spring → doc2

所以用户搜索 “Java AI” 时,ES 可以很快找到相关文档。

但现在 ES 不只支持传统关键词检索,也支持 dense vector 字段,也就是向量检索。

所以 ES 可以同时做两类检索:

关键词检索:适合精确匹配
向量检索:适合理解语义相似

例如用户问:

如何让大模型基于企业知识回答问题?

虽然文档里可能没有完全一样的关键词,但如果某些 chunk 的语义和这个问题接近,向量检索就可以召回它。

这也是为什么有些项目会直接用 ES 来做 RAG 存储。

它不一定是最专业的向量数据库,但对于很多项目来说,ES 同时支持全文检索和向量检索,已经足够实用。


九、用户提问时,RAG 到底怎么工作?

把今天的内容串起来,用户提问阶段的流程大概是:

用户输入问题
  ↓
将用户问题向量化
  ↓
去 ES / 向量库中搜索相似 chunk
  ↓
拿到相关文本片段
  ↓
把问题 + 召回内容一起交给大模型
  ↓
大模型基于上下文生成回答

也就是说,RAG 的核心不是“把文件上传给大模型”,而是:

先把知识提前处理好,用户提问时再按需召回相关内容。

上传文件时做的是知识入库:

文件 → 文本 → chunk → embedding → 存储

用户提问时做的是知识检索:

问题 → embedding → 相似度搜索 → 召回 chunk → LLM 回答

这两个阶段一定要分清楚。


十、从手写 RAG 到 Spring AI 框架 RAG,是一个很好的学习路线

今天还对比了两个项目里的 RAG 实现。

一个项目更偏底层,很多流程都是自己写的:

文件上传
MinIO存储
Kafka异步处理
Tika解析
手动切片
向量化
存入ES
检索召回
拼接Prompt
调用LLM

另一个项目则更多使用 Spring AI 框架能力,比如:


DocumentReader
TokenTextSplitter
EmbeddingModel
VectorStore
Advisor
ChatClient

这两个项目放在一起看,其实是非常好的学习路线。

1. 手写 RAG 的价值

手写 RAG 虽然代码繁琐,但可以真正理解底层流程。

你会知道:

  • 文件是怎么上传的;
  • 原始文件存在哪里;
  • 文本是怎么提取的;
  • chunk 是怎么生成的;
  • embedding 是什么时候调用的;
  • 向量和原文为什么都要保存;
  • 用户提问时为什么要先做召回;
  • prompt 是怎么拼接给大模型的。

这些东西如果只用框架,很容易变成“会用但不懂”。


2. 框架 RAG 的价值

Spring AI 这类框架的价值是把通用流程封装起来,让开发者更专注业务。

比如原来你可能要自己写很多代码:

切片
向量化
存储
检索
拼 prompt
调用模型

框架可以帮你抽象成:

VectorStore
EmbeddingModel
QuestionAnswerAdvisor
ChatClient

开发体验会更好,代码也更清晰。

但是如果没有前面手写 RAG 的理解,直接看框架代码,反而容易不明白这些组件背后到底做了什么。

所以我觉得比较好的学习路线是:

先手写理解原理
  ↓
再用框架提升效率
  ↓
最后结合业务设计出真正可落地的 Agent / RAG 系统

十一、今天最大的收获:RAG 不是一个点,而是一条工程链路

今天看下来,我最大的感受是:

很多时候我们说 RAG,脑子里只想到:

向量化 + 召回 + 大模型回答

但真正落到项目里,RAG 是一整条工程链路。

它至少包含:

文件上传
对象存储
异步任务
文档解析
文本切片
向量生成
向量存储
元数据管理
问题向量化
相似度召回
Prompt增强
大模型生成

每一个环节都有自己的工程意义。

比如:

  • MinIO 解决原始文件存储;
  • Kafka 解决耗时任务异步化;
  • Tika 解决多格式文档解析;
  • chunk size 和 overlap 影响召回质量;
  • ES 既可以做全文检索,也可以做向量检索;
  • metadata 让知识库具备可管理性;
  • LLM 最终负责基于召回内容组织自然语言回答。

所以 RAG 不是简单调一个 API,而是一个完整的后端 + AI 工程系统。


十二、总结

今天通过分析项目代码,我对 RAG 的理解从“概念层”进一步走到了“工程实现层”。

以前我知道 RAG 是:

检索增强生成

现在我更清楚它在项目中是怎么落地的:

上传文件 → 保存原文件 → 异步解析 → 提取文本 → 文本切片 → 向量化 → 存储 → 用户提问 → 召回相关内容 → 大模型回答

同时也区分清楚了几个容易混淆的概念:

  • 上传分片不是 RAG 文本切片;
  • MinIO 存的是原始文件;
  • Tika 负责从文件中提取文本;
  • Kafka 负责异步处理耗时任务;
  • 数据库存的不只是向量,还有文件、chunk、metadata 等管理信息;
  • ES 不只可以做倒排索引,也可以支持向量检索;
  • 框架 RAG 更简洁,但手写 RAG 更能帮助理解底层原理。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐