😁 LLM 不知道的事它不会说"不知道",而是会"编"——RAG 就是让它闭嘴先查资料

写在前面:今天学 RAG(检索增强生成),老师说了一句话让我醍醐灌顶------"大模型不会说'我不知道',它会胡乱回答。" 你问他昨天世界杯谁赢了,它不知道(知识截止到训练时间),但它不会说"我不知道啊",它会编一个答案给你。而且编得还像模像样------这就是传说中的"AI 幻觉"。RAG 就是解决这个问题的:别让你的 AI 瞎编,先让它去查资料,查到了再回答。


一、AI 的"不懂装懂"------幻觉从哪来?

1.1 AI 的知识不是"活的"

老师说:

"大模型知道的知识,取决于训练时给它的数据集。如果你问它最近发生的事情,或者你企业内部私有的文档,LLM 是不知道的。"

但问题在于------LLM 不会说"我不知道"。

它会基于自己有限的训练知识,强行编一个听起来合理的答案 。这就是所谓的幻觉(Hallucination)

你问它:"2024 年巴黎奥运会中国队拿了多少金牌?" 如果它的训练数据截止到 2023 年,它不知道。但它不会说"我不知道",它会说:"中国队获得了 38 枚金牌......"------这就是编的。

1.2 解决幻觉的三种方案

老师说:

"如何解决大模型的幻觉?想一下。"

方案 成本 说明
微调(Fine-tune) 大公司、专业领域用,需要算力和数据
RAG(检索增强生成) 先查知识库,再回答
重新训练 非常高 几乎没人为了新知识重训大模型

RAG 是性价比最高的方案。


二、RAG 不是魔法,是"先查后答"

2.1 RAG 是什么?

老师说:

"RAG:Retrieval(检索)--- Augmented(增强)--- Generation(生成)。"

复制代码
用户提问
  ↓
【Retrieval 检索】去知识库里找相关的文档片段
  ↓
【Augmented 增强】把找出来的内容加到 Prompt 里
  ↓
【Generation 生成】LLM 根据增强后的上下文回答问题

核心思想:在给 LLM 发 Prompt 之前,先去查一下知识库,把相关文档作为背景知识塞进去,再让 LLM 回答。

2.2 为什么不用关键词搜索?

老师说:

"关键词搜索------文本匹配,不太行。向量(Vector)语义查询就可以。"

举个例子:

关键词 用 LIKE 搜索 用向量搜索
"光光" 只匹配含"光光"的段落 即使搜"朋友",也能找到光光相关的段
"足球比赛" 只匹配含"足球比赛"的段落 搜"运动"也能找到足球相关内容

向量搜索找的是"意思",关键词搜索找的是"字"。

2.3 向量的"数学魔法"

老师画了一个很直观的例子:

css 复制代码
水果向量:[0.9, 0.3]
苹果向量:[0.9, 0.5]  ← 和水果很近
香蕉向量:[0.9, 0.1]  ← 和水果也比较近
石头向量:[0.1, 0.9]  ← 和水果很远

每个词都可以表示为一个多维向量。 语义相近的词,在向量空间中的夹角就小(余弦相似度高)。语义无关的词,夹角就大。

所以 RAG 一般用**嵌入模型(Embedding Model)**来做语义搜索,比关键词搜索精准得多。


三、LangChain 实现 RAG:一个完整的故事

3.1 准备知识库文档

index.mjs,老师用了一个"光光和东东"的小故事:

javascript 复制代码
const documents = [
    new Document({
        pageContent: `光光是一个活泼开朗的小男孩,他有一双明亮的大眼睛,总是带着灿烂的笑容。
        光光最喜欢的事情就是和朋友们一起玩耍,他特别擅长踢足球......`,
        metadata: { 
            chapter: 1, 
            character: "光光", 
            type: "角色介绍", 
            mood: "活泼"
        },
    }),
    new Document({
        pageContent: `东东是光光最好的朋友,他是一个安静而聪明的男孩。
        东东喜欢读书和画画,他的画总是充满了想象力......`,
        metadata: { 
            chapter: 2, 
            character: "东东", 
            type: "角色介绍", 
            mood: "温馨"
        },
    }),
    // ......一共 7 个片段
];

