吴恩达《LangChain LLM 应用开发精读笔记》5-Question and Answer 问答
今天我们学习了 RAG 的核心流程。核心三要点Embeddings是灵魂:让机器理解语义相似度,把“词”变成“数”。是仓库:高效存取这些语义向量,实现快速检索。是工人:负责“搬运文档”并让 LLM 结合文档回答问题。一句话记住它RAG 就是给 LLM 买了本参考书,遇到不会的问题,先查书,再回答。怎么知道 AI 回答得对不对?—— Evaluation (评估)。🚀。

大家好,我是飞哥!👋
欢迎来到吴恩达《LangChain LLM 应用开发》系列课程的第五讲。这一讲,我们要聊一个非常火的话题:如何让 AI 基于你自己的文档来回答问题?
也就是大家常说的 “RAG” (Retrieval-Augmented Generation,检索增强生成)。
1. 为什么:给 AI 配一本“参考书” 📖
Harrison 在视频中提到:
“One of the most common complex applications… is a system that can answer questions on top of a document.”
“最常见也是最复杂的应用场景之一… 就是一个能够基于文档回答问题的系统。”
1.1 场景锚定 ⚓️
你有没有发现,直接问 ChatGPT 一些公司内部的问题(比如“我们公司去年的报销政策是啥?”),它肯定答不上来。
或者问它“昨天的最新新闻”,它也可能不知道。
因为 LLM(大语言模型)有三个主要局限:
- 不知道私有数据:它没看过你公司的内网文档。
- 知识过时:它的训练数据截止到某个时间点。
- 容易产生幻觉:不知道的时候它可能会一本正经地胡说八道。
1.2 生动类比 🍊
- LLM 就像一个“博学的博士”,但他是个“书呆子”,只知道书本上的旧知识,对你公司的情况一无所知。
- RAG 就像是“给博士配了一本参考书”(你的文档)。
- 当你问问题时,博士先去“翻书”(检索),找到相关的那几页,然后结合书里的内容“回答”你。
1.3 核心骨架 🦴
所以,“RAG” (检索增强生成) 的本质,就是:
先检索 (Retrieval),后生成 (Generation)。
我们来看一下这个过程的流程图:
2. 是什么:LangChain 的 RAG 组件 🧩
要实现这个流程,我们需要两个关键技术组件:
2.1 Embeddings (嵌入/向量化)
- 通俗解释:把文字变成一串数字(向量)。
- 神奇之处:意思相近的句子,变出来的数字也靠得很近。比如“狗”和“宠物”在数学空间里距离很近,但和“汽车”很远。
- 作用:让计算机能理解“语义”。
💡 飞哥小贴士:数值越大越相似,还是越小越相似?
这取决于你用什么“尺子”去量:
- 余弦相似度 (Cosine Similarity):这是最常用的(如 OpenAI Embeddings)。它衡量的是“方向一致性”。数值越大 (越接近 1),相似度越高。
- 欧几里得距离 (L2 Distance):它衡量的是“空间距离”。数值越小 (越接近 0),相似度越高。
在 LangChain 和大多数 RAG 应用中,默认通常使用余弦相似度,所以记住:分数越高,越相关!
2.2 Vector Database (向量数据库)
- 通俗解释:专门存这些数字向量的仓库。
- 作用:让我们能在一瞬间(毫秒级)从海量文档中找到和“用户问题”最相似的片段。
3. 怎么用:实战演练 (DeepSeek 版) 🛠️
我们要用代码来验证一下。为了方便大家通过 DeepSeek 学习,我们对代码做了适配,并将所有 Prompt 翻译成了中文。
3.1 环境准备
请确保你的项目目录中有一个 .env 文件,内容如下(把 Key 换成你自己的):
OPENAI_API_KEY=sk-你的DeepSeek密钥
OPENAI_API_BASE=https://api.deepseek.com
OPENAI_MODEL_NAME=deepseek-chat
此外,本节代码需要安装以下依赖:
pip install docarray langchain-openai sentence-transformers
3.2 代码实战
(1) 准备工作:初始化模型与数据 🏗️
首先,我们需要加载环境变量,并配置 DeepSeek 模型和 Embeddings。
import os
from langchain_community.document_loaders import CSVLoader
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain.indexes import VectorstoreIndexCreator
from langchain_openai import OpenAIEmbeddings
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from dotenv import load_dotenv, find_dotenv
# 加载环境变量
_ = load_dotenv(find_dotenv())
# 配置 OpenAI API Key 和 Base URL (适配 DeepSeek)
api_key = os.getenv("OPENAI_API_KEY")
base_url = os.getenv("OPENAI_API_BASE")
model_name = os.getenv("OPENAI_MODEL_NAME")
print(f"🚀 正在启动...")
print(f"🤖 模型名称: {model_name}")
print(f"🔗 API Base: {base_url}")
# 初始化 LLM (大语言模型)
# 注意:tiktoken_model_name="gpt-3.5-turbo" 是为了解决 DeepSeek 等非 OpenAI 官方模型
# 在 LangChain 中计算 Token 时可能出现的 "model not found" 错误。
llm = ChatOpenAI(
temperature=0.0,
model=model_name,
base_url=base_url,
api_key=api_key,
tiktoken_model_name="gpt-3.5-turbo"
)
# 初始化 Embeddings (嵌入模型)
# 策略:自动选择最佳 Embeddings 模型
try:
if "deepseek" in (model_name or "").lower():
print("💡 检测到 DeepSeek 模型,自动切换为本地 HuggingFaceEmbeddings 以节省成本并避免 API 兼容问题...")
from langchain_community.embeddings import HuggingFaceEmbeddings
# 使用更轻量级的模型,下载更快
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
else:
embeddings = OpenAIEmbeddings(api_key=api_key)
except Exception as e:
print(f"⚠️ Embeddings 初始化失败,正在尝试 fallback 到 OpenAIEmbeddings... 错误: {e}")
embeddings = OpenAIEmbeddings(api_key=api_key, base_url="https://api.openai.com/v1")
# 模拟一个 CSV 文件 (如果没有本地文件)
csv_content = """name,description
男士热带格纹短袖衬衫,"评级:防晒。由100%聚酯纤维制成,这款衬衫抗皱并具有UPF 50+的防晒功能。"
男士格纹热带衬衫,"评级:防晒。UPF 50+防晒。100%聚酯纤维。抗皱。"
女士防晒衬衫,"评级:防晒。吸湿排汗。UPF 50+。100%聚酯纤维。"
男士羊毛衫,"温暖舒适。100%羊毛。仅干洗。"
"""
csv_file_path = "OutdoorClothingCatalog_1000.csv"
if not os.path.exists(csv_file_path):
with open(csv_file_path, "w") as f:
f.write(csv_content)
print(f"✅ 已创建模拟数据文件: {csv_file_path}")
loader = CSVLoader(file_path=csv_file_path)
(2) 快速上手:一行代码创建问答系统 (High Level) ⚡️
LangChain 提供了一个极简的封装 VectorstoreIndexCreator,就像魔法一样,把所有复杂的 RAG 步骤都藏在了一行代码里。
print("\n--- 1. 一行代码创建索引与问答 (High Level) ---")
print("说明:LangChain 提供了极简的封装 (VectorstoreIndexCreator),像魔法一样一行代码搞定 RAG。")
# 创建索引 (需要安装 docarray: pip install docarray)
# 这一步内部做了三件事:
# 1. Loader 加载数据
# 2. Embeddings 将数据向量化
# 3. VectorStore 存储向量
try:
index = VectorstoreIndexCreator(
vectorstore_cls=DocArrayInMemorySearch,
embedding=embeddings,
).from_loaders([loader])
query = "请用 Markdown 表格列出所有具有防晒功能的衬衫,并为每个衬衫写一个简短的中文摘要。"
# query 方法内部自动完成了:检索(Retrieval) -> 拼接Prompt -> 调用LLM(Generation)
response = index.query(query, llm=llm)
print(f"User Query: {query}")
print(f"\nAI Response:\n{response}")
# --- 运行结果示例 ---
# User Query: 请用 Markdown 表格列出所有具有防晒功能的衬衫,并为每个衬衫写一个简短的中文摘要。
#
# AI Response:
# | 产品名称 | 防晒功能 | 材质 | 特点摘要 |
# | :--- | :--- | :--- | :--- |
# | 女士防晒衬衫 | UPF 50+ | 100%聚酯纤维 | 具有防晒、吸湿排汗功能的女士衬衫。 |
# | 男士热带格纹短袖衬衫 | UPF 50+ | 100%聚酯纤维 | 防晒、抗皱的男士短袖格纹衬衫。 |
# | 男士格纹热带衬衫 | UPF 50+ | 100%聚酯纤维 | 防晒、抗皱的男士格纹衬衫。 |
except Exception as e:
print(f"❌ Error in Index Creator: {e}")
(3) 深度拆解:Step-by-Step 理解 RAG 原理 🔍
虽然“一行代码”很爽,但作为开发者,我们必须理解黑盒里发生了什么。让我们把 RAG 的四个步骤拆开来看。
Step 1: Embeddings (向量化)
把文本变成计算机能理解的数字。
print("\n--- 2. 深度拆解 (Step-by-Step RAG) ---")
print("\n[Step 1] Embeddings (向量化)...")
test_text = "你好,我叫飞哥"
embed = embeddings.embed_query(test_text)
print(f"文本: '{test_text}'")
print(f"向量维度: {len(embed)}")
print(f"向量前5位: {embed[:5]}... (计算机看到的语义)")
# --- 运行结果示例 ---
# 文本: '你好,我叫飞哥'
# 向量维度: 384
# 向量前5位: [-0.03247414156794548, 0.10191547125577927, 0.04291674867272377, 0.03600271791219711, 0.036942895501852036]... (计算机看到的语义)
Step 2: Vector Store (存入向量库)
将所有文档切片并向量化,存入数据库。
print("\n[Step 2] Vector Store (存入向量库)...")
docs = loader.load()
# 将所有文档转换成向量,并存入内存中的向量数据库 (DocArrayInMemorySearch)
db = DocArrayInMemorySearch.from_documents(docs, embeddings)
print("✅ 文档已存入向量数据库。")
Step 3: Retrieval (检索)
根据用户的问题,在数据库中找出最相似的文档片段。
print("\n[Step 3] Retrieval (检索)...")
query = "请列出所有具有防晒功能的衬衫"
# 找出与 query 语义最相似的文档
docs = db.similarity_search(query)
print(f"用户问题: {query}")
print(f"🔍 检索到 {len(docs)} 个相关文档:")
print(f" > 文档 1: {docs[0].page_content}")
# --- 运行结果示例 ---
# 用户问题: 请列出所有具有防晒功能的衬衫
# 🔍 检索到 4 个相关文档:
# > 文档 1: name: 男士热带格纹短袖衬衫
# description: 评级:防晒。由100%聚酯纤维制成,这款衬衫抗皱并具有UPF 50+的防晒功能。
Step 4: Chain (构建问答链)
将检索到的文档片段(Context)和用户问题(Question)一起发给 LLM,生成最终回答。
print("\n[Step 4] Chain (构建问答链)...")
# 创建检索器接口
retriever = db.as_retriever()
# 创建 RetrievalQA 链
# chain_type="stuff" 的意思是:
# 把检索到的所有文档片段(Stuffing),一股脑填充到 Prompt 中发给 LLM。
qa_stuff = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
verbose=True
)
print("🚀 开始运行问答链...")
response = qa_stuff.run(query)
print(f"\n🤖 AI 最终回答:\n{response}")
# --- 运行结果示例 ---
# 🚀 开始运行问答链...
#
# 🤖 AI 最终回答:
# 根据提供的产品信息,具有防晒功能的衬衫如下:
#
# 1. **男士热带格纹短袖衬衫**
# - **评级**:防晒
# - **关键功能**:具有UPF 50+的防晒功能。
#
# 2. **女士防晒衬衫**
# - **评级**:防晒
# - **关键功能**:UPF 50+。
#
# 3. **男士格纹热带衬衫**
# - **评级**:防晒
# - **关键功能**:UPF 50+防晒。
#
# **请注意**:另一款“男士羊毛衫”的描述中未提及任何防晒功能,因此不在上述列表中。
4. 飞哥总结 📝
今天我们学习了 RAG 的核心流程。
核心三要点:
- Embeddings 是灵魂:让机器理解语义相似度,把“词”变成“数”。
- VectorStore 是仓库:高效存取这些语义向量,实现快速检索。
- RetrievalQA 是工人:负责“搬运文档”并让 LLM 结合文档回答问题。
一句话记住它:
“RAG 就是给 LLM 买了本参考书,遇到不会的问题,先查书,再回答。”
下一讲,我们来聊聊一个很现实的问题:怎么知道 AI 回答得对不对?—— Evaluation (评估)。🚀
更多推荐

所有评论(0)