🤖 Agent 智能体开发实战 · RAG 实战 —— 从零搭建检索增强生成系统

上篇回顾:RAG 入门篇讲了幻觉问题、Embedding 原理、向量语义搜索、Document 切分等理论基础。

本课聚焦 :用一个完整的 index.mjs,把 RAG 理论落地为可运行的代码------从文档向量化存入 MemoryVectorStore,到创建检索器 retriever,再到拼 Prompt 让 LLM 回答,一步步拆解全套 RAG 工作流


📖 本课目录

  • 一、依赖全景:三类核心组件
  • [二、模型初始化:生成模型 + 嵌入模型](#二、模型初始化:生成模型 + 嵌入模型 "#%E4%BA%8C%E6%A8%A1%E5%9E%8B%E5%88%9D%E5%A7%8B%E5%8C%96%E7%94%9F%E6%88%90%E6%A8%A1%E5%9E%8B--%E5%B5%8C%E5%85%A5%E6%A8%A1%E5%9E%8B")
  • [三、构建知识库:Document → Embedding → VectorStore](#三、构建知识库:Document → Embedding → VectorStore "#%E4%B8%89%E6%9E%84%E5%BB%BA%E7%9F%A5%E8%AF%86%E5%BA%93document--embedding--vectorstore")
  • 四、检索器:语义搜索的入口
  • [五、Augmented + Generation:拼接 Prompt + LLM 回答](#五、Augmented + Generation:拼接 Prompt + LLM 回答 "#%E4%BA%94augmented--generation%E6%8B%BC%E6%8E%A5-prompt--llm-%E5%9B%9E%E7%AD%94")
  • 六、完整流程复盘
  • 七、本课学习总结

一、依赖全景:三类核心组件

javascript 复制代码
import 'dotenv/config';
import {
  ChatOpenAI,         // llm generate
  OpenAIEmbeddings,   // 嵌入模型
} from '@langchain/openai';
// 内存向量存储 rag 学习,或轻量
// psql
// 帮我安装postgreSQL root 密码设置为123456 开启向量存储扩展
import { MemoryVectorStore } from '@langchain/classic/vectorstores/memory';
import { Document } from '@langchain/core/documents';
依赖 来源 职责
ChatOpenAI @langchain/openai 生成模型------根据增强后的 Prompt 生成最终回答
OpenAIEmbeddings @langchain/openai 嵌入模型------把文本转成向量
MemoryVectorStore @langchain/classic/vectorstores/memory 内存向量存储------学习/轻量场景,向量 + 原文一起存内存里
Document @langchain/core/documents 知识库的最小单元(pageContent + metadata

💡 @langchain/classic 是 LangChain 经典常用模块。MemoryVectorStore 把向量数据存内存里,适合 RAG 学习和轻量场景。生产环境会换 PostgreSQL + pgvector 或 Pinecone 等持久化向量数据库。


二、模型初始化:生成模型 + 嵌入模型

javascript 复制代码
// 生成模型 ------ 负责回答问题(贵)
const model = new ChatOpenAI({
  temperature: 0,
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_BASE_URL
})

// 嵌入模型 ------ 负责把文本转成向量(便宜)
const embeddings = new OpenAIEmbeddings({
  apiKey: process.env.OPENAI_API_KEY,
  model: process.env.EMBEDDINGS_MODEL_NAME,
  baseURL: process.env.OPENAI_BASE_URL
})
模型 用途 何时调用
生成模型 ChatOpenAI 生成文本回答 最后一步(Generation)
嵌入模型 OpenAIEmbeddings 文本 → 向量 建库时 + 查询时

🎯 两个模型各司其职。嵌入模型轻量便宜,专做"文本转数字"这件事;生成模型重且贵,只在最终回答时调用一次。


三、构建知识库:Document → Embedding → VectorStore

第1步:准备 Document 数组

javascript 复制代码
const documents = [
  new Document({
    pageContent: `光光是一个活泼开朗的小男孩,他有一双明亮的大眼睛,总是带着灿烂的笑容。光光最喜欢的事情就是和朋友们一起玩耍,他特别擅长踢足球,每次在球场上奔跑时,就像一道阳光一样充满活力。`,
    // metadata 用于后续过滤或溯源,不参与向量化计算,但非常有用
    metadata: {
      chapter: 1,
      character: "光光",
      type: "角色介绍",
      mood: "活泼"
    },
  }),
  new Document({
    pageContent: `东东是光光最好的朋友,他是一个安静而聪明的男孩......和光光从幼儿园就认识了......`,
    metadata: {
      chapter: 2,
      character: "东东",
      type: "角色介绍",
      mood: "温馨"
    },
  }),
  // ... 共 7 个 Document,涵盖完整故事线
];

7 个 Document 构成一个儿童故事知识库(光光和东东的友谊故事),每个都标注了 chaptercharactertypemood 元数据。

第2步:一键向量化 + 存入内存

javascript 复制代码
// 把一堆的documents 用 embeddings 模型向量化
// 存入到内存中
// 抽象 业务太好了
// 将上述documents 数组中的所有文本
// 通过 embeddings 转换成向量,并存入内存
// 可以拥有一个语义搜索的知识库
const vectorStore = await MemoryVectorStore.fromDocuments(documents, embeddings);

这一行干了三件事:

css 复制代码
documents(7 个 Document 对象)
    │
    ▼
① 遍历每个 Document,取 pageContent
    │
    ▼
② 用 embeddings 把每个 pageContent 转成向量 [0.12, -0.34, 0.78, ...]
    │
    ▼
③ 把 { 向量, pageContent, metadata } 存入 MemoryVectorStore
参数 说明
documents 7 个 Document 的数组
embeddings 嵌入模型实例,负责把文本转成向量
返回值 MemoryVectorStore 实例,已包含全部文档的向量索引

🎯 MemoryVectorStore.fromDocuments() 是一个高度抽象的方法------一行代码完成"遍历→向量化→建索引→存入"。AI 发展太快,LangChain 版本更新也快,关键是理解抽象背后的原理


四、检索器:语义搜索的入口

🔍 从 VectorStore 到 Retriever

javascript 复制代码
// prompt 去语义匹配
// 提供检索器 不用去手工的prompt embedding
// 将向量数据库转换成检索器
// 是一个标准入口,输入问题,检索器输出最相关的文档列表
const retriever = vectorStore.asRetriever({
  k: 3 // 最相似的3个文档
});
概念 说明
asRetriever() 把 VectorStore 包装成检索器------RAG 系统的标准入口
k: 3 Top-K 参数:只返回最相似的 3 个文档

💡 有了 Retriever 就不用手工做 Prompt Embedding 了------它是一个标准接口:输入问题,输出最相关的文档列表。背后自动做"问题向量化 → 相似度计算 → 排序 → 返回 top-K"。

📊 Retriever vs 原始向量查询

javascript 复制代码
// 方式1:retriever.invoke() ------ 标准检索入口
// invoke 执行
// 内部逻辑,将question 转为向量
// 在向量数据库中计算距离 返回K个最相关的文档 Document 对象
// 工作流编排
const docs = await retriever.invoke(question);

// 方式2:similaritySearchWithScore() ------ 原始向量查询 + 打分
// 还想要打分,返回每个文档的相似度
// 向量的距离 越小,相似度越高
// 1-距离 = 相似度
const scoredResults = await vectorStore.similaritySearchWithScore(question, 3);
对比维度 retriever.invoke() vectorStore.similaritySearchWithScore()
返回内容 Document 列表 [Document, score] 元组列表
处理能力 检索 + 去重 + 过滤 + rerank 纯向量相似度查询
score 含义 无(由 retriever 内部处理) 向量距离(越小越相似)
适用场景 Agent 工作流编排 ✅ 需要看打分/调试/评估质量

📐 相似度打分解读

javascript 复制代码
console.log("/n [检索到的文档及相似度评分]");
docs.forEach((doc, i) => {
  const scoredResult = scoredResults.find(([sorceDoc]) =>
    sorceDoc.pageContent === doc.pageContent
  )
  // retriever 过滤,rerank
  // 1-值越大越相似,cosine
  const score = scoredResult ? scoredResult[1] : null;
  const similarity = score != null ? (1 - score).toFixed(4) : "N/A";
  console.log(`\n[文档 ${i + 1}] 相似度指标: ${similarity} (原始分: ${score})`);
  console.log(`内容: ${doc.pageContent.substring(0, 50)}...`);
  console.log(`元数据:章节=${doc.metadata.chapter}, 角色=${doc.metadata.character}, 类型=${doc.metadata.type}`);
})
数值 含义
score(原始分) 向量间的余弦距离,越小越相似
similarity(相似度) 1 - score,越大越相似,范围 0~1
metadata 溯源信息------告诉用户"这个信息来自第几章、关于哪个角色"

🎯 retriever 在相似度查询的基础上额外做了去重、过滤、rerank ,返回的结果更干净。如果调试阶段想看"为什么返回这个文档",用 similaritySearchWithScore 看原始打分。


五、Augmented + Generation:拼接 Prompt + LLM 回答

📝 Augmented:拼接增强 Prompt

javascript 复制代码
// Augmented
const context = docs
  .map((doc, i) => `[片段${i}]\n ${doc.pageContent}`)
  .join("\n\n-----\n\n");

const prompt = `你是一个讲友情故事的老师。基于以下故事片段回答问题,用温暖生动的语言。如果故事中没有提到,就说"这个故事里还没有提到这个细节"。

故事片段:
${context}

问题:${question}

老师的回答:`;
组成部分 说明
角色设定 "你是一个讲友情故事的老师"------给 LLM 定人设
故事片段 检索到的 top-3 文档,用 ----- 分隔
问题 用户原始问题
防幻觉指令 "如果故事中没有提到就说没提到"------很关键

💡 防幻觉指令是 Prompt Engineering 的重要技巧。不写这句的话,LLM 被问到知识库没有的内容时,可能又开始编造。

🤖 Generation:LLM 生成回答

javascript 复制代码
const response = await model.invoke(prompt);
console.log(response.content);

RAG 的整个流程到这里就走完了。LLM 拿到的不是一个孤立的问题,而是一个包含了相关背景知识的增强 Prompt------它不需要"记住"这些知识,只需要读懂上下文然后组织语言回答。


六、完整流程复盘

🔄 从代码看 RAG 流水线

scss 复制代码
═══════════════════ 准备阶段 ═══════════════════

  documents(7个Document)
    │
    ▼
  MemoryVectorStore.fromDocuments(documents, embeddings)
    │  ① 遍历 documents
    │  ② 每个 pageContent 用 embeddings 转向量
    │  ③ 存入内存向量存储
    ▼
  vectorStore.asRetriever({ k: 3 })
    │  包装成标准检索器接口
    ▼
  retriever(就绪,等待查询)

═══════════════════ 查询阶段 ═══════════════════

  question = "光光和东东是怎么成为朋友的?"
    │
    ▼
  retriever.invoke(question)
    │  ① 把 question 用 embeddings 转向量
    │  ② 在 VectorStore 中计算余弦距离
    │  ③ 排序 + 去重 + rerank
    │  ④ 返回 Top-3 Document
    ▼
  docs(3个最相关的故事片段)

    ├──► similaritySearchWithScore(question, 3)
    │     获取原始相似度分数(调试/评估用)

    ▼
  Augmented: 拼接 context + question → Prompt
    ▼
  model.invoke(prompt) → LLM 基于事实回答

📦 整个流程只用了一个文件

scss 复制代码
rag-demo/
└── index.mjs  ←  200行不到,包含完整的 RAG 流程
    ├── import 依赖
    ├── 初始化模型(ChatOpenAI + OpenAIEmbeddings)
    ├── 构建 Document[](7 章故事)
    ├── MemoryVectorStore.fromDocuments()
    ├── asRetriever() + invoke() 检索
    ├── similaritySearchWithScore() 打分
    └── Augmented Prompt + model.invoke() 生成

七、本课学习总结

🧠 知识点

scss 复制代码
📋 RAG 实战 · 知识点
│
├── 🧩 三类核心依赖
│   ├── ChatOpenAI:生成模型(回答问题)
│   ├── OpenAIEmbeddings:嵌入模型(文本→向量)
│   └── MemoryVectorStore:内存向量存储
│
├── 📚 构建知识库(一行代码)
│   ├── MemoryVectorStore.fromDocuments(docs, embeddings)
│   ├── 遍历 → Embedding → 建索引 → 存入
│   └── 抽象层次高,理解原理更重要
│
├── 🔍 检索器 Retriever
│   ├── vectorStore.asRetriever({ k: 3 })
│   ├── 标准入口:输入问题 → 输出相关文档
│   ├── invoke():内部自动问题向量化 + 相似度计算
│   └── retriever 会做去重、过滤、rerank
│
├── 📊 相似度打分
│   ├── vectorStore.similaritySearchWithScore(question, k)
│   ├── score = 余弦距离(越小越相似)
│   ├── similarity = 1 - score(越大越相似)
│   └── 调试用,retriever 内部也会用到
│
├── 📝 Augmented 增强
│   ├── 检索到的文档 → 拼接成 context
│   ├── 角色设定 + context + 问题 → Prompt
│   └── 防幻觉指令:知识库没有就说没提到
│
└── 🤖 Generation 生成
    ├── model.invoke(prompt)
    ├── LLM 基于增强后的 Prompt 回答
    └── 不需要记住知识,读懂上下文即可

✅ 知识清单

编号 掌握项 核心要点
1 双模型架构 生成模型(回答)+ 嵌入模型(转向量),各司其职
2 MemoryVectorStore 内存向量存储,fromDocuments() 一行建库
3 fromDocuments() 遍历文档 → 向量化 → 建索引 → 存入,三步合一
4 asRetriever({ k }) 把 VectorStore 包装成标准检索器,k 控制返回数量
5 retriever.invoke() 输入问题,自动转向量 → 查相似度 → 返回 top-K 文档
6 similaritySearchWithScore() 原始向量查询 + 相似度打分,用于调试评估
7 score 含义 余弦距离(越小越相似),1 - score = 相似度
8 retriever vs 原始查询 retriever 多做去重+过滤+rerank,原始查询只做向量匹配
9 Augmented Prompt 拼接 context(检索结果)+ question → 增强 Prompt
10 防幻觉指令 "知识库没有的内容就说没提到"------关键的 Prompt Engineering

📊 RAG 入门 vs RAG 实战

维度 入门篇 本课实战
内容 概念 + Document 定义 完整可运行的 RAG 流水线
向量存储 理论介绍 MemoryVectorStore.fromDocuments()
检索 概念 retriever.invoke() + similaritySearchWithScore()
Augmented 概念 代码拼接 context → Prompt
Generation 概念 model.invoke(prompt)
产出 理解 RAG 原理 能直接运行的 index.mjs

🎯 本课定位:入门篇讲"RAG 是什么",本课讲"RAG 怎么做"。200 行不到的代码,一条完整的流水线:Document → Embedding → VectorStore → Retriever → Augmented Prompt → LLM 生成。理解了这六步,就掌握了 RAG 的核心实战能力。


📅 2026-07-25 | 🏷️ Agent · RAG · MemoryVectorStore · Retriever · Embedding · 实战

相关推荐
ambition2371 小时前
WorkBuddy迁移至D盘完整操作指南
人工智能
科技圈快迅2 小时前
在机场与高铁站:智能服务机器人正在深度对接业务系统,实现从信息孤岛到移动服务窗口的转变
大数据·人工智能·机器人
小企鹅么么2 小时前
【AI应用开发工程师】第七章:Agent 工程与实践
人工智能
CIO_Alliance2 小时前
AI+iPaaS解决方案:跨系统业务流程自动化助力企业AI化转型
运维·人工智能·自动化
综合资讯2 小时前
2026年打开设备ETF的产品清单:八只同类,两只能买
人工智能·科技·芯片·半导体·科创
声讯电子2 小时前
A-59 双麦双通道语音处理模块:重新定义全双工通话的极限性能
人工智能·语音识别·ai降噪·usb接口·回音消除
菜鸟‍2 小时前
【论文学习】Frontiers in Physics 2024 || Cap2Seg:基于文本描述生成的 COVID-19 医学图像分割性能提升方法
人工智能·学习
陕西企来客2 小时前
生成式引擎优化(GEO)行业白皮书:从流量分配到认知占有的战略跃迁
人工智能·算法·机器学习·技术好geo优化
梦想的颜色2 小时前
飞书 CLI + Skill 完全硬核指南|让 AI Agent 全自动操控飞书(消息 / 文档 / 日历 / 多维表)
人工智能·飞书·办公自动化·飞书 skill·飞书全自动工作流·agent 工具集成