Document 是 LangChain 中 Embedding 的最小单元:

字段 作用 比喻
pageContent 要向量化的文本内容 一段话
metadata 附加信息,不参与向量化 标签(章节、角色、类型)

3.2 把文档向量化,存入内存向量库

javascript 复制代码
const embeddings = new OpenAIEmbeddings({
    model: process.env.EMDEDDING_MODEL_NAME,
});

const vectorStore = await MemoryVectorStore
    .fromDocuments(documents, embeddings);

console.log(`向量库创建成功,共 ${vectorStore.memoryVectors.length} 条文档已向量化`);

MemoryVectorStore 是 LangChain 提供的内存向量存储。 把文档通过 Embedding 模型转成向量,存到内存里。

老师说:

"简单就放内存,复杂就放向量数据库(如 PostgreSQL + pgvector)。"

3.3 创建检索器

javascript 复制代码
const retriever = vectorStore.asRetriever({
    k: 3 // 返回最相似的 3 条
});

检索器(Retriever)是一个标准入口:

复制代码
知识库 → 文档 → Document → Embedding → MemoryVectorStore → Retriever

3.4 查询并获取相关文档

javascript 复制代码
const question = "东东和光光是怎么成为朋友的?";
const docs = await retriever.invoke(question);

const scoredResults = 
    await vectorStore.similaritySearchWithScore(question, 3);

输出的结果包含相似度评分:

ini 复制代码
[文档 1] 相似度: 0.9234
内容: 东东是光光最好的朋友,他是一个安静而聪明的男孩...
元数据:章节=2, 角色=东东, 类型=角色介绍

[文档 2] 相似度: 0.8876
内容: 有一天,学校要举办一场足球比赛...
元数据:章节=3, 角色=光光和东东, 类型=友情情节

[文档 3] 相似度: 0.7543
内容: 接下来的日子里,光光每天放学后都会教东东踢足球...
元数据:章节=4, 角色=光光和东东, 类型=友情情节

相似度越接近 1,说明和问题越相关。

3.5 增强 Prompt,生成回答

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

const prompt = `你是一个讲友情故事的老师。
基于以下故事片段回答问题,用温暖生动的语言。

故事片段:
${context}

问题:${question}

老师的回答:`;

const response = await model.invoke(prompt);

检索到的文档片段被塞进了 Prompt 的"故事片段"部分,LLM 基于这个增强后的上下文来回答问题。


四、RAG 的完整流程回顾

arduino 复制代码
用户提问:"东东和光光是怎么成为朋友的?"

  ↓ 1. 检索(Retrieval)
  把问题转成向量 → 在向量库中搜索 → 找到最相似的 3 个片段

  ↓ 2. 增强(Augmented)
  把 3 个片段组装成 Prompt 的"故事片段"部分

  ↓ 3. 生成(Generation)
  LLM 基于故事片段 + 问题,生成回答

  ↓ 输出
  "东东和光光是从幼儿园就认识的好朋友......
  光光邀请东东一起参加足球比赛,虽然东东不会踢,
  但光光耐心地教他,他们的友谊因此更加深厚了......"

如果没有 RAG,LLM 只能基于自己的训练知识回答。但如果这个故事不在训练数据里,LLM 就会"编"。

有了 RAG,LLM 先查知识库,基于真实的内容回答------幻觉就大大减少了。


五、RAG 在实际项目中的架构

老师说:

"RAG 要实现语义查询,需要基于向量来做,把文档向量化存储到向量数据库,查询的时候也把 Prompt 向量化,去数据库中做相似度检索,可以找到语义相近的文本块。"

