RAG 实战:从原理、文本切割到 Milvus 落地
从 RAG 原理讲起,覆盖文档加载、三类文本分割器对比、Milvus 与 MySQL 双库架构,并用 Node.js 串起加载、切分、入库与检索的最小工程链路。
落地大模型应用时,常见两个问题:知识滞后和幻觉。
预训练模型的知识是静态的:训练截止之后的新信息、企业内部文档、业务专属资料,它都感知不到。遇到未知内容,模型只能按概率补全,写出通顺却不一定正确的答案。
RAG(Retrieval-Augmented Generation,检索增强生成) 是目前缓解幻觉、做私有知识问答的主流方案,也是企业知识库与 Agent 记忆的常见底座。
本文顺着完整链路往下拆:原理 → 文档加载与切分 → Milvus 工程落地 → 最小可运行串联。
什么是 RAG
RAG = Retrieval(检索)+ Augmented(增强)+ Generation(生成)。
核心想法很直接:先检索专属知识库,再让模型基于真实资料作答,而不是凭空生成。
- 传统问答:用户提问 → 模型靠训练数据生成
- RAG 问答:用户提问 → 检索私有库 → 资料拼进提示词 → 模型基于资料作答
完整工作链路
整套流程分两段:离线构建知识库,以及在线问答推理。底层依赖向量语义检索,而不是传统关键词匹配。
flowchart LR
subgraph offline["离线阶段:知识库构建"]
A["原始文档 PDF / Word / 网页 / 笔记"] --> B["Loader 解析为标准 Document"]
B --> C["Splitter 切成 Chunk"]
C --> D["Embedding 文本转向量"]
D --> E["Milvus 存储向量与业务 ID"]
end
subgraph online["在线阶段:用户问答"]
F["用户提问 Query"] --> G["Embedding 生成查询向量"]
G --> H["Milvus 相似度召回 Top-N Chunk"]
H --> I["过滤与重排"]
I --> J["Prompt:问题 + 参考资料"]
J --> K["LLM 基于资料作答"]
K --> L["返回答案"]
end要点可以收成三步:
- 知识库预处理:加载文档、切分、向量化、入库
- 问答检索:问题同样向量化,按相似度召回相关 Chunk
- 增强生成:问题 + 检索资料拼进 Prompt,再交给模型
为什么比关键词检索更合适
关键词检索只能盯字面。搜「大模型如何避免出错」,未必能命中「大模型幻觉的解决方案」。
RAG 靠 Embedding 做语义检索:
- 文本与问题都变成高维向量
- 用余弦相似度衡量语义接近程度
- 字面不同、意思相近的内容也能对上
文档加载与文本预处理
RAG 效果很大一部分取决于预处理。文档太长、语义混杂时,直接整篇向量化,检索会糊成一团。常用两块:Loader 与 Splitter。
Loader:多源文档统一接入
Loader 负责把不同来源、不同格式的数据解析成统一的 Document(文本 + 元数据),方便后续切分与向量化。LangChain 生态里有大量开源 Loader,集中在 @langchain/community。
| Loader 类型 | 支持数据源 | 核心能力 | 适用场景 |
|---|---|---|---|
| PDFLoader | 本地 PDF | 解析正文、保留段落、提取页码等元数据 | 手册、制度文档、电子书 |
| DocxLoader | Word(.docx) | 解析正文与段落结构 | 报告、方案、办公文档 |
| WebBaseLoader | 公开网页 | 抓取正文,尽量去掉广告与导航 | 资讯、博客类知识库 |
| MarkdownLoader | Markdown | 保留标题、代码块、列表结构 | 技术文档、项目手册 |
| TextLoader | 纯文本 .txt | 快速读全文 | 日志、简易笔记 |
| CSVLoader | CSV | 按行把表格转成文本 Chunk | 参数表、台账问答 |
| NotionLoader | Notion | 经 API 同步在线文档 | 团队协作知识库 |
| GitHubLoader | GitHub 仓库 | 解析代码与 README | 代码 / 开源项目问答 |
设计目标一致:屏蔽数据源差异,输出标准 Document,后面的切分、向量化、检索不用再为格式单独适配。
安装依赖:
npm install @langchain/community @langchain/coreTextLoader
import { TextLoader } from "@langchain/community/document_loaders/fs/text";
const loader = new TextLoader("./test.txt");
const docs = await loader.load();
console.log("TXT文档内容:", docs);MarkdownLoader
import { MarkdownLoader } from "@langchain/community/document_loaders/fs/markdown";
const loader = new MarkdownLoader("./doc.md");
const docs = await loader.load();
console.log("MD文档内容:", docs);PDFLoader
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
const loader = new PDFLoader("./book.pdf");
const docs = await loader.load();
console.log("PDF文档页数:", docs.length);WebBaseLoader
import { WebBaseLoader } from "@langchain/community/document_loaders/web/web";
const loader = new WebBaseLoader("https://xxx-blog.com/article");
const docs = await loader.load();
console.log("网页正文内容:", docs);Splitter:文本切割
长文本信息混杂,必须切成合适大小的 Chunk。Chunk 太大,噪声多、命中不准;太小,上下文碎、模型难理解。
多数通用业务场景,优先考虑 RecursiveCharacterTextSplitter。
CharacterTextSplitter
最基础的字符分割:按固定分隔符切割,达到长度就截断。快、简单,但几乎不做语义判断,容易切断句子。适合日志、短句这类轻量文本。
TokenTextSplitter
按 Token 数切割,更贴近模型上下文窗口与计费单位。利于控制单次输入成本,但同样可能拆碎语义,且依赖具体 Tokenizer。
RecursiveCharacterTextSplitter(通用首选)
用多级分隔符递归切割:优先段落,再句号、逗号、空格……大块超长再往下细分;再配 overlap,让相邻 Chunk 保留一点重叠,减少上下文断裂。
flowchart TD
A["原始长文本"] --> B["优先按段落分隔符切割"]
B --> C{"单 Chunk 尺寸是否合规"}
C -->|合规| D["保留语义块"]
C -->|超标| E["用下级分隔符继续切"]
E --> C
D --> F["叠加 Overlap"]
F --> G["输出标准化 Chunk"]| 分割器 | 切割依据 | 语义保留 | 性能开销 | 推荐度 |
|---|---|---|---|---|
| CharacterTextSplitter | 固定字符分隔符 | 弱 | 极低 | 低 |
| TokenTextSplitter | Token 数量 | 一般 | 中等 | 中 |
| RecursiveCharacterTextSplitter | 多级递归分隔 | 较好 | 略高 | 高 |
三类分割器示例
import {
CharacterTextSplitter,
TokenTextSplitter,
RecursiveCharacterTextSplitter
} from "@langchain/textsplitters";
const longText = `RAG技术全称检索增强生成,用于解决大模型幻觉与知识滞后问题。
通过文档检索增强提示词,让大模型基于私有知识库生成精准答案。
是目前企业知识库、AI Agent开发的核心技术栈。`;基础字符分割:
const charSplitter = new CharacterTextSplitter({
chunkSize: 50,
chunkOverlap: 10
});
const charChunks = await charSplitter.splitText(longText);
console.log("基础字符分割结果:", charChunks);按 Token 分割:
const tokenSplitter = new TokenTextSplitter({
chunkSize: 30,
chunkOverlap: 5
});
const tokenChunks = await tokenSplitter.splitText(longText);
console.log("Token精准分割结果:", tokenChunks);递归语义分割(推荐):
const recursiveSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 100,
chunkOverlap: 20,
separators: ["\n\n", "\n", "。", ",", " "]
});
const recursiveChunks = await recursiveSplitter.splitText(longText);
console.log("递归语义分割结果:", recursiveChunks);两个常被忽略、却很关键的点:
- Overlap:相邻块留一段重叠,缓解切分带来的语义断裂
- 场景适配:可用
fromLanguage适配代码文档;也可自定义lengthFunction,在字符计数与 Token 计数之间切换
Milvus:向量存储与检索
关系型数据库擅长精确条件与事务,但不擅长高维向量的语义检索。Milvus 这类向量库,用来存海量向量并做相似度匹配,是 RAG 常见基建之一。
数据层级
常见结构是:Database → Collection → Entity。建集合时要定义 Schema;向量字段通常需要建索引,数据量大时否则检索会明显变慢。
Milvus + MySQL 双库
生产里向量库很少单打独斗。更常见的是 MySQL(或其它业务库)+ Milvus 分工:
flowchart LR SUB["业务原始数据"] -- 结构化字段 --> M1["MySQL"] SUB -- 非结构化文本 --> M2["Milvus"] M1 --> A["条件查询 / 关联 / 事务"] M1 --> B["业务元数据"] M2 --> C["向量存储"] M2 --> D["语义相似度检索"] A --> RES["完整业务问答结果"] B --> RES C --> RES D --> RES
- MySQL:结构化字段、精准查询、事务、元数据
- Milvus:文本向量、语义召回
例如知识库:正文向量进 Milvus 做检索;文章 ID、时间、作者等元数据进 MySQL,两边用业务 ID 关联。
最小落地流水线
把组件串起来,通用路径可以是:
- Loader 接入文档
- 递归分割成 Chunk,配置 overlap
- Embedding 后写入 Milvus,并关联业务 ID
- 提问实时向量化,做相似度检索
- 用业务 ID 回查结构化数据(可选)
- 资料 + 问题组装 Prompt,交给 LLM
下面是 Node.js 最小串联示例(加载 → 切分 → 入库 → 检索):
import { TextLoader } from "@langchain/community/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { Milvus } from "@langchain/community/vector_stores/milvus";
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 100,
chunkOverlap: 20
});
const loader = new TextLoader("./knowledge.txt");
const docs = await loader.load();
const splitDocs = await splitter.splitDocuments(docs);
const vectorStore = await Milvus.fromDocuments(
splitDocs,
embeddings,
{
collectionName: "rag_knowledge",
milvusConfig: {
address: "http://localhost:19530"
}
}
);
const query = "RAG技术的核心作用是什么?";
const results = await vectorStore.similaritySearch(query, 3);
console.log("检索到的参考文档:", results);小结
- RAG:用检索补知识、压幻觉,让模型能用上私有与更新更快的资料
- 预处理:Loader 统一接入;通用场景优先 RecursiveCharacterTextSplitter,并认真配置 overlap
- 存储:Milvus 做语义检索,业务库管结构化元数据,双库互补
- 链路:加载 → 切分 → 嵌入 → 入库 → 检索 → 增强生成
企业知识库、私有文档问答、Agent 记忆,大多都绕不开这套「检索 + 向量库 + 生成」骨架;LangChain / Milvus 只是其中一种常见落地组合。