什么是RAG:给AI找个好老师
什么是RAG:给AI找个好老师
你有没有遇到过这种情况:兴冲冲地打开一个AI聊天机器人,想问问它今天有什么新闻,结果它一本正经地告诉你:“根据我的了解,昨天在某地举办了一场盛大的科技发布会……”可你明明知道,那场发布会是去年办的,或者压根没发生过。AI说得头头是道,可说的全是错的——这种情况有个不太好听的名字,叫“AI幻觉”(Hallucination)。
再比如,你问AI:“2024年巴黎奥运会金牌榜第一名是谁?”它可能含糊其辞,或者给你一个明显过时的答案。这不是AI在故意骗你,而是它真的“不知道”。这就引出了我们今天要聊的核心问题:为什么看起来无所不知的大模型,有时候会“睁眼说瞎话”?我们又有什么办法让它变得可靠?
答案藏在一个听起来很学术的词里——RAG。别被这个名字吓跑,它的中文翻译其实很直白:检索增强生成。拆开来看,就是“先检索,再生成”。想象你考试的时候,可以翻书查资料,而不是死记硬背——RAG就是给AI装了一个“开卷考试”的能力。
接下来,我们会把这个问题拆成几个部分来理解:从大模型的知识局限开始,到RAG的工作原理,再到它和另一种常见方法“微调”的区别,最后用一个简单的代码示例,让你亲自动手体验一下RAG是怎么工作的。
---
大模型为什么会“胡说八道”
要理解RAG为什么有用,首先得搞清楚大模型为什么会犯错。这里的关键在于大模型的“知识”是哪里来的。
当你和ChatGPT、Claude或者国产的大语言模型聊天时,它们的回答都来自一个叫“参数知识”的东西。啥意思呢?模型在训练阶段,会阅读海量的文本——网页、书籍、新闻、论文、代码……工程师们把这些数据喂给模型,模型通过复杂的数学运算,在它的“脑子”里建立起几十亿甚至上百亿个参数(可以理解为神经连接)。这些参数编码了它从训练数据中学到的语言规律、世界知识和常识。
问题来了:训练数据是有截止日期的。
举个例子:假设一个模型是在2023年6月完成训练的,那么它对2023年6月之后发生的事情一无所知。你问它“2024年奥运会谁得了金牌”,它要么老老实实说“我不知道”,要么——更糟糕的是——它会“编”一个看起来合理的答案,因为它的语言模型能力还在,但它手里的“最新信息”已经过期了。这就像一个记忆力很好的学生,他把2023年6月之前的内容背得滚瓜烂熟,但你问他明天的天气预报,他只能靠猜。
更棘手的是,模型有时候会把自己“编”出来的东西说得特别自信、特别有逻辑,让你很难分辨真假。这不是模型在故意撒谎,而是它的训练目标就是“生成流畅通顺的文本”,而不是“保证内容真实可靠”。这两件事有时候会打架——一段瞎编但语法正确的话,可能比一句正确但略显生硬的话更符合模型的“审美”。
这就是为什么大模型会“胡说八道”的根本原因:它的知识是“死”的,是训练时固定下来的,不能随时更新。
那怎么办?
两个思路:
思路一:继续给它喂新数据,重新训练模型,让它“记住”最新的知识。这就好比让那个学生重新把所有教材背一遍。但问题是,训练一次大模型要花几百万甚至上千万美元,耗时几周甚至几个月,根本不可能天天更新。
思路二:不给模型塞新知识,而是给它接一个“外部图书馆”。学生不需要记住所有知识,只需要知道怎么查资料就行了。这就是RAG的核心思想。
---
RAG:从“闭卷考试”到“开卷考试”
RAG的英文全称是Retrieval-Augmented Generation,中文叫“检索增强生成”。名字听起来复杂,但背后的逻辑非常清晰:让AI在回答问题之前,先去“图书馆”里查一查相关资料,然后用查到的真实信息来辅助生成答案。
我们先用一个你可能熟悉的场景来类比:
闭卷考试:学生把所有知识都背在脑子里。优点是反应快,缺点是记不住最新的、记不住细节、记错了还改不掉。
开卷考试:学生可以带教材、笔记、甚至上网搜。优点是答案更准确、可以处理最新信息,缺点是如果不会找资料、不会用资料,还是白搭。
大模型在没有RAG的情况下,就是典型的“闭卷考试”——它只能靠训练时学到的“死知识”来回答问题。一旦遇到训练数据之外的问题,它就开始瞎编。
RAG就是给AI装上“开卷考试”的能力。具体怎么做呢?
把图书馆借书证交给AI
想象一下,你有一张超级图书馆的借书证,但这张借书证不是给你用的,而是给AI用的。当用户提问时,AI不是直接凭记忆回答,而是先问自己:“这个问题涉及到什么知识?我得去图书馆查一查。”
它走进“图书馆”,图书馆里放的不是纸质书,而是你事先准备好的知识库——可以是公司内部文档、产品手册、研究论文、网络文章,甚至是你的个人笔记。AI在里面检索和用户问题相关的资料,然后把找到的内容和自己的语言能力结合起来,生成最终答案。
这个过程可以分为三个关键步骤,用术语来说就是索引(Indexing)、检索(Retrieval)、生成(Generation)。我们来逐一拆解。
三大核心环节:索引、检索、生成
```mermaid flowchart TD A[用户提问] --> B[检索环节<br/>在知识库中找相关资料] B --> C[增强环节<br/>将检索结果注入prompt] C --> D[生成环节<br/>大模型基于增强后的prompt作答] D --> E[返回答案给用户]
F[知识库] --> B F[知识库] --> G[索引环节<br/>事先将文档切分向量化] ```
第一环节:索引(Indexing)
这是“打基础”的环节。你得先把“图书馆”的藏书整理好,才能让AI来借阅。
具体来说,你需要把大量的文本资料(文档、网页、数据库记录等)进行预处理:把长文档切成一小段一小段的文本(叫“文本块”),然后把每一段文本转换成一种特殊的数字形式——向量(Embedding)。为什么要转换成向量?因为向量是一种“数学语言”,可以让计算机理解“哪些文本在语义上相似”。相似的文本会拥有“距离很近”的向量,这样检索的时候就能快速找到和用户问题最相关的文本块。
你可以把索引环节理解为:图书管理员把每一本书的内容摘要写成“索引卡片”,并按照主题分类归档。
第二环节:检索(Retrieval)
用户提问后,AI先把用户的问题也转换成一个向量(在同一个向量空间里),然后去索引好的知识库中寻找和这个“问题向量”最相似的文本块。
这一步的关键是“相似度匹配”。比如用户问:“苹果公司最新款的手机是什么时候发布的?”检索系统会找出知识库中关于苹果公司、手机发布时间的相关段落,即使这些段落的文字和用户的问题不完全一致,只要内容相关,就能被找出来。
第三环节:生成(Generation)
找到相关资料后,RAG系统把这些资料和用户原本的问题一起,塞进一个“增强后的提示词”(Augmented Prompt)里,然后让大模型基于这个增强后的提示词来生成答案。
打个比方,这就像是学生在开卷考试时,把从书上找到的相关段落用荧光笔标出来,然后根据这些段落来组织答案,而不是凭空瞎编。生成环节让AI的“语言能力”和外部知识的“事实准确性”强强联合。
这就是RAG的基本工作流程。你不需要让AI记住一切,只需要让它学会“查资料”,然后基于查到的真实信息来回答。
---
RAG vs 微调:什么时候该选谁
说到这里,你可能会想:既然想让AI学会新知识,为什么不直接微调模型呢?
微调(Fine-tuning)是另一种让大模型适配特定任务的方法。它的原理是:在已经训练好的大模型基础上,用特定领域的数据再训练一小段时间,让模型的参数发生细微调整,从而适应新任务。打个比方,微调就像是把一个已经学会开车的人送去学开卡车——他还是司机,但专长变了。
微调和RAG看起来都能让AI“学到新东西”,但它们解决的问题和使用场景很不一样。
什么时候选微调
微调适合这种情况:你希望改变模型的行为方式、说话风格,或者让它完成一个特定格式的任务。
举个例子:你想让AI扮演一个“海底捞客服”,能模仿客服的语气和顾客聊天,回复要热情、有礼貌、符合品牌调性。这种“怎么做”的能力,通过微调来学效果更好,因为微调可以改变模型的“行为模式”。
再比如,你想让AI学会写特定格式的代码、或者按照某种专业领域(比如法律、医疗)的规范来回答问题,微调也是个好选择。
什么时候选RAG
RAG适合这种情况:你需要AI回答的答案必须是最新、最准确、最可追溯的。
比如:
- 企业的内部知识库(员工手册、规章制度、产品文档),这些内容随时可能更新,用RAG可以让AI实时“读”到最新版本。
- 需要引用权威来源的场景,比如让AI回答“根据我们的产品手册,这款打印机的墨盒型号是什么”,用RAG可以直接在产品文档中找到并引用。
- 处理时效性信息的场景,比如新闻、赛事结果、股市行情——这些内容一天一个样,靠微调根本跟不上,必须用RAG实时检索。
一个简单的判断标准
这里有个小技巧,可以帮你快速判断该用哪个:
“这个任务,需要AI‘知道’还是‘会做’?”
- 如果是“知道”——比如知道某个产品的参数、某个事件的发生时间——用RAG更合适,因为它能保证信息的时效性和准确性。
- 如果是“会做”——比如按照某种风格说话、按特定格式输出、学会某种推理模式——用微调更合适。
当然,RAG和微调并不是互斥的。很多高级的AI应用会把两者结合起来:用RAG保证信息的准确性,用微调优化生成的质量。但那是后面的章节会深入讨论的内容。现在你只需要记住一个核心结论:RAG解决的是“知识过时”和“信息不可靠”的问题,是给AI装上“实时查资料”能力的最佳选择。
---
动手搭建你的第一个RAG系统
好了,聊了这么多原理,是时候来点实际的了。接下来我会带你用Python搭建一个最基础的RAG演示系统。整个过程会用到几个关键的库,我们一步一步来。
温馨提醒:这个示例的目的是让你体验RAG的核心流程,真正理解“检索+生成”是如何配合工作的。如果你是编程新手,不要担心,我会把每一步都解释清楚;如果你是编程老手,可以直接跳到代码部分,看看我是怎么组织的。
准备工作:安装必要的工具
我们需要用到几个Python库:
- `langchain`:一个帮你快速构建RAG流水线的框架,它把索引、检索、生成这些环节封装好了。
- `chromadb` 或 `faiss`:用于向量存储和检索的数据库。
- `openai`:调用OpenAI的模型(需要API Key)。
对于初学者,我建议先从LangChain的官方文档开始,因为它提供了很多现成的模板。但如果你想自己从零理解原理,也可以不用框架,直接调用底层的向量数据库和大模型API。
步骤一:准备知识库
首先,我们要有一些“资料”供AI检索。为了简单起见,我们用几段文本作为知识库。
```python
知识库中的文档(你可以替换成自己的文档)
documents = [ "巴黎是法国的首都,也是该国最大的城市。它位于法国北部的塞纳河沿岸。", "2024年夏季奥运会将在法国巴黎举办。这是历史上第二次夏季奥运会在巴黎举行。", "埃菲尔铁塔是巴黎最著名的地标之一,建于1889年,高324米。", "卢浮宫是世界上最大的艺术博物馆之一,位于巴黎市中心,收藏了约35万件艺术品。", "法国首都是巴黎,人口约1100万,是该国的政治、经济和文化中心。" ] ```
在真实的RAG系统里,这些文档可能来自一个PDF文件、一些网页内容、或者企业数据库。但现在,我们先用这几行文字来演示核心原理。
步骤二:创建向量索引
接下来,我们需要把这些文档转换成向量,并存储到一个向量数据库中。这里用`ChromaDB`作为演示,它是一个轻量级的本地向量数据库。
```python from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma
1. 将文档切分成小块(这里每块就是一个完整句子)
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0) docs = text_splitter.split_text(documents)
2. 使用OpenAI的Embedding模型将文本转换为向量
embeddings = OpenAIEmbeddings()
3. 将向量存入Chroma数据库
vectorstore = Chroma.from_texts(texts=docs, embedding=embeddings) ```
解释一下这段代码做了什么:
- `CharacterTextSplitter`:把长文档切成小段。在这里,因为我们的文档已经是短句子了,所以每个句子就是一个chunk。
- `OpenAIEmbeddings`:调用OpenAI的模型,把每个文本块转换成一个向量(数字列表)。这一步叫“向量化”。
- `Chroma.from_texts`:把这些向量存进Chroma数据库,并建立索引,方便后续快速检索。
步骤三:检索相关文档
当用户提问时,我们先在知识库里检索和问题最相关的几个文档。
```python
用户的问题
question = "巴黎是哪个国家的首都?"
检索最相关的3个文档
results = vectorstore.similarity_search(question, k=3)
print("检索到的相关文档:") for i, doc in enumerate(results): print(f"{i+1}. {doc.page_content}") ```
运行后,你应该能看到系统检索出了和“巴黎首都”相关的文档。检索的依据是向量之间的“距离”——距离越近,语义越相似。
步骤四:增强提示词并生成答案
检索到相关文档后,我们需要把它们“注入”到提示词里,让大模型基于这些真实资料来回答。
```python from langchain.chains import RetrievalQA from langchain.llms import OpenAI
创建一个RAG链(Retrieval + Generation)
qa_chain = RetrievalQA.from_chain_type( llm=OpenAI(model_name="gpt-3.5-turbo"), chain_type="stuff", # 把检索到的文档全部塞进一个prompt retriever=vectorstore.as_retriever() )
用户提问
question = "巴黎是哪个国家的首都?2024年奥运会在哪里举办?"
获取答案
answer = qa_chain.run(question) print(f"AI回答:{answer}") ```
在这个流程里,系统做了两件事:
1. 检索:找到知识库中与问题最相关的文档。 2. 增强+生成:把检索到的文档和问题一起组成一个新的提示词,让GPT基于这些真实资料来回答,而不是凭“记忆”瞎编。
完整流程可以用下面这张图来总结:
```mermaid flowchart LR subgraph 索引阶段 A1[原始文档] --> A2[文本切分] A2 --> A3[向量化] A3 --> A4[向量数据库] end
subgraph 查询阶段 Q1[用户问题] --> Q2[问题向量化] Q2 --> Q3[向量相似度检索] Q3 --> Q4[获取相关文档] end
subgraph 生成阶段 G1[用户问题] --> G2[构建增强prompt] A4 --> G2 Q4 --> G2 G2 --> G3[调用大模型] G3 --> G4[返回答案] end ```
完整代码一览
把所有步骤串起来,就是一个最小的RAG系统:
```python from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.chains import RetrievalQA from langchain.llms import OpenAI
========== 1. 准备知识库 ==========
documents = [ "巴黎是法国的首都,也是该国最大的城市。它位于法国北部的塞纳河沿岸。", "2024年夏季奥运会将在法国巴黎举办。这是历史上第二次夏季奥运会在巴黎举行。", "埃菲尔铁塔是巴黎最著名的地标之一,建于1889年,高324米。", "卢浮宫是世界上最大的艺术博物馆之一,位于巴黎市中心,收藏了约35万件艺术品。", "法国首都是巴黎,人口约1100万,是该国的政治、经济和文化中心。" ]
========== 2. 创建向量索引 ==========
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0) docs = text_splitter.split_text(documents) embeddings = OpenAIEmbeddings() vectorstore = Chroma.from_texts(texts=docs, embedding=embeddings)
========== 3. 构建RAG问答链 ==========
qa_chain = RetrievalQA.from_chain_type( llm=OpenAI(model_name="gpt-3.5-turbo"), chain_type="stuff", retriever=vectorstore.as_retriever