大家好,我是小编~
上一节我们讲了RAG:
AI不是“知道答案”,而是“去资料里找答案”
于是很多小伙伴兴冲冲做了第一版RAG。
然后马上遇到一个诡异的问题:
明明资料里有答案,AI却说不知道
或者答得断断续续,像拼接失败
有时候甚至答非所问
你们的第一反应是不是:
embedding不行?
prompt写错了?
模型太弱?
Maybe, But我想说
你可能冤枉模型了
还有一种可能,也是概率最大的一种:
chunk(文本切分)
一、什么是chunk?
不妨这样想象一下:
你让一个实习生帮你在文档里找答案。
你有三种给法:
方式1:整本说明书丢给他
100页文档
没有分段
结果:他找不到重点
方式2:把文档剪成碎片
一句话一张纸
完全打散
结果:他看不懂上下文
给你2秒钟思考一下
你也许想到了,正确的做法就是:
按“有意义的段落”切好,再给他
这个“切段落”的动作:
就是 chunk
二、技术定义(够用就行)
chunk,本质就是:
把长文本切成多个“有语义的小段”,每一段单独做向量化(embedding)
但这里很容易忽略的关键点:
AI并不是在“理解整篇文档”,而是在“理解这些被切出来的小段”
这样说你能明白吗:
在RAG系统里,真正参与检索的,不是文档本身,而是这些chunk。
而chunk的流程其实就是这样的:
原始文档 → 切分成多个chunk → 每个chunk生成向量 → 存入向量数据库
当用户提问时:
问题 → 转成向量 → 去匹配最相似的chunk → 再把这些chunk交给AI生成答案
“后面我将用代码完整的演示这个流程”
所以其实你可以这样理解:
chunk = AI可搜索的最小知识单元
再进一步说:
每一个chunk,都会变成一条“可被检索的记录”,类似这样:
{
content: "ServiceA接口使用POST,请求路径为/api/v1/serviceA",
embedding: [0.12, -0.98, ...],
metadata: {...}
}
这里的embedding向量,本质上是:
把“这段话的语义”,转换成一串数字
前面我已经说过,AI并不是在做关键词匹配,而是在做 “语义相似度计算”
当用户提问
“serviceA怎么调用?”
系统不会去找“有没有完全一样的句子”,
而是去找:
“语义上最接近‘怎么调用’的那几段chunk”
这也是为什么:
chunk切得好不好,直接决定了“能不能被找到”
按我理解
我们不是在“存文档”,而是在“构建一堆可以被AI检索的语义块”
所以chunk这一步,其实做了三件事:
降低理解难度(把长文本拆小)
提高匹配精度(每段更聚焦)
决定检索边界(AI最多只能看到这些块)
【三、代码展示】
假如你的代码是这样写的
List<TextSegment> segments =
DocumentSplitters.recursive(1000, 200).split(document);
有的小伙伴可能看不懂参数,解释一下
SegmentSize: 每个chunk最大长度1000
Overlap: chunk之间重叠200
意思就是每一段不会超过1000字符,并且相邻两段之间,会有200字符是重复的。
注意哦,不是硬切哦
它是“尽量按语义切”,实在不行才按长度切
按长度切的策略大致是:
优先按“段落”切(比如换行)
如果一段本身 < 1000 → 直接作为一个chunk
如果段落太大(超过1000)
再继续往下拆:
按句子
按标点(句号、逗号等)
如果还太大
最后才按字符数硬切
可能有的小伙伴会问,为什么需要overlap,为什么要重复100个字符?这不是浪费吗?
如果你能想到这个问题,恭喜你
你已经超越了小编啦
那么,overlap的作用是什么?
可以这样说,overlap是整个chunk策略里,最容易被忽略、但最关键的一环
咱们先看一个没有 overlap 的情况
原文:
ServiceA接口使用POST,请求路径为/api/v1/serviceA,请求头需要Authorization token
如果没有 overlap,切出来可能是:
chunk1:ServiceA接口使用POST
chunk2:请求路径为/api/v1/serviceA
chunk3:请求头需要Authorization token
问题来了:
👉 用户问:serviceA怎么调用?
系统可能只检索到:
chunk1(因为包含“serviceA”)
但 chunk1 只有:“使用POST”
路径呢?token呢?全没了
有 overlap 的情况(完全不一样)
假设 overlap = 50
chunk1:ServiceA接口使用POST,请求路径为/api/v1/serviceA
chunk2:请求路径为/api/v1/serviceA,请求头需要Authorization token
这时候:
无论命中 chunk1 还是 chunk2
AI都能看到“完整信息的一部分”,它就可以拼出完整答案
所以overlap 其实就是在解决防止信息被“切断”的这个问题,换句话说overlap 是在保证“上下文连续性”,所以overlap 不是“冗余”,而是“容错机制”
即使只命中一个chunk,也尽量保证信息完整
【四、如何自定义切法】
很多小伙伴看到这里估计就会问了,我自己怎么自定义chunk的切法呢?
方案一:调参数
DocumentSplitters.recursive(500, 100)
但是你只能控制chunk和overlap的大小,无法改变它的“切法”
方案二:自己先切,再喂给它
比如按接口切
String[] parts = document.split("【接口说明】");
List<TextSegment> segments = new ArrayList<>();
for (String part : parts) {
segments.add(TextSegment.from(part));
}
按业务切,再用 recursive 微调
List<TextSegment> finalSegments = new ArrayList<>();
for (String part : parts) {
List<TextSegment> subSegments =
DocumentSplitters.recursive(500, 100)
.split(TextSegment.from(part));
finalSegments.addAll(subSegments);
}
方案三:完全自定义切分器
public List<TextSegment> splitBySentence(String text) {
List<TextSegment> list = new ArrayList<>();
String[] sentences = text.split("。|!|?");
for (String s : sentences) {
list.add(TextSegment.from(s));
}
return list;
}
无论哪种方法,请记住:
-
一个接口 = 一个chunk(不能拆)
-
一问一答必须在一起
-
表格 / 配置说明不能被拆散
-
有明显结构的文档,比如:标题,小节,列表,recursive 很容易切坏
没有最好的,只有适合自己的。
【五、chunk在RAG中的地位】
chunk 在 RAG 三要素中的位置
RAG效果 = chunk × embedding × 检索
但在工程实践中:
chunk 的权重远高于你想象,原因很简单:
embedding 再强,也无法弥补“信息被切断”
检索再准,也找不到“不存在的chunk”
换句话说
embedding 决定“找得准不准”,
chunk 决定“有没有得找”
好了,讲到这里,相信你已经对chunk有了深刻的理解,赶紧去看看你的chunk合理不合理吧!
`📦 配套完整源码为方便大家直接落地,我把完整工程整理好了:完整 Spring Boot 项目、pgvector 建表脚本、chunk 切分优化、批量导入、Postman 示例一应俱全。
关注公众号「进阶技术客栈」,回复 RAG 源码 即可领取。`