深入浅出 LangChain —— 第八章:RAG 检索增强生成

📖 本章学习目标

完成本章后,你将能够:

  • ✅ 理解 RAG 的核心价值和应用场景
  • ✅ 掌握 RAG Pipeline 的五个阶段及其实现
  • ✅ 选择合适的文档切割策略和 Embedding 模型
  • ✅ 根据业务需求选择向量数据库
  • ✅ 实现进阶检索策略(混合检索、查询改写、重排序)
  • ✅ 诊断和优化 RAG 系统的常见问题

一、为什么需要 RAG

LLM 有两大固有知识型缺陷,RAG 正是为了解决这些问题而生。

💡 提示: RAG是一整套技术体系,涉及文本处理、向量数据、优化等多个知识体系,请关注后续推出的完整的RAG教程。

1、LLM 的局限性

问题 1:知识截止

现象:

typescript 复制代码
// 用户问:"2025 年诺贝尔文学奖得主是谁?"
// LLM 回答:"抱歉,我的训练数据截止到 2024 年..."

原因:

  • LLM 的知识来自静态的训练数据
  • 训练完成后,知识就固定了
  • 无法知道最新发生的事件

问题 2:私有数据缺失

现象:

typescript 复制代码
// 用户问:"我们公司的产品退货政策是什么?"
// LLM 回答:"我不知道贵公司的具体政策..."

原因:

  • LLM 没有访问你公司内部数据的权限
  • 训练数据不包含你的私有文档
  • 每个公司的业务流程都不一样

2、RAG 的解决方案

RAG = Retrieval(检索)+ Augmented(增强)+ Generation(生成)

核心思路:在提问时,先从知识库里找到最相关的内容,再把它作为上下文提供给模型,让模型"带着资料"回答

这有点类似你在参加考试:

  • 不使用 RAG:闭卷考试,只能靠记忆
  • 使用 RAG:开卷考试,可以查阅参考书

显然,开卷考试的答案更准确、更可靠。

3、RAG 的工作流程

flowchart LR Q["用户问题"] --> E1["Embedding 化"] E1 --> R["向量检索
找出最相关的文档片段"] R --> P["构建 Prompt
问题 + 检索到的上下文"] P --> LLM["LLM 生成回答"] LLM --> A["最终回答
基于真实知识库"] DB[("知识库
向量数据库")] --> R style DB fill:#f6ffed,stroke:#52c41a,stroke-width:3px style R fill:#fff7e6,stroke:#fa8c16,stroke-width:3px

关键优势:

优势 说明 效果
实时性 可以随时更新知识库 回答最新信息
准确性 基于真实文档回答 减少幻觉
可追溯 可以显示引用来源 便于验证
成本低 不需要重新训练模型 经济高效

二、RAG Pipeline 的五个阶段

完整的 RAG 系统包含两个主要阶段:索引阶段 (离线)和检索生成阶段(在线)。

整体架构图

flowchart TB subgraph Indexing["索引阶段:离线执行一次"] L["1. 文档加载
Loaders"] --> S["2. 文档切割
Splitters"] S --> EM["3. Embedding 生成
Embeddings"] EM --> VS[("4. 向量存储
Vector Store")] end subgraph Retrieval["检索阶段:每次查询执行"] Q["用户问题"] --> QE["问题 Embedding"] QE --> SR["5. 相似度检索"] VS --> SR SR --> Docs["相关文档片段"] end subgraph Generation["生成阶段:每次查询执行"] Docs --> Prompt["构建 Prompt"] Q --> Prompt Prompt --> LLM["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

接下来,我们逐一详解每个阶段。


三、阶段 1:文档加载(Document Loaders)

文档加载器的作用是将各种格式的文件转换为统一的 Document 对象。

1、Document 对象结构

typescript 复制代码
interface Document {
  pageContent: string;   // 文档内容
  metadata: Record<string, any>;  // 元数据(来源、页码等)
}

示例:

typescript 复制代码
{
  pageContent: "产品的保修期为一年...",
  metadata: {
    source: "./docs/warranty.pdf",
    page: 5,
    author: "TechCorp"
  }
}

2、常见文档加载器

LangChain.js 提供了丰富的文档加载器,支持几乎所有常见格式。

(1)PDF 文档

typescript 复制代码
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";

const pdfLoader = new PDFLoader("./docs/product-manual.pdf");
const pdfDocs = await pdfLoader.load();

console.log(`加载了 ${pdfDocs.length} 个页面`);
// 每个页面对应一个 Document 对象

(2)Word 文档

typescript 复制代码
import { DocxLoader } from "@langchain/community/document_loaders/fs/docx";

const docxLoader = new DocxLoader("./docs/guide.docx");
const docxDoms = await docxLoader.load();

(3)CSV 表格

typescript 复制代码
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";

const csvLoader = new CSVLoader("./data/products.csv");
const csvDocs = await csvLoader.load();

// 每行数据转换为一个 Document
// pageContent: "产品名称: iPhone 15, 价格: 7999"
// metadata: { row: 1 }

(4)网页内容

typescript 复制代码
import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";

const webLoader = new CheerioWebBaseLoader("https://example.com/doc");
const webDocs = await webLoader.load();

// 自动提取网页的文本内容,去除 HTML 标签

(5)GitHub 仓库

typescript 复制代码
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();

// 适合构建代码问答机器人

3、批量加载多个文件

使用DirectoryLoader可以实现多个文档的加载。

typescript 复制代码
import { DirectoryLoader } from "langchain/document_loaders/fs/directory";

// 加载整个目录
const directoryLoader = new DirectoryLoader(
  "./docs",
  {
    ".pdf": (path) => new PDFLoader(path),
    ".docx": (path) => new DocxLoader(path),
    ".txt": (path) => new TextLoader(path),
  }
);

const allDocs = await directoryLoader.load();
console.log(`总共加载了 ${allDocs.length} 个文档`);

四、阶段 2:文档切割(Text Splitters)

LLM 的上下文窗口有限,不能直接塞进整个文档,这是就需要对文档进行适当的切割,而切割策略直接影响检索质量。

1、为什么要切割?

问题场景:

假设你有一个 100 页的产品手册:

  • 不切割:整个手册 50,000 字符,超出 Context Window
  • 按页切割:可能把一个完整的话题截断
  • 智能切割:按语义边界切割,保持话题完整性

2、RecursiveCharacterTextSplitter(推荐)

这是Langchain.js提供的最常用的文档切割器,会尝试多种分隔符,优先在语义边界处切割。

(1)基础用法

typescript 复制代码
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,       // 每个 chunk 的最大字符数
  chunkOverlap: 200,     // 相邻 chunk 的重叠字符数
  separators: [
    "\n\n",   // 优先在段落之间切割
    "\n",     // 其次在换行处切割
    "。",      // 中文句号
    ",",      // 中文逗号
    " ",       // 空格
    ""         // 最后才按字符切割
  ],
});

