这是系列的第 18 篇。整个系列写给零基础、想入行 AI 的朋友,每天一篇,30 天后你会做出 3 个能写进简历的项目。
这篇解决什么问题
昨天你的最小 RAG 跑起来了,但有人反馈:简单问题答得挺准,问复杂一点的就答非所问了。
这是个好现象,说明你已经越过了"能不能跑"的阶段,进入"效果好不好"的阶段。今天这篇就是分水岭。RAG 项目做得好不好,八成取决于一个不起眼的环节:文档怎么切。
很多人做完 demo 就停在这里,因为不知道问题出在哪。今天把切分的门道讲清楚。
一、为什么切分这么重要
先回到原理。RAG 的流程是:提问 → 找出最相关的几个片段 → 交给 AI 回答。
注意这里的关键词是"片段"。AI 最终看到的不是你整份文档,而是检索出来的那几段。所以片段切得对不对,直接决定 AI 看到的内容对不对。
切得太碎会怎样?比如一句话被切成两半,"差旅费报销需要提交申请单"和"出差结束后五个工作日内提交发票"。如果提问命中前半句,AI 只看到半条信息,回答就会残缺。
切得太大又会怎样?一段里混了报销、请假、考勤三件事。检索命中了这一段,AI 就要从一堆无关内容里找答案,容易被干扰,还浪费 token。
所以好的切分标准只有一句话:一个片段只讲一件事。
二、三个实用的切分原则
原则一:优先按语义边界切,别机械按字数切。
文档本身就有天然的边界:段落、标题、列表项。按这些边界切,切出来的片段是完整的。硬按 500 字一刀切,很容易把一段完整的话劈开。
原则二:设置重叠区。
相邻片段之间留一点重复内容。比如第 1 段是 1 到 500 字,第 2 段就从 450 字开始,重叠 50 字。这样即使关键信息正好落在边界上,也不会被劈掉。这个参数通常叫 chunk_overlap。
原则三:给每个片段贴上"身份证"。
片段入库时顺便记下它来自哪个文件、第几页、哪个标题。检索出来的时候,你就知道答案的出处了:
python
{
"text": "差旅费报销流程:先提交申请单......",
"source": "员工手册.pdf",
"page": 12,
"section": "第五章 费用管理"
}
这个元数据很有用,一是能在回答里标注来源(用户会更信任),二是能做过滤(比如只在某一类文档里搜)。
三、动手改一版切分代码
先看最朴素的写法,按固定长度切:
python
def split_naive(text, size=500):
"""最朴素的切法:每 500 字一刀(有缺陷,不推荐)"""
return [text[i:i + size] for i in range(0, len(text), size)]
问题很明显:不问语义,硬切。改进版加上重叠:
python
def split_with_overlap(text, size=500, overlap=50):
"""加重叠的切法,避免边界信息被劈开"""
chunks = []
start = 0
while start < len(text):
end = start + size
chunks.append(text[start:end])
start = end - overlap # 关键:下一次从重叠处开始
return chunks
再进一步,优先在段落边界切。这里可以直接用 LangChain 提供的成熟工具,不用自己造轮子:
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每段目标长度
chunk_overlap=50, # 重叠字数
separators=["\n\n", "\n", "。", "!", "?", " ", ""] # 优先在这些位置切
)
chunks = splitter.split_text(长文本)
print(f"切成了 {len(chunks)} 段")
separators 这个列表是精髓,它会从上到下依次尝试:先按空行切(段与段),切完发现某段还是太长,再按换行切,还长就按句号切,最后才按字切。这样切出来的片段尽量保持语义完整。
装包命令是 pip install langchain-text-splitters,用清华源更快。
四、比切分更狠的三招:检索优化
切分是基础,想再往上提一档,还有三个技巧值得知道。
第一招:多召回几条再筛。不要只取 3 条,取 10 条,然后让 AI 或者用算法再筛一遍。多给一点余地,命中率会明显提升。
第二招:改写问题再检索。用户的提问往往很口语,比如"那个报销的事咋弄"。可以让 AI 先把问题改写成规范的检索语句,再去查:
python
def rewrite_question(question):
prompt = f"""把下面的问题改写成适合检索的规范表述,
只输出改写结果,不要解释:
{question}"""
# ......调用 API 返回改写后的问题......
return rewritten
改写完再检索,命中率经常有肉眼可见的提升。
第三招:混合检索。向量检索擅长"意思相近",但对专有名词不敏感。比如搜"泰坦尼克号",向量检索可能返回一堆"船难""沉船"的内容,而关键词检索能精准锁定这个词。把两种检索的结果合并,效果比单独用一种好很多。
这三招你了解就行,做项目时用上前两招,已经比多数入门项目讲究了。
五、怎么判断自己的切分合不合格
给你一个自测方法:随便抽三个片段读一遍。
- 每段是不是在讲一件完整的事?
- 把这段单独拿给一个不了解背景的人看,他能不能看懂?
- 有没有哪个片段是半句话开头、半句话结尾?
三个问题都是"是",切分就算过关了。这个自测很快,但极其有效,比调参数管用。
常见报错排查
报错一:langchain 装不上或导入报错。
版本问题居多。教程里的导入路径会随版本变化,如果 from langchain.text_splitter import ... 报错,换成 from langchain_text_splitters import ... 试试,这是新版路径。
报错二:chunk_size 设多少合适?
没有标准答案,500 到 1000 是常见区间。判断标准是内容密度:技术文档、规范类文字密集,可以小一点;散文、说明性内容可以大一点。用你自己的资料试两三个值,对比检索效果,比问别人更靠谱。
报错三:切出来的片段里有一堆空白或乱码。
PDF 转文本时常见,页眉页脚、页码混进来了。入库前做一遍清洗,去掉空行和重复的页眉,效果立竿见影。
报错四:中文按默认分隔符切效果差。
有些默认配置对中文不友好(英文按空格分词,中文没有空格)。检查 separators 里有没有中文标点,像"。""?""!"这些一定要加进去。
今天的作业
拿你昨天的资料,用两种参数各跑一遍(比如 chunk_size 设 300 和 800),问同一个问题,对比检索出来的片段有什么不同。把观察结果贴到评论区。
这个对比做过一次,你对切分的理解就超过大部分只跑过 demo 的人了。
明天预告
Day 19:《项目二完结:RAG 知识库问答系统,把自己攒的资料变成私人顾问》。明天是项目二的大结局,我们会把前面四天学的东西组装成一个完整系统:能导入文档、能问答、能标出来源、资料里没有就老实说不知道。这是你简历上第二个项目,含金量比第一个高。
*系列目录:30天从零开始学AI应用 开发