生产环境的 RAG 架构:

复制代码
原始文档(PDF、Word、网页等)
  ↓ 文本提取 + 分片
文本片段
  ↓ Embedding 模型
向量
  ↓ 存储
向量数据库(PostgreSQL+pgvector、Pinecone、Weaviate)
  ↓ 创建
检索器(Retriever)
  ↓ 查询
用户问题 → 向量化 → 相似度检索 → 相关片段 → 增强 Prompt → LLM 生成
组件 作用 我们用的什么
文档分片 把长文档切成有语义的段落 手动切分 7 个片段
Embedding 模型 把文本转成向量 OpenAIEmbeddings
向量存储 存向量,支持相似度搜索 MemoryVectorStore
检索器 标准入口,输入问题输出文档 vectorStore.asRetriever()

六、总结:RAG 是 LLM 的"外挂知识库"

概念 说明
LLM 幻觉 LLM 不懂也不会说不知道,会强行编答案
RAG Retrieval Augmented Generation,检索增强生成
检索 从知识库中找到和问题相关的文档片段
增强 把片段加入 Prompt 上下文
生成 LLM 基于增强后的上下文回答
向量搜索 用 Embedding 找语义相似的文本
Document LangChain 中 Embedding 的最小单元
MemoryVectorStore 内存向量存储,轻量级方案
检索器 retriever.invoke(question) 标准入口

RAG 的核心思想:不要让你的 AI 空口白牙地编答案,先给它查资料,查到了再回答。


写在最后

今天最大的收获,是彻底理解了 RAG 的原理。以前觉得"AI 会编答案"是个玄学问题,现在知道了------LLM 不是故意骗你,它是真的不知道,但它"不好意思"说不知道。RAG 就是给 LLM 配了一个"秘书",遇到不懂的先去查资料,查到了再开口。

下次面试官问你:"RAG 的工作原理是什么?怎么用 LangChain 实现?"

你可以淡定地说:

"RAG 是 Retrieval Augmented Generation 的缩写。核心思想是:在给 LLM 发 Prompt 之前,先到知识库中检索相关文档。流程分三步:① 检索 ------把用户问题通过 Embedding 模型转成向量,在向量库中做相似度搜索,找到最相关的 N 个文档片段;② 增强 ------把检索到的片段作为上下文加入 Prompt;③ 生成 ------LLM 基于增强后的 Prompt 回答问题。在 LangChain 中,通过 Document 对象定义知识片段,MemoryVectorStore.fromDocuments() 创建向量库,vectorStore.asRetriever(k) 创建检索器,调用 retriever.invoke(question) 检索相关文档。这种方式有效解决了 LLM 的幻觉问题,让 AI 基于真实知识回答,而不是凭空编造。"

然后看着面试官满意的表情,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。

相关推荐
stringwu1 小时前
用 LangGraph 落地飞书 Bug 自动修复 Agent 实践
前端
Revolution611 小时前
变量提升到底提升了什么:为什么 var 是 undefined,let 却直接报错
前端·javascript
labixiong1 小时前
ES2026 落地了什么?两个真上线的特性 + 两个被毙的,一次讲清楚
javascript·ecmascript 6·ecmascript 8
Revolution611 小时前
本地明明正常,为什么 CI 又挂了:一次前端构建失败的排查过程
前端·前端工程化
太平洋月光1 小时前
stagewise如何结合cursor开发
前端·ai编程
前端Hardy1 小时前
JavaScript 终于又进化了!ES2026 正式发布,这些新特性你必须知道!
前端
光影少年1 小时前
react navite 页面跳转、传参、路由监听、导航栏自定义
前端·react native·react.js
IT知识分享1 小时前
WebP转JPG开发经验:从格式解码到本地批量转换的实践
javascript·python·图片转换
肖志-AI全栈1 小时前
JavaScript 快速排序:从 pivot、双指针到分治思想
开发语言·javascript·排序算法