const chunks = await splitter.splitDocuments(pdfDocs);
console.log(`切割为 ${chunks.length} 个 chunks`);

参数详解:

参数 作用 推荐值 说明
chunkSize 每个块的最大字符数 500-1500 太小会丢失上下文,太大会稀释相关性
chunkOverlap 相邻块的重叠字符数 chunkSize 的 10-20% 防止关键信息在边界处被截断
separators 分隔符优先级列表 如上所示 从粗到细尝试切割

(2)可视化切割过程

bash 复制代码
原始文档:
"第一段内容...\n\n第二段内容...\n\n第三段内容..."

切割过程:
1. 尝试在 "\n\n" 处切割 → 成功
2. 如果某段超过 chunkSize,尝试在 "\n" 处切割
3. 如果还超,尝试在 "。" 处切割
4. 依此类推...

结果:
Chunk 1: "第一段内容..."
Chunk 2: "...第一段内容...\n\n第二段内容..."  (有重叠)
Chunk 3: "...第二段内容...\n\n第三段内容..."

3、其他切割器

(1)Markdown 专用切割器

MarkdownTextSplitter 专门用于切割Markdown文档。

typescript 复制代码
import { MarkdownTextSplitter } from "@langchain/textsplitters";

const mdSplitter = new MarkdownTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 200,
});

// 会识别 Markdown 标题层级,优先在标题处切割
const mdChunks = await mdSplitter.splitDocuments(mdDocs);

(1)代码专用切割器

typescript 复制代码
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const codeSplitter = RecursiveCharacterTextSplitter.fromLanguage(
  "typescript",  // 支持 python, java, javascript 等
  {
    chunkSize: 1000,
    chunkOverlap: 200,
  }
);

// 会识别代码结构(函数、类等),避免在中间切割
const codeChunks = await codeSplitter.splitDocuments(codeDocs);

4、⚠️ 切割策略的常见误区

误区 1:chunkSize 越大越好

❌ 错误做法:

typescript 复制代码
chunkSize: 10000  // 太大!

