【RAG 详解:让模型学会“查资料”】
【LangChain】本文主要是我在学习 LangChain 过程中的一些理解总结,偏入门和认知梳理。
- 一、问题:模型如何获取“它不知道的信息”?
- 二、RAG 是什么?
- 三、RAG 的完整流程
- 四、Embedding(向量化)
- 五、向量数据库
- 六、RAG 中的 Prompt
- 七、用 LangChain 实现 RAG
- 1. 加载文档
- 2. 切分文本(按 Token 数切分,保持语义完整性)
- 3. 定义嵌入模型
- 4. 配置 Redis 向量存储
- 5. 将文档存入向量库(离线阶段完成)
- 6. 创建检索器
- 7. 定义提示词模板
- 8. 定义文档格式化函数
- 9. 构建 RAG 链
- 10. 结果演示
- 八、RAG 的本质
- 九、小结
上一篇: LangChain 核心组件梳理:从模块到流程
一、问题:模型如何获取“它不知道的信息”?
在上一篇中,我们已经了解了:
LangChain 可以将 AI 任务拆解为多个步骤,将组件链接起来并组织成完整流程。
但还有一个关键问题没有解决:
模型如何获取“它不知道的信息”?
比如:
本地文档
企业知识库
实时数据
LLM的训练数据有截止日期,它无法回答关于你公司内部流程的问题,也无法告诉你今天发生了什么。
原生 LLM 就像一个知识渊博但“与世隔绝”的专家——它的知识停留在训练完成的那一刻。
举个例子:假设你问 ChatGPT:“我们公司今年的年假政策是什么?”它会礼貌地告诉你它不知道,或者更糟——编造一个听起来合理但完全错误的答案(这就是“幻觉”)。因为它从未见过你们公司的内部员工手册。
这就引出了本文的核心内容:
RAG(检索增强生成)
二、RAG 是什么?
RAG = 检索(Retrieval) + 生成(Generation)
核心流程:
用户提问 → 检索相关内容 → 作为上下文输入模型 → 生成答案举个例子:想象你在写一篇论文。
纯 LLM 模式:你不查任何资料,全凭记忆写。写到后面可能记错了某个年份,或者漏掉了重要观点。
RAG 模式:你先去图书馆,找到几本最相关的参考书,把关键段落复印下来,摊在桌上。然后你一边看这些资料,一边写论文。这样写出来的内容准确、有据可查。
本质变化:
以前:
模型“凭记忆回答”——容易产生幻觉,信息陈旧
现在:
模型“查资料再回答”——基于实时、可信的参考资料
这就像给模型配备了一个专属图书管理员:每次提问时,管理员先去资料库里找到最相关的几页内容,递到模型面前,模型再据此作答。
三、RAG 的完整流程
RAG 通常分为两个阶段,理解这个流程是掌握 RAG 的关键。
离线数据准备阶段
加载文档 → 切分文本 → 向量化 → 存入向量数据库
这个阶段的目标是:把私有知识库“翻译”成模型能检索的形式。
举个例子:假设你要把一本 300 页的《公司员工手册》变成 RAG 知识库。
加载文档:把 PDF 文件读进来。
切分文本:300 页太长,一次塞给模型它看不过来(上下文窗口有限)。所以要把它切成一段一段,比如每段约 200 个 Token,像把一本大书剪成一张张卡片。
向量化:把每张“卡片”上的文字转换成一串数字(向量),让计算机能“理解”它的含义。
存入向量数据库:把所有卡片和它们的数字指纹(向量)存进一个专门用来快速搜索的仓库。
在线检索阶段
用户提问 → 问题向量化 → 相似度检索 → 获取相关内容 → 组装 Prompt → 生成答案
这个阶段的目标是:根据用户问题,找到最相关的资料,让模型据此回答。
继续上面的例子:现在有员工问:“年假怎么申请?”
系统把“年假怎么申请?”也转换成一串数字(向量)。
在向量仓库里找哪些卡片上的向量和这个问题向量最“接近”。
找到最相似的那几张卡片——上面写的恰好就是年假政策的内容。
把这些卡片内容和问题一起打包,告诉模型:“嘿,参考这些资料来回答用户的问题。”
模型看完资料,给出准确答案:“您可以在 OA 系统中提交年假申请,每年享有 10 天年假……”
我们可以理解为:
“先建索引,再查询”——和搜索引擎的原理异曲同工。
下面这张图清晰地展示了完整流程:
四、Embedding(向量化)
1.什么是Embedding?
Embedding 的核心思想是:把人类语言的“语义”转换为计算机能计算的“数字”。
举个例子:
“我喜欢猫” → [0.12, 0.98, -0.34, …]
这个数字列表就是向量,它的维度通常是固定的(比如 OpenAI 的 text-embedding-3-large 模型生成 3072 维向量)
一个形象的理解方式:想象你要向一个外星人描述各种动物的特征。外星人不认识文字,只懂数字。于是你发明了一套编码系统:
“毛茸茸” → +0.8
“会喵喵叫” → +0.9
“喜欢抓老鼠” → +0.7
“汪汪叫” → -0.8
那么“猫”的描述可能就接近 [0.8, 0.9, 0.7, -0.8],而“狗”的描述则是 [0.7, -0.9, -0.5, 0.9]。
Embedding 模型做的事情更复杂、更精确,但原理是一样的——用数字来代表含义。
2.为什么需要?
->用于计算语义相似度
在向量空间中,含义相近的文本,其向量也彼此接近。我们可以用余弦相似度来度量两个向量的“方向一致性”——方向越一致,语义越相似。
实验一下:
“猫是一种可爱的宠物” → 向量 A
“狗是人类忠实的朋友” → 向量 B
“今天天气真好” → 向量 C
计算相似度你会发现:A 和 B 的相似度较高(都是关于宠物的),而它们和 C 的相似度都很低。这就是向量检索的基础。
理解
把“语义相似”变成“向量接近”
这使得计算机可以用数学方法完成“找意思相近的内容”这种原本只有人类才能做的事。
五、向量数据库
1.作用
存储向量,并支持高效的相似度搜索。
一篇文档转换成一个 1536 维的向量,一百万篇文档就是约 1.5GB 的纯向量数据。如何在海量向量中快速找到与查询最相似的几条?这就是向量数据库的专长。
传统数据库 vs 向量数据库
用一个找电影的例子来理解:
传统数据库(如 MySQL):
你输入:SELECT * FROM movies WHERE title LIKE '%爱情%'
它返回:所有标题里包含“爱情”两个字的电影。
问题来了:《泰坦尼克号》标题里没有“爱情”,但它确实是一部爱情片,传统数据库找不到它。
向量数据库:
你把《泰坦尼克号》的剧情简介转换成向量存进去。
你输入:“推荐一些感人的爱情电影” → 转换成向量
向量数据库计算发现,《泰坦尼克号》的剧情向量和你的查询向量方向很接近,于是把它推荐给你——哪怕标题里压根没有“爱情”二字
将两者比较:
| 对比维度 | 传统数据库 | 向量数据库 |
|---|---|---|
| 匹配方式 | 关键词精确匹配 | 语义相似匹配 |
| 查询示例 | WHERE name = '苹果' | “一种红色的水果”也能找到“苹果”相关文档 |
| 语义理解能力 | 不懂“含义”,仅做字面匹配 | 理解“语义”,可基于上下文和意图检索 |
| 典型工具 / 集成 | MySQL、PostgreSQL 等关系型数据库 | Chroma、Redis(RediSearch)、Pinecone、Milvus 等(LangChain 均支持集成) |
我们可以理解为:
向量数据库是一个“按语义搜索”的数据库
六、RAG 中的 Prompt
1.问题:
模型本身并不知道:
哪部分是用户的问题
哪部分是我们提供的参考资料
应该基于什么来回答
所以我们需要用 Prompt 明确告诉模型
错误的 Prompt 写法(没有区分角色):
数据库表怎么设计的?
(后面跟着一大堆检索出来的文档内容)
模型会困惑:“我是应该直接回答数据库设计的问题,还是总结这些资料?”结果可能是它忽略了资料,凭自己的知识回答,完全丧失了 RAG 的意义。
正确的 Prompt 写法:
根据以下检索到的上下文片段来回答问题。
如果你不知道答案,就说你不知道。
最多只用三句话,回答要简明扼要。
问题:{question}
上下文:{context}
答案:{anwer}
这样模型就清楚了用户的需求:
角色(Role):我是一个基于资料回答的助手
输入(Intput):问题是 X,参考资料是 Y
输出要求( Limit):简明扼要,不超过三句,不知道就说不知道
- 本质:
让模型“基于资料回答”,而不是自己发挥。
这种 Prompt 设计是 RAG 效果的关键——既要让模型充分参考上下文,又要约束它不胡编乱造。
七、用 LangChain 实现 RAG
我们用 LangChain 的组件来完整实现一个 RAG 流程。
步骤:
加载文档 → 切分文本 → 向量化存储 → 创建检索器 → 构建 RAG 链 → 生成答案我们可以看段实现 RAG的完整代码:
fromlangchain_openaiimportOpenAIEmbeddings,ChatOpenAIfromlangchain_redisimportRedisConfig,RedisVectorStorefromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.runnablesimportRunnablePassthroughfromlangchain_community.document_loadersimportUnstructuredMarkdownLoaderfromlangchain_text_splittersimportCharacterTextSplitter1. 加载文档
#UnstructuredMarkdownLoader 专门用于MD文档#PDF文档可用 PyPDFLoaderloader=UnstructuredMarkdownLoader("./knowledge.md")data=loader.load()2. 切分文本(按 Token 数切分,保持语义完整性)
# ⽣成分割器text_splitter=CharacterTextSplitter.from_tiktoken_encoder(encoding_name="cl100k_base",#cl100k_base 是一种默认的编码格式chunk_size=200,#块大小chunk_overlap=50#块与块之间的重叠长度)documents=text_splitter.split_documents(data)3. 定义嵌入模型
我们可以直接在LangChain官网搜索我们需要的嵌入模型以及如何安装和使用(需要魔法)
#这里使用OpenAi的text-embedding-3-largeembeddings=OpenAIEmbeddings(model="text-embedding-3-large")4. 配置 Redis 向量存储
# Redis向量存储相关配置config=RedisConfig(index_name="my_knowledge_base",redis_url="redis://localhost:6666",)vector_store=RedisVectorStore(embeddings,config=config)5. 将文档存入向量库(离线阶段完成)
vector_store.add_documents(documents)6. 创建检索器
#通过调⽤向量数据库的as_retriever ⽅法,将向量存储⽤作检索器retriever=vector_store.as_retriever(search_kwargs={"k":4})7. 定义提示词模板
# 提示词模板prompt=ChatPromptTemplate.from_messages([("human","""根据以下上下文回答问题。如果你不知道答案,就说不知道。 问题:{question} 上下文:{context} 答案:""")])8. 定义文档格式化函数
#将检索到的文档转化成文本传递给提示词模板defformat_docs(docs):return"\n\n".join(doc.page_contentfordocindocs)9. 构建 RAG 链
#自己尝试可以使用国产的千问,配置好apikey#model =ChatTongyi(model="qwen-turbo")model=ChatOpenAI(model="gpt-4o-mini")chain=(# 检索器+format_docs 分支1# question 分支2:RunnablePassthrough()在链中透传数据{"context":retriever|format_docs,"question":RunnablePassthrough()}|prompt|model|StrOutputParser())10. 结果演示
forchunkinchain.stream("数据库表怎么设计的?"):print(chunk,end="",flush=True)运行效果示例:
假设你的 knowledge.md 文档中有这样一段内容:
数据库表设计遵循第三范式,主要分为用户表、订单表、商品表。用户表包含用户ID、姓名、注册时间等字段。订单表通过用户ID与用户表关联,商品表通过商品ID与订单表关联。 当你运行代码并提问“数据库表怎么设计的?”时,系统会: 检索出上述这段最相关的内容 将其作为上下文喂给 LLM 模型据此生成答案,比如: 数据库设计采用第三范式,主要包含用户表(存储用户基本信息)、 订单表(记录订单数据,通过用户ID关联用户表)和商品表通过商品ID关联订单表)。八、RAG 的本质
RAG = 模型能力 + 外部知识
我们也可以理解为:
RAG 并没有提升模型“智商”,它只是:让模型“看资料再回答”
举个例子,期末考试分开闭卷:
纯 LLM:一个闭卷考试的学生,只能靠记忆答题。
RAG:一个开卷考试的学生,可以翻书找答案。
显然,对于需要精确、实时、私有知识的问题,开卷考试的成绩会好得多
九、小结
从上文我们可以了解到RAG的总流程:
问题 → 检索 → 上下文 → 生成这是一个“最小可运行 RAG Demo”,实际生产中还需要考虑缓存、召回优化、重排序等问题。
还有RAG 的优势:
减少幻觉:答案有据可查,不再凭空编造
支持私有数据:企业文档、个人笔记都可以成为知识源
提高准确性:基于实时、相关的信息作答
LangChain 为 RAG 提供了完整的组件支持:
组件 作用
Document Loaders ——>加载多种格式的文档
Text Splitters——>将长文档切分为语义完整的块
Embeddings——>将文本转换为向量
Vector Stores——>存储向量并支持相似度检索
Retrievers——>统一的检索接口
今天的分享就到这里,下章再会~
