从文件上传到 RAG 检索:真正看懂了一个 AI 项目的知识库链路
一、前言:今天不是单独学一个知识点,而是串起了一条完整链路
今天继续分析 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。
它适合存:
- Word
- 图片
- 视频
- 日志文件
- 上传的原始文档
在这个项目里,MinIO 主要负责保存用户上传的原始文件。
也就是说,用户上传的完整文件最终不会直接塞进 MySQL,而是放到 MinIO 里。
MySQL 或其他数据库里一般只保存:
文件ID 文件名 文件大小 文件类型 MinIO存储路径 上传用户 上传时间 处理状态真正的文件内容在 MinIO。
这样做的好处是:
- 数据库不会被大文件撑爆;
- 文件存取更适合对象存储;
- 后续解析、下载、预览都可以通过文件路径去 MinIO 取;
- 方便做分布式部署。
所以 MinIO 在这个项目中的定位是:
原始文件仓库。
四、Kafka 为什么出现在文件处理流程里?
文件上传完成后,项目并没有直接在接口里完成所有解析、切片、向量化操作,而是通过 Kafka 发送了一个文件处理任务。
一开始我也会想:
为什么不直接上传完就解析?还要搞 Kafka?
后来理解是,因为文件解析和向量化属于比较耗时的任务。
比如一个用户上传了一个几十 MB 的 PDF,后端如果在上传接口里直接完成:
上传文件 解析文件 切片 向量化 写入 ES 返回结果那这个接口可能会卡很久。
更合理的做法是:
上传接口只负责上传和保存文件 ↓ 发送 Kafka 消息 ↓ 后台消费者异步处理文件这样用户上传完成后,接口可以快速返回。
真正耗时的文件解析和向量化交给后台慢慢处理。
所以 Kafka 在这里的作用是:
解耦上传流程和文件处理流程,让耗时任务异步执行。
这也是很多后端项目常见的设计思想。
五、Tika 是什么?它解决了什么问题?
今天还接触到了一个以前不太熟的工具:Tika。
Tika 的作用可以简单理解为:
从各种文件中提取文本内容。
比如用户上传的文件可能是:
- 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: 03. 向量数据
每个 chunk 会被 embedding 模型转换成向量。
比如:
[0.012, -0.431, 0.875, ...]这个向量用于后续相似度搜索。
4. 元数据 metadata
例如:
source: Java并发编程.pdf page: 12 chunkIndex: 5 userId: 10001metadata 的作用是让系统知道:
- 这段内容来自哪个文件;
- 属于哪个用户;
- 是第几个切片;
- 以后回答时能不能标注来源;
- 删除文件时要删除哪些 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 更能帮助理解底层原理。