问题:

  • 一个 chunk 里包含多个话题
  • 检索时相关性计算被稀释
  • 浪费 Token

✅ 推荐做法:

typescript 复制代码
chunkSize: 800-1200  // 根据文档类型调整

误区 2:忽略 chunkOverlap

❌ 错误做法:

typescript 复制代码
chunkOverlap: 0  // 没有重叠

问题:

  • 关键信息可能在边界处被截断
  • 上下文不连贯

✅ 推荐做法:

typescript 复制代码
chunkOverlap: chunkSize * 0.15  // 15% 的重叠

误区 3:中文文档用英文分隔符

❌ 错误做法:

typescript 复制代码
separators: ["\n\n", "\n", " ", ""]  // 缺少中文标点

✅ 推荐做法:

typescript 复制代码
separators: ["\n\n", "\n", "。", ",", ";", " ", ""]

五、阶段 3:Embedding 生成

Embedding 是将文本转换为向量,使得语义相似的文本在向量空间中距离更近。

1、什么是 Embedding?

直观理解:

Embedding 就像给文本分配一个"语义坐标":

  • "猫" → [0.1, 0.8, -0.3, ...]
  • "狗" → [0.2, 0.7, -0.2, ...] (与"猫"接近)
  • "汽车" → [-0.5, -0.1, 0.9, ...] (与"猫"远离)

这样,我们可以通过计算向量距离来判断文本相似度。

2、常用的 Embedding 模型

(1)OpenAI Embeddings(推荐)

typescript 复制代码
import { OpenAIEmbeddings } from "@langchain/openai";

const embeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-small",  // 性价比最高
  // model: "text-embedding-3-large",  // 效果更好,但更贵
});

// 生成向量
const vector = await embeddings.embedQuery("你好,世界");
console.log(vector.length);  // 1536 维(small)或 3072 维(large)

模型对比:

模型 维度 性能 成本(每 1M tokens) 适用场景
text-embedding-3-small 1536 ⭐⭐⭐⭐ $0.02 大多数场景
text-embedding-3-large 3072 ⭐⭐⭐⭐⭐ $0.13 对精度要求极高
text-embedding-ada-002 1536 ⭐⭐⭐ $0.10 旧项目兼容

(2)开源模型(自托管)

typescript 复制代码
import { HuggingFaceTransformersEmbeddings } from "@langchain/community/embeddings/hf_transformers";

const embeddings = new HuggingFaceTransformersEmbeddings({
  modelName: "Xenova/all-MiniLM-L6-v2",  // 轻量级模型
});

// 优点:免费、隐私性好
// 缺点:效果略逊于 OpenAI,需要本地计算资源

3、批量生成 Embedding

typescript 复制代码
// 单个文本
const vector1 = await embeddings.embedQuery("第一个文本");

// 批量文本(更高效)
const vectors = await embeddings.embedDocuments([
  "第一个文本",
  "第二个文本",
  "第三个文本",
]);

console.log(vectors.length);  // 3
console.log(vectors[0].length);  // 1536

六、阶段 4:向量存储

向量数据库专门你负责存储和检索 Embedding 向量。

1、向量数据库选型矩阵

数据库 部署方式 适用场景 特点 学习曲线
Pinecone 云端托管 生产环境首选 零运维,按量付费,高性能 ⭐⭐
Chroma 本地/云端 开发测试、小规模 开源,嵌入式,易上手
PGVector 自托管 已有 PostgreSQL 直接在 PG 里存向量,运维简单 ⭐⭐
Weaviate 自托管/云端 混合检索场景 支持关键词+向量混合搜索 ⭐⭐⭐
Qdrant 自托管/云端 高性能需求 Rust 实现,资源消耗低 ⭐⭐⭐
Milvus 自托管 超大规模 分布式,亿级向量 ⭐⭐⭐⭐

2、Pinecone(生产环境推荐)

(1)安装依赖

bash 复制代码
pnpm add @langchain/pinecone @pinecone-database/pinecone

(2)创建索引

typescript 复制代码
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",
    },
  },
});

(3)存储向量

typescript 复制代码
import { PineconeStore } from "@langchain/pinecone";
import { OpenAIEmbeddings } from "@langchain/openai";

const embeddings = new OpenAIEmbeddings();
const index = pinecone.Index("my-knowledge-base");

// 从 Documents 批量写入
const vectorStore = await PineconeStore.fromDocuments(
  chunks,           // 切割后的文档块
  embeddings,       // Embedding 模型
  { 
    pineconeIndex: index,
    namespace: "product-docs",  // 可选:命名空间隔离
  }
);

