96SEO 2026-05-06 20:59 25
我们常常会遇到两个让人抓狂的问题:要么是模型一本正经地胡说八道,要么就是它对你的私有数据一问三不知。想象一下你费尽心思搭建了一个客服机器人,结果它连自己公司的退货政策dou搞不清楚,这岂不是hen尴尬?

这时候,RAG 就像是一根救命稻草出现了。简单来说RAG 就是给大模型外挂了一个“知识库”,让它在回答问题之前,先去翻翻书,kankan资料里是怎么说的。今天我们就结合 LangChain,来深入探讨一下如何构建一个高质量的 RAG 系统,让 AI 的生成结果不再“飘”在空中,而是脚踏实地。
一、为什么我们需要 RAG?大模型虽然聪明,但有两个天然的“硬伤”:
1. 知识盲区: 模型的训练数据是有截止日期的。Ru果你问它“今年的诺贝尔文学奖得主是谁?”,它大概率会一脸懵圈,因为它根本没学过新发生的事。
// 用户问:"2024年诺贝尔文学奖得主是谁?"
// LLM 回答:"抱歉,我的训练数据截止到 2023 年..."
2. 幻觉问题: 有时候为了“讨好”用户,模型会编造一些听起来hen合理但完全错误的信息。特别是在处理企业内部文档、医疗记录或法律条款时这种胡编乱造是绝对不Neng容忍的。
RAG 的核心思路非常直观:在提问时先从知识库里找到Zui相关的内容,再把它作为上下文提供给模型,让模型“带着资料”回答。这就像考试从“闭卷”变成了“开卷”,答案自然geng准确、geng可靠。
二、RAG 的核心工作流一个完整的 RAG 系统其实并不神秘,它主要分为两个大的阶段:索引阶段和检索生成阶段。我们Ke以通过下面的流程图来直观地理解这个过程:
flowchart TB
subgraph Indexing
L --> S
S --> EM
EM --> VS
end
subgraph Retrieval
Q --> QE
QE --> SR
VS --> SR
SR --> Docs
end
subgraph Generation
Docs --> Prompt
Q --> Prompt
Prompt --> LLM
LLM --> Answer
end
style Indexing fill:#e8f4fd,stroke:#1890ff,stroke-width:2px
style Retrieval fill:#fff7e6,stroke:#fa8c16,stroke-width:2px
style Generation fill:#f6ffed,stroke:#52c41a,stroke-width:2px
接下来我们就把这个流程拆解开,一步步kankan如何用代码实现,以及其中有哪些不为人知的细节和坑。
三、阶段 1:文档加载万事开头难,RAG 的第一步就是把你的数据“喂”给系统。这些数据可Neng散落在 PDF、Word、网页甚至是 GitHub 仓库里。LangChain 提供了非常丰富的文档加载器,帮我们把各种格式的文件转换成统一的 Document 对象。
一个标准的 Document 对象长这样:
{
pageContent: "产品的保修期为一年...",
metadata: {
source: "./docs/warranty.pdf",
page: 5,
author: "TechCorp"
}
}
1. 常见文档加载器实战
假设你有一个 100 页的产品手册,我们来kankan怎么加载它:
PDF 文档
PDF 是企业知识库中Zui常见的格式,处理起来也Zui麻烦。
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
const pdfLoader = new PDFLoader;
const pdfDocs = await pdfLoader.load;
console.log;
// 每个页面对应一个 Document 对象
Word 文档
import { DocxLoader } from "@langchain/community/document_loaders/fs/docx";
const docxLoader = new DocxLoader;
const docxDoms = await docxLoader.load;
CSV 表格
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
const csvLoader = new CSVLoader;
const csvDocs = await csvLoader.load;
// 每行数据转换为一个 Document
// pageContent: "产品名称: iPhone 15, 价格: 5999"
// metadata: { row: 0 }
网页内容
import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";
const webLoader = new CheerioWebBaseLoader;
const webDocs = await webLoader.load;
// 自动提取网页的文本内容,去除 HTML 标签
GitHub 仓库
这对于构建代码问答机器人非常有用。
import { GithubRepoLoader } from "@langchain/community/document_loaders/web/github";
const githubLoader = new GithubRepoLoader(
"https://github.com/langchain-ai/langchainjs",
{
branch: "main",
recursive: false, // 是否递归加载子目录
unknown: "warn" // 遇到未知文件类型的处理方式
}
);
const codeDocs = await githubLoader.load;
2. 批量加载多个文件
实际业务中,文件通常是以文件夹形式存在的。使用 DirectoryLoader Ke以实现多个文档的加载。
import { DirectoryLoader } from "langchain/document_loaders/fs/directory";
// 加载整个目录
const directoryLoader = new DirectoryLoader(
"./docs",
{
".pdf": => new PDFLoader,
".docx": => new DocxLoader,
".txt": => new TextLoader,
}
);
const allDocs = await directoryLoader.load;
console.log;
四、阶段 2:文档切割
LLM 的上下文窗口有限,不Neng直接塞进整个文档,这就需要对文档进行适当的切割。而切割策略直接影响检索质量,这是hen多新手容易忽视的地方。
LangChain 中Zui常用的切割器是 RecursiveCharacterTextSplitter。它的聪明之处在于会尝试多种分隔符,优先在语义边界处切割。
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, // 每个 chunk 的Zui大字符数
chunkOverlap: 200, // 相邻 chunk 的重叠字符数
separators: ,
});
const chunks = await splitter.splitDocuments;
console.log;
1. ⚠️ 切割策略的常见误区
这里有几个大坑,大家一定要避开:
误区 1:chunkSize 越大越好❌ 错误Zuo法:
chunkSize: 5000 // 太大!
问题: Ru果 chunk 太大,包含的信息太多,检索时就会引入hen多噪音,导致模型“迷路”。而且,大 chunk 也会导致 Embedding 的语义变得不纯粹。
误区 2:忽略 chunkOverlap❌ 错误Zuo法:
chunkOverlap: 0 // 没有重叠
问题: Ru果没有重叠,一些关键的句子可Neng正好被切断在两个 chunk 之间。比如“退货期限为30天”可Neng被切成了“退货期限为”和“30天”,导致检索失败。通常建议设置 chunkSize 的 10%-20% 作为重叠。
chunkOverlap: chunkSize * 0.2 // 20% 的重叠
误区 3:中文文档用英文分隔符
❌ 错误Zuo法:
separators: // 缺少中文标点
✅ 推荐Zuo法:
separators:
2. 其他切割器
针对不同类型的文档,我们还Ke以使用geng专业的切割器。
Markdown 专用切割器
import { MarkdownTextSplitter } from "@langchain/textsplitters";
const mdSplitter = new MarkdownTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
const mdChunks = await mdSplitter.splitDocuments;
代码专用切割器
代码的切割非常讲究,不Neng随便把一个函数名切断。
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
const codeSplitter = RecursiveCharacterTextSplitter.fromLanguage(
"typescript", // 支持 python, java, javascript 等
{
chunkSize: 1000,
chunkOverlap: 100,
}
);
// 会识别代码结构,避免在中间切割
const codeChunks = await codeSplitter.splitDocuments;
五、阶段 3:Embedding 生成
文档切好后下一步就是把它变成机器Neng理解的向量。Embedding 就是将文本转换为向量,使得语义相似的文本在向量空间中距离geng近。
你Ke以把 Embedding 理解为给每一段文字分配一个独一无二的“语义坐标”。
1. 选择 Embedding 模型OpenAI 的模型效果Zui好,但需要付费;HuggingFace 的模型免费,但需要本地计算资源。
使用 OpenAI:
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings({
model: "text-embedding-3-small", // 性价比Zui高
// model: "text-embedding-3-large", // 效果geng好,但geng贵
});
// 生成向量
const vector = await embeddings.embedQuery;
console.log; // 1536 维或 3072 维
使用 HuggingFace:
import { HuggingFaceTransformersEmbeddings } from "@langchain/community/embeddings/hf_transformers";
const embeddings = new HuggingFaceTransformersEmbeddings({
modelName: "Xenova/all-MiniLM-L6-v2", // 轻量级模型
});
// 优点:免费、隐私性好
// 缺点:效果略逊于 OpenAI,需要本地计算资源
2. 批量生成 Embedding
为了提高效率,我们通常批量处理文档。
// 单个文本
const vector1 = await embeddings.embedQuery;
// 批量文本
const vectors = await embeddings.embedDocuments();
console.log; // 3
console.log; // 384
六、阶段 4:向量存储
向量有了总得找个地方存吧。向量数据库专门负责存储和检索 Embedding 向量。市面上有hen多选择,比如 Pinecone、Chroma、Weaviate、PGVector 等。
1. PineconePinecone 是一个全托管的向量数据库,性Neng强劲,但需要付费。
pnpm add @langchain/pinecone @pinecone-database/pinecone
创建索引
import { Pinecone } from "@pinecone-database/pinecone";
const pinecone = new Pinecone({
apiKey: process.env.PINECONE_API_KEY!
});
// 创建索引
await pinecone.createIndex({
name: "my-knowledge-base",
dimension: 1536, // 与 Embedding 维度一致
metric: "cosine", // 相似度度量:cosine / euclidean / dotproduct
spec: {
serverless: {
cloud: "aws",
region: "us-east-1",
},
},
});
存储向量
import { PineconeStore } from "@langchain/pinecone";
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings;
const index = pinecone.Index;
// 从 Documents 批量写入
const vectorStore = await PineconeStore.fromDocuments(
chunks, // 切割后的文档块
embeddings, // Embedding 模型
{
pineconeIndex: index,
namespace: "product-docs", // 可选:命名空间隔离
}
);
console.log;
2. Chroma
Chroma 是开源的,非常适合本地开发和测试。
pnpm add chromadb
import { Chroma } from "@langchain/community/vectorstores/chroma";
const vectorStore = await Chroma.fromDocuments(
chunks,
embeddings,
{
collectionName: "my-collection",
url: "http://localhost:8000", // Chroma 服务地址
}
);
启动 Chroma 服务:
docker run -p 8000:8000 chromadb/chroma
3. PGVector
Ru果你Yi经在用 PostgreSQL,那么 PGVector 是Zui方便的选择,不需要引入新的数据库组件。
pnpm add @langchain/community pg
import { PGVectorStore } from "@langchain/community/vectorstores/pgvector";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
const vectorStore = await PGVectorStore.initialize(
embeddings,
{
pool,
tableName: "documents",
columns: {
idColumnName: "id",
vectorColumnName: "embedding",
contentColumnName: "content",
metadataColumnName: "metadata",
},
}
);
// 添加文档
await vectorStore.addDocuments;
七、阶段 5:检索与生成
数据准备好了现在终于Ke以开始“开卷考试”了。这是 RAG 的在线阶段,每次用户提问时执行。
1. 基础检索// 从向量存储创建检索器
const retriever = vectorStore.asRetriever({
k: 4, // 返回Zui相似的 4 个文档
});
// 执行检索
const docs = await retriever.invoke;
console.log; // 4
console.log; // Zui相关的文档内容
console.log; // 元数据
2. 构建 RAG Prompt
检索到的文档需要通过 Prompt 喂给 LLM。Prompt 的设计至关重要,要明确告诉 LLM 的行为准则。
import { ChatPromptTemplate } from "@langchain/core/prompts";
const ragPrompt = ChatPromptTemplate.fromMessages(,
,
]);
3. 执行生成链
import { ChatOpenAI } from "@langchain/openai";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { createRetrievalChain } from "langchain/chains/retrieval";
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0 // 确定性输出
});
// 第一步:创建文档合并链
const combineDocsChain = await createStuffDocumentsChain({
llm: model,
prompt: ragPrompt,
});
// 第二步:创建检索链
const ragChain = await createRetrievalChain({
retriever,
combineDocsChain,
});
// 第三步:执行
const result = await ragChain.invoke({
input: "产品的退货政策是什么?",
});
console.log; // 基于知识库的回答
console.log; // 检索到的文档片段
返回结果结构:
{
input: "产品的退货政策是什么?",
context: ,
answer: "根据产品手册,退货政策如下:
1. 退货期限:购买后30天内
2. 退款时间:7个工作日内处理
来源:product-manual.pdf 第5页"
}
4. 显示引用来源
为了增加可信度,我们Ke以把引用的来源也展示出来。
// 提取来源信息
const sources = result.context.map(doc => ({
content: doc.pageContent.slice + "...",
source: doc.metadata.source,
page: doc.metadata.page,
}));
console.log;
sources.forEach => {
console.log`);
console.log;
});
八、检索策略进阶
基础的相似度检索Neng覆盖大多数场景,但在实际业务中,往往需要geng精细的检索策略。
1. 混合检索纯向量检索有时候对关键词的匹配不够敏感。混合检索结合了“关键词检索”和“向量检索”的优势。
混合检索的优势:
这有点类似你在参加考试:既要理解题目的意思,又要找到书里的原话。
使用 Weaviate 实现混合检索:
import { WeaviateStore } from "@langchain/weaviate";
const vectorStore = await WeaviateStore.fromDocuments(
chunks,
embeddings,
{
client: weaviateClient,
indexName: "Documents",
textKey: "text",
metadataKeys: ,
}
);
// 混合检索
const hybridRetriever = vectorStore.asRetriever({
searchType: "hybrid",
searchKwargs: {
alpha: 0.5, // 0: 纯关键词, 1: 纯向量, 0.5: 各一半
},
});
const docs = await hybridRetriever.invoke;
alpha 参数调优:
alpha = 0: 完全依赖关键词匹配,适合查找专有名词。
alpha = 1: 完全依赖向量语义,适合同义词搜索。
alpha = 0.5: 平衡模式。
问题: 向量检索是“粗检索”,为了不漏掉相关内容,通常会召回较多的文档。但这其中可Neng包含hen多相关性不强的噪音。
解决方案: 引入一个重排序模型,对召回的文档进行精细打分,只把Zui相关的 Top N 交给 LLM。
import { CohereRerank } from "@langchain/cohere";
const reranker = new CohereRerank({
apiKey: process.env.COHERE_API_KEY,
topN: 3, // 重排序后取前 3 个
model: "rerank-multilingual-v2.0", // 支持多语言
});
// 第一步:粗检索
const coarseResults = await retriever.invoke;
// 第二步:精排序
const refinedResults = await reranker.compressDocuments(
coarseResults,
query
);
console.log; // 3
效果对比:
经过 Rerank 后进入 Prompt 的上下文质量大幅提升,LLM 的回答准确率也会显著提高。
3. 查询用户的原始问题有时候表述不清,比如“它有几年历史了?”,这里的“它”指代不明。直接检索效果会hen差。
让 LLM 先对问题Zuo ,再检索:
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
// 定义查询
Prompt
const queryRewriterPrompt = ChatPromptTemplate.fromMessages(,
,
]);
const model = new ChatOpenAI;
const rewriteChain = queryRewriterPrompt.pipe;
//
查询
const rewrittenQuery = await rewriteChain.invoke({
question: "它有几年历史了?",
});
console.log;
// 输出:"Apple 公司成立多少年了?"
// 用
后的查询进行检索
const docs = await retriever.invoke;
多步查询
对于复杂问题,Ke以进行多轮 :
// 第一轮:澄清指代
const clarified = await clarifyReferences;
// 第二轮:
缩写
const expanded = await expandAbbreviations;
// 第三轮:提取关键词
const keywords = await extractKeywords;
// Zui终查询
const finalQuery = `${expanded} ${keywords}`;
九、实战中的优化技巧
Zui后分享一些在实际项目中踩坑后出来的经验。
1. 针对不同类型的文档使用不同的切割策略// 技术文档:较小的 chunk
const techSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 50,
});
// 故事/文章:较大的 chunk
const storySplitter = new RecursiveCharacterTextSplitter({
chunkSize: 2000,
chunkOverlap: 200,
});
// 代码:使用语言感知切割
const codeSplitter = RecursiveCharacterTextSplitter.fromLanguage(
"typescript",
{ chunkSize: 1000, chunkOverlap: 100 }
);
2. 检索参数调优
// 从小 k 值开始,逐步增加
const retriever = vectorStore.asRetriever({
k: 3, // 从 3 开始测试
});
// 监控检索结果的相关性
const docs = await retriever.invoke;
docs.forEach => {
console.log;
});
// Ru果相关性低,增大 k 或改进 Embedding
3. Prompt 优化
// 不好的 Prompt
const badPrompt = ChatPromptTemplate.fromMessages(,
]);
// 好的 Prompt
const goodPrompt = ChatPromptTemplate.fromMessages(,
,
]);
十、本章小结
RAG 是让 LLM 具备实时知识和私有数据Neng力的关键技术,在减少AI输出幻觉和增加准确度上dou非常重要。通过 LangChain,我们Ke以将这一复杂的流程模块化,从文档加载、切割、向量化到Zui终的检索生成,每一步dou有精细的控制空间。
当然RAG 也不是万Neng药。Ru果你的文档本身质量hen差,或者切割策略极其不合理,那么“垃圾进,垃圾出”的定律依然适用。希望这篇文章Neng帮你避开一些常见的坑,构建出geng精准、geng聪明的 AI 应用。
💡 提示: RAG是一整套技术体系,涉及文本处理、向量数据、优化等多个知识体系,请关注后续推出的完整的RAG教程。
下一章:《第九章 —— 多 Agent 系统》
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback