RAG 灵魂三问:你的文档是怎么"喂"给大模型的?

RAG 灵魂三问:你的文档是怎么"喂"给大模型的?

🔥 本文基于 LangChain + Cheerio 实战,聊聊 RAG 中最容易被忽视却最影响效果的环节------文档切片(Text Splitting)

前言

搞 RAG 的同学都知道,RAG 的全称是 Retrieval-Augmented Generation(检索增强生成)。听起来很高大上,但拆开来看就三步:

  1. 把文档切成小块(Splitting)
  2. 把小块变成向量(Embedding)
  3. 检索 + 生成(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();

这段代码做了什么?

  1. axios 发 HTTP 请求 → 拿到一坨 HTML 字符串
  2. cheerio.load(html) → 在内存里虚拟化一个 DOM 树(没错,就像浏览器里的那个 DOM)
  3. $('.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 优化的第一步。如果你想要更好的效果,还需要考虑:

  1. Embedding 模型选择 :中文场景推荐 text2vec-large-chinese 或 OpenAI 的 text-embedding-3-small
  2. 检索策略:不要只用向量检索,试试混合检索(向量 + BM25)
  3. Reranking:用 Cross-Encoder 对检索结果重排序
  4. Prompt 工程:给模型明确的指令,告诉它如何利用检索到的上下文

但这一切的前提是------你的切片得先做好

总结

回到开头的灵魂三问:

问题 答案
为什么要切片? 为了检索精度和语义完整性
怎么切? RecursiveCharacterTextSplitter + 合适的分隔符
切多大? 300-800 字符,具体看文档类型

RAG 不是什么黑魔法,它的核心就是把正确的信息,在正确的时间,以正确的粒度,喂给大模型

而切片,就是这个"正确粒度"的第一道关卡。


📚 参考资料

💬 互动话题:你在 RAG 项目中遇到过哪些切片的坑?欢迎在评论区分享!


如果这篇文章对你有帮助,别忘了点赞 👍 收藏 ⭐ 关注,后续会继续分享 RAG 实战系列!

相关推荐
陳陈陳1 小时前
LLM的"消化系统":一文讲透RAG文档加载与智能切割
数据库·langchain·llm
武子康2 小时前
HunyuanVideo 全家族选型:原版 13B / I2V / Avatar / Foley / 1.5 8.3B 怎么分工
人工智能·llm·aigc
CS semi3 小时前
【LangChain篇章】@tool使用(创建+绑定+调用)
langchain
南方程序猴3 小时前
Codex CLI + VS Code 组合教程:从 Node.js 安装到接入国内中转站
gpt·ai·chatgpt·node.js·ai编程
先吃饱再说4 小时前
后端也能用 jQuery 爬网页?Cheerio 了解一下
爬虫·node.js·jquery
qq_212838974 小时前
后端Node.js + MySQL,前端Html+js,Nginx反向代理快速部署WEB项目
前端·mysql·node.js
徐小超4 小时前
从0到1落地AI知识问答系统(一):AI结对协作实战技巧
vue.js·node.js·全栈
呆呆敲代码的小Y5 小时前
RAG 表格解析完全指南:xParse、PaddleOCR、MinerU 实测对比并接入Agent使用
ai·llm·agent·rag·表格解析·xparse
先吃饱再说10 小时前
一篇文章被切成 100 块还怎么保持语义:RAG 分块的底层逻辑
langchain·llm