console.log("向量存储完成");

3、Chroma(开发测试推荐)

bash 复制代码
pnpm add chromadb
typescript 复制代码
import { Chroma } from "@langchain/community/vectorstores/chroma";

const vectorStore = await Chroma.fromDocuments(
  chunks,
  embeddings,
  {
    collectionName: "my-collection",
    url: "http://localhost:8000",  // Chroma 服务地址
  }
);

启动 Chroma 服务:

bash 复制代码
docker run -p 8000:8000 chromadb/chroma

4、PGVector(PostgreSQL 用户推荐)

bash 复制代码
pnpm add @langchain/community pg
typescript 复制代码
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(chunks);

优势:

  • ✅ 无需额外部署向量数据库
  • ✅ 可以利用 PostgreSQL 的事务和备份
  • ✅ 适合已有 PG 基础设施的团队

七、阶段 5:检索与生成

这是 RAG 的在线阶段,每次用户提问时执行。

1、基础检索

(1)创建检索器

typescript 复制代码
// 从向量存储创建检索器
const retriever = vectorStore.asRetriever({
  k: 5,  // 返回最相似的 5 个文档
});

// 执行检索
const docs = await retriever.invoke("产品的退货政策是什么?");

console.log(docs.length);  // 5
console.log(docs[0].pageContent);  // 最相关的文档内容
console.log(docs[0].metadata);     // 元数据(来源、页码等)

(2)相似度阈值过滤

typescript 复制代码
const retriever = vectorStore.asRetriever({
  k: 5,
  filter: {
    similarityThreshold: 0.7,  // 只返回相似度 > 0.7 的文档
  },
});

2、构建 RAG Prompt

typescript 复制代码
import { ChatPromptTemplate } from "@langchain/core/prompts";

const ragPrompt = ChatPromptTemplate.fromMessages([
  [
    "system",
    `你是一个专业的知识库问答助手。请根据以下上下文信息回答用户的问题。

重要规则:
1. 只基于提供的上下文回答问题
2. 如果上下文中没有相关信息,请明确告知"根据现有文档,我无法回答这个问题"
3. 不要编造答案
4. 在回答末尾标注信息来源

上下文:
{context}`,
  ],
  ["human", "{input}"],
]);

关键点:

  • 明确告诉模型只基于上下文回答
  • 提供"不知道"的出口,避免幻觉
  • 要求标注来源,便于验证

3、完整的 RAG Chain

typescript 复制代码
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(result.answer);     // 基于知识库的回答
console.log(result.context);    // 检索到的文档片段

返回结果结构:

typescript 复制代码
{
  input: "产品的退货政策是什么?",
  context: [
    Document { pageContent: "退货期限为购买后30天...", metadata: {...} },
    Document { pageContent: "退款将在7个工作日内处理...", metadata: {...} },
    // ...更多相关文档
  ],
  answer: "根据产品手册,退货政策如下:\n1. 退货期限:购买后30天内\n2. 退款时间:7个工作日内处理\n\n来源:product-manual.pdf 第5页"
}

4、显示引用来源

typescript 复制代码
// 提取来源信息
const sources = result.context.map(doc => ({
  content: doc.pageContent.slice(0, 100) + "...",
  source: doc.metadata.source,
  page: doc.metadata.page,
}));

console.log("引用来源:");
sources.forEach((src, i) => {
  console.log(`${i + 1}. ${src.source} (第${src.page}页)`);
  console.log(`   ${src.content}\n`);
});

八、检索策略进阶

基础的相似度检索能覆盖大多数场景,但在实际业务中,往往需要更精细的检索策略。

1、混合检索(Hybrid Search)

(1)为什么需要混合检索?

纯向量检索的局限:

  • 对精确关键词匹配不够敏感
  • 例如搜索产品型号"X-2000",向量检索可能找不到

混合检索的优势:

  • 结合关键词搜索(BM25)和向量搜索
  • 兼顾语义相似性和关键词匹配

(2)实现方式

使用 Weaviate:

typescript 复制代码
import { WeaviateStore } from "@langchain/weaviate";

const vectorStore = await WeaviateStore.fromDocuments(
  chunks,
  embeddings,
  {
    client: weaviateClient,
    indexName: "Documents",
    textKey: "text",
    metadataKeys: ["source"],
  }
);

// 混合检索
const hybridRetriever = vectorStore.asRetriever({
  searchType: "hybrid",
  searchKwargs: {
    alpha: 0.5,  // 0: 纯关键词, 1: 纯向量, 0.5: 各一半
  },
});

