RAG 灵魂三问:你的文档是怎么"喂"给大模型的?
🔥 本文基于 LangChain + Cheerio 实战,聊聊 RAG 中最容易被忽视却最影响效果的环节------文档切片(Text Splitting)
前言
搞 RAG 的同学都知道,RAG 的全称是 Retrieval-Augmented Generation(检索增强生成)。听起来很高大上,但拆开来看就三步:
- 把文档切成小块(Splitting)
- 把小块变成向量(Embedding)
- 检索 + 生成(Retrieval + Generation)
大多数人把精力花在了第 3 步------选什么模型、怎么调 prompt、检索策略怎么优化。但说实话,第 1 步才是真正的"地基"。
你切片切得好,后面怎么检索都不会太差;切片切得烂,用 GPT-5.6 也救不回来。
这就像做饭,食材处理不好,再好的锅也炒不出米其林的味道。
一、先来点"原始人"操作:手搓爬虫
在用框架之前,我们先搞清楚底层原理。来看最朴素的网页爬取方式:
javascript
import axios from 'axios';
import * as cheerio from 'cheerio';
const targetUrl = 'https://juejin.cn/post/7660707431753678854';
async function crawlPage() {
const { data: html } = await axios.get(targetUrl);
const $ = cheerio.load(html);
const pageContent = $('.main-area p').text();
console.log(pageContent);
}
crawlPage();
这段代码做了什么?
- axios 发 HTTP 请求 → 拿到一坨 HTML 字符串
- cheerio.load(html) → 在内存里虚拟化一个 DOM 树(没错,就像浏览器里的那个 DOM)
- $('.main-area p').text() → 用 CSS 选择器精准提取文章段落
cheerio 这个库真的很妙------它让 Node.js 开发者可以用前端的 DOM 操作思维来解析 HTML,不用写那些令人头秃的正则表达式。
💡 小知识 :cheerio 的
load()会把整个 HTML 字符串解析成树状结构,然后你用 CSS 选择器去"遍历"这棵树找到目标节点。这和浏览器渲染页面的原理是一样的,只不过 cheerio 不会渲染、不执行 JS,纯粹是个"解析器"。
二、LangChain 正式登场:优雅地"喂"数据
手搓爬虫理解原理可以,但实际项目里我们有更优雅的选择------LangChain。
javascript
import "dotenv/config";
import "cheerio";
import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
// 第一步:加载文档
const cheerioLoader = new CheerioWebBaseLoader(
'https://juejin.cn/post/7660707431753678854',
{
selector: '.main-area p' // CSS 选择器,精准定位内容
}
);
const documents = await cheerioLoader.load();
// 第二步:切片
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 400,
separators: ["。", "!", "?"],
chunkOverlap: 100,
});
const splitDocuments = await textSplitter.splitDocuments(documents);
console.log(splitDocuments);
两步走,干净利落。但我们得深入理解每个参数背后的"小心思"。
三、灵魂拷问:为什么要切片?
你可能会问:直接把整篇文章扔给大模型不行吗?
答案是:不行,至少在 RAG 场景下不行。
原因有三:
1. 上下文窗口是有限的
即使现在 GPT-5.6 支持超长上下文,Claude 也支持 200K,但你不可能把整个知识库都塞进去。你需要检索出最相关的片段,而不是全部内容。
2. 检索精度要求小块
向量检索的本质是语义相似度计算。如果一个 chunk 太大(比如整篇文章),它的向量表示会被太多不相关的信息"稀释",导致检索精度下降。
3. 生成质量依赖上下文相关性
大模型在生成回答时,更倾向于利用与问题高度相关的上下文。如果你给它一整篇文章,它可能找不到重点,或者被无关内容"带偏"。
四、RecursiveCharacterTextSplitter:切片界的"瑞士军刀"
这个类名看起来很长,但它的核心思想其实很朴素:
递归地尝试不同的分隔符,找到最优的切分方式,使每个 chunk 都有完整的语义。
来看参数配置:
javascript
{
chunkSize: 400, // 每个 chunk 的目标大小(字符数)
separators: ["。", "!", "?"], // 分隔符优先级
chunkOverlap: 100, // 相邻 chunk 的重叠字符数
}
chunkSize:400 是怎么来的?
这不是拍脑袋定的。一般来说:
- 太小(< 200):语义碎片化,上下文不完整,检索到了也答不好
- 太大(> 1000):检索精度下降,不相关信息干扰
- 甜蜜区间(300-800):平衡语义完整性和检索精度
400 是个不错的起点,但具体值要根据你的文档类型和模型能力来调。
separators:为什么用中文标点?
注意这里用的是 ["。", "!", "?"],这是中文标点。
LangChain 的默认分隔符是英文的 ["\n\n", "\n", " ", ""]。但如果你处理的是中文文档,用中文标点作为分隔符,切出来的 chunk 语义会更完整------因为中文的句子边界就是这些标点。
💡 进阶技巧 :你可以设置多级分隔符,比如
["\n\n", "。", "!", "?", ",", " "]。LangChain 会优先用第一个分隔符切分,如果切出来的 chunk 还是太大,就用下一个,以此类推。
chunkOverlap:这个"重叠"到底在保护什么?
这是最容易被误解的参数。
假设你有一段话:"张三今天去了北京。他在北京见了李四。他们讨论了项目方案。"
如果没有重叠,可能切分成:
- Chunk 1:
"张三今天去了北京。" - Chunk 2:
"他在北京见了李四。他们讨论了项目方案。"
问题来了:"他"是谁? 如果检索只命中 Chunk 2,模型就不知道"他"指的是"张三"。
加上 100 字符的重叠后:
- Chunk 1:
"张三今天去了北京。他在北京见了李四。" - Chunk 2:
"他在北京见了李四。他们讨论了项目方案。"
这样,即使只检索到 Chunk 2,模型也能从上下文推断出"他"是张三。
chunkOverlap 就是语义完整性的"保险丝"。
五、实战踩坑:你可能会遇到的问题
坑 1:中文分词的陷阱
如果你用英文的默认分隔符处理中文,可能会出现这种情况:
arduino
"今天天气真好我想出去玩" → 按空格切分 → 整段话变成一个 chunk
因为中文没有空格分词!所以一定要设置中文标点作为分隔符。
坑 2:chunkOverlap 设太大的后果
overlap 不是越大越好。如果你设了 300 的 overlap,chunkSize 才 400,那每个 chunk 有 75% 的内容是重复的!这会导致:
- 存储成本翻倍
- 检索时同一个信息被命中多次
- 向量数据库里充斥着大量相似的向量
经验法则:chunkOverlap 设为 chunkSize 的 10%-25%。
坑 3:CSS 选择器选不对
爬取网页时,CSS 选择器的选择至关重要。比如掘金的文章,如果你用 .article-content 而不是 .main-area p,可能会拿到一堆导航栏、侧边栏的垃圾内容。
建议:先在浏览器 DevTools 里调试好选择器,再写代码。
六、不止于切片:RAG 优化的完整思路
文档切片只是 RAG 优化的第一步。如果你想要更好的效果,还需要考虑:
- Embedding 模型选择 :中文场景推荐
text2vec-large-chinese或 OpenAI 的text-embedding-3-small - 检索策略:不要只用向量检索,试试混合检索(向量 + BM25)
- Reranking:用 Cross-Encoder 对检索结果重排序
- Prompt 工程:给模型明确的指令,告诉它如何利用检索到的上下文
但这一切的前提是------你的切片得先做好。
总结
回到开头的灵魂三问:
| 问题 | 答案 |
|---|---|
| 为什么要切片? | 为了检索精度和语义完整性 |
| 怎么切? | RecursiveCharacterTextSplitter + 合适的分隔符 |
| 切多大? | 300-800 字符,具体看文档类型 |
RAG 不是什么黑魔法,它的核心就是把正确的信息,在正确的时间,以正确的粒度,喂给大模型。
而切片,就是这个"正确粒度"的第一道关卡。
📚 参考资料:
💬 互动话题:你在 RAG 项目中遇到过哪些切片的坑?欢迎在评论区分享!
如果这篇文章对你有帮助,别忘了点赞 👍 收藏 ⭐ 关注,后续会继续分享 RAG 实战系列!