const docs = await hybridRetriever.invoke("X-2000 产品规格");

alpha 参数调优:

alpha 值 检索类型 适用场景
0.0 纯关键词 搜索精确的产品型号、代码
0.5 平衡模式 大多数场景
1.0 纯向量 语义搜索、概念性问题

2、查询改写(Query Rewriting)

(1)问题场景

用户的原始问题有时候表述不清:

  • "它有几年历史了?"("它"指代什么?)
  • "这个怎么样?"("这个"是什么?)
  • "多少钱?"(什么的价格?)

(2)解决方案

让 LLM 先对问题做改写,再检索:

typescript 复制代码
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";

// 定义查询改写 Prompt
const queryRewriterPrompt = ChatPromptTemplate.fromMessages([
  [
    "system",
    `你是一个查询优化助手。将用户的问题改写为更适合向量检索的格式:
1. 去除指代词(它、这个、那个)
2. 补充完整语义
3. 提取核心实体
4. 只输出改写后的查询,不要解释`,
  ],
  ["human", "原始问题:{question}"],
]);

const model = new ChatOpenAI({ model: "gpt-4o-mini" });
const rewriteChain = queryRewriterPrompt.pipe(model);

// 改写查询
const rewrittenQuery = await rewriteChain.invoke({
  question: "它有几年历史了?",
});

console.log(rewrittenQuery.content);
// 输出:"Apple 公司成立多少年了?"

// 用改写后的查询进行检索
const docs = await retriever.invoke(rewrittenQuery.content as string);

(3)多步查询改写

对于复杂问题,可以进行多轮改写:

typescript 复制代码
// 第一轮:澄清指代
const clarified = await clarifyReferences(originalQuestion);

// 第二轮:扩展缩写
const expanded = await expandAbbreviations(clarified);

// 第三轮:提取关键词
const keywords = await extractKeywords(expanded);

// 最终查询
const finalQuery = `${expanded} ${keywords}`;

3、重排序(Reranking)

(1)为什么需要重排序?

问题:

  • 向量检索返回的结果按相似度排序
  • 相似度 ≠ 相关性
  • 可能返回语义相似但不相关的文档

解决方案:

  • 先用向量检索召回较多结果(如 20 个)
  • 再用专门的重排序模型精排
  • 取前 N 个最相关的

(2)使用 Cohere Reranker

typescript 复制代码
import { CohereRerank } from "@langchain/cohere";

const reranker = new CohereRerank({
  apiKey: process.env.COHERE_API_KEY,
  topN: 3,  // 重排序后取前 3 个
  model: "rerank-multilingual-v2.0",  // 支持多语言
});

// 第一步:粗检索(召回 20 个)
const coarseResults = await retriever.invoke(query, { k: 20 });

// 第二步:精排序(取前 3 个)
const refinedResults = await reranker.compressDocuments(
  coarseResults, 
  query
);

console.log(refinedResults.length);  // 3

效果对比:

策略 召回数量 准确率 延迟 成本
纯向量检索 5 70%
向量 + 重排序 20→3 90%
混合检索 + 重排序 20→3 95%

(3)其他重排序模型

模型 提供商 特点 成本
Cohere Rerank Cohere 效果好,支持多语言 $0.02/100次
Jina Reranker Jina AI 开源,可自托管 免费
BGE Reranker BAAI 中文效果好 免费

九、RAG 的常见问题与调优

1. 问题诊断矩阵

问题现象 可能原因 诊断方法 解决方案
回答不准确 chunk 太大,相关内容被稀释 检查 chunk 大小和内容 减小 chunkSize 到 500-800
找不到相关内容 语义距离太大 检查检索结果的相似度分数 1. 尝试查询改写 2. 切换更好的 Embedding 模型 3. 增大 k 值
来源文档不准确 关键词检索失效 检查是否包含精确关键词 使用混合检索
回答跨段落混乱 chunk 之间上下文丢失 检查 chunkOverlap 增大 chunkOverlap 到 20-25%
频繁出现"无法回答" 检索到的文档不相关 检查检索结果的相关性 1. 加入重排序步骤 2. 调整相似度阈值 3. 优化文档切割
响应速度慢 检索或生成耗时过长 监控各环节耗时 1. 减小 k 值 2. 使用更快的 Embedding 模型 3. 缓存常见查询

2. 调优建议

(1)文档切割优化

typescript 复制代码
// 针对不同类型的文档使用不同的切割策略

// 技术文档:较小的 chunk
const techSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 600,
  chunkOverlap: 100,
});

// 故事/文章:较大的 chunk
const storySplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1200,
  chunkOverlap: 200,
});

// 代码:使用语言感知切割
const codeSplitter = RecursiveCharacterTextSplitter.fromLanguage(
  "typescript",
  { chunkSize: 800, chunkOverlap: 150 }
);

(2)Embedding 模型选择

typescript 复制代码
// 根据预算和精度要求选择

// 预算充足,追求精度
const premiumEmbeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-large",
});

// 性价比之选(推荐)
const balancedEmbeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-small",
});

// 预算有限,自托管
const freeEmbeddings = new HuggingFaceTransformersEmbeddings({
  modelName: "Xenova/all-MiniLM-L6-v2",
});

(3)检索参数调优

typescript 复制代码
// 从小 k 值开始,逐步增加
const retriever = vectorStore.asRetriever({
  k: 3,  // 从 3 开始测试
});

// 监控检索结果的相关性
const docs = await retriever.invoke(query);
docs.forEach((doc, i) => {
  console.log(`文档 ${i + 1} 相关性:${doc.metadata.score}`);
});

// 如果相关性低,增大 k 或改进 Embedding

(4)Prompt 优化

typescript 复制代码
// 不好的 Prompt
const badPrompt = ChatPromptTemplate.fromMessages([
  ["system", "回答问题:{input}"],
]);

// 好的 Prompt
const goodPrompt = ChatPromptTemplate.fromMessages([
  [
    "system",
    `你是专业的客服助手。基于以下上下文回答问题:

规则:
1. 只使用提供的信息
2. 如果信息不足,说"我需要更多信息"
3. 引用具体来源
4. 语气友好专业

上下文:
{context}`,
  ],
  ["human", "{input}"],
]);

十、本章小结

RAG 是让 LLM 具备实时知识和私有数据能力的关键技术,在减少AI输出幻觉和增加准确度上都非常重要。

📝 核心知识点回顾

阶段 关键组件 最佳实践
1. 文档加载 PDFLoader, WebLoader 等 选择适合的加载器,保留元数据
2. 文档切割 RecursiveCharacterTextSplitter chunkSize 800-1200,overlap 15%
3. Embedding OpenAI text-embedding-3-small 平衡成本和效果
4. 向量存储 Pinecone, Chroma, PGVector 根据场景选择
5. 检索生成 Retriever + RAG Chain 添加引用来源,控制幻觉

🎯 动手练习

尝试完成以下练习,巩固所学知识:

练习 1:构建产品知识库

  • 收集公司产品文档(PDF、Word、网页)
  • 实现完整的 RAG Pipeline
  • 测试常见问题,评估回答质量
  • 优化 chunkSize 和 k 值

练习 2:实现查询改写

  • 创建查询改写 Prompt
  • 测试指代消解效果
  • 对比改写前后的检索质量

练习 3:添加重排序

  • 集成 Cohere Reranker
  • 对比有无重排序的效果差异
  • 分析成本和收益

练习 4:性能优化

  • 监控各环节耗时
  • 实现查询缓存
  • 优化向量数据库配置
  • 目标:P95 延迟 < 2 秒

📚 延伸阅读


下一章:《第九章 ------ 多 Agent 系统》

相关推荐
深海鱼在掘金1 小时前
深入浅出 LangChain —— 第九章:多 Agent 系统
人工智能·langchain·agent
Main. 241 小时前
LangChain - AI应用开发利器(三)
langchain
用户068866817511 小时前
Windows端Codex接入第三方模型(DeekSeek,BaiLian)
人工智能
陈天伟教授1 小时前
AI 未来趋势:产业应用范式之变
大数据·开发语言·人工智能·gpt
技术达芬奇1 小时前
OpenClaw 模型配置问题调试实战 - DeepSeek 404 错误解决
agent
Luhui Dev1 小时前
AHE 深度解析:Coding Agent 的 Harness 如何自动演化
人工智能·agent·luhuidev
码农的神经元2 小时前
从论文复现到模型升级:Transformer-Attention-WOA-XGBoost 在含新能源配电网故障诊断中的实现
人工智能·深度学习·transformer
EnCi Zheng2 小时前
04-缩放点积注意力代码实现 [特殊字符]
人工智能·pytorch·python
一江寒逸2 小时前
5个免费开源大模型API,完美平替OpenAI,个人开发完全够用了(2026最新保姆级指南)
人工智能·个人开发