07_RAG应用 - 知识库问答系统

本课程教你使用RAG(检索增强生成)技术让AI访问私有知识库。你将学习完整的RAG流程:文档加载、分割、向量化、检索、生成回答。课程详细讲解文本分割的技巧、向量数据库的选择、检索参数的调优,以及如何平衡效果与成本。学完后能构建企业级知识库问答系统。

🎯 学习目标

  • 理解RAG的概念和价值
  • 掌握文档处理的完整流程
  • 学会使用向量数据库
  • 构建知识库问答系统

📖 核心概念

什么是RAG?

RAG = Retrieval-Augmented Generation(检索增强生成)

问题:AI的知识有限

1
2
3
4
5
6
7
8
user: "我们公司2024年的销售政策是什么?"
AI: "抱歉,我不知道你们公司的具体政策"

# AI不知道你的私有数据:
# - 公司内部文档
# - 产品手册
# - 客户资料
# - 最新信息

解决方案:RAG

1
2
3
4
5
6
7
8
1. 把文档放入知识库
公司文档 → 分割 → 向量化 → 存入数据库

2. 用户提问时
问题 → 搜索相关文档 → 文档+问题一起给AI → AI回答

3. AI有了参考资料
AI: "根据2024年销售政策文档,..." ✅

生活类比:开卷考试

没有RAG = 闭卷考试

  • 只能靠AI的”记忆”
  • 不知道的就答不出

有RAG = 开卷考试

  • AI可以”翻书”查资料
  • 基于资料给出准确答案

RAG的完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
📚 准备阶段(一次性)
──────────────────────
原始文档

文档加载 (Document Loader)

文本分割 (Text Splitter)

向量化 (Embedding)

存入向量数据库 (Vector Store)


💬 查询阶段(每次提问)
──────────────────────
用户问题

问题向量化

搜索相似文档 (Similarity Search)

取出Top K个相关片段

拼接:问题 + 相关片段

发送给LLM

生成答案

💻 核心组件

1. 文档加载器 (Document Loader)

支持各种格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# PDF文件
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("file.pdf")

# Word文档
from langchain.document_loaders import Docx2txtLoader
loader = Docx2txtLoader("file.docx")

# 文本文件
from langchain.document_loaders import TextLoader
loader = TextLoader("file.txt")

# 网页
from langchain.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://example.com")

# 目录(批量加载)
from langchain.document_loaders import DirectoryLoader
loader = DirectoryLoader("./docs", glob="**/*.txt")

2. 文本分割器 (Text Splitter)

为什么要分割?

1
2
3
4
5
6
7
# 问题:文档太长
长文档 (10000字) → AI的上下文窗口有限
→ 无法一次处理

# 解决:分割成小块
长文档 → 分割成多个chunk → 每个chunk独立存储
→ 查询时只取相关chunk

常用分割器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 按字符数分割(最简单)
from langchain.text_splitter import CharacterTextSplitter

splitter = CharacterTextSplitter(
chunk_size=1000, # 每块1000字符
chunk_overlap=200, # 块之间重叠200字符
separator="\n" # 按换行分割
)

# 2. 递归分割(推荐)
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)

# 3. 按token分割(精确控制)
from langchain.text_splitter import TokenTextSplitter

splitter = TokenTextSplitter(
chunk_size=500, # 500个token
chunk_overlap=50
)

为什么要overlap(重叠)?

1
2
3
4
5
6
7
Chunk1: ...上海是中国最大的城市之一│...
↑ 重叠部分
Chunk2: ...上海是中国最大的城市之一,位于长江入海口...

好处:
- 避免重要信息被切断
- 提供更完整的上下文

3. 向量嵌入 (Embedding)

把文本转换为数字向量:

1
2
3
4
5
6
7
文本: "今天天气很好"

向量: [0.23, -0.45, 0.67, ..., 0.12] # 1536维

文本: "天气不错"

向量: [0.25, -0.43, 0.69, ..., 0.10] # 相似文本,向量相近

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用国内模型
from langchain.embeddings import DashScopeEmbeddings

embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key="your_key"
)

# 或使用OpenAI兼容接口
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key="your_key"
)

4. 向量数据库 (Vector Store)

存储和检索向量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. Chroma(推荐,轻量级)
from langchain.vectorstores import Chroma

vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化
)

# 2. FAISS(快速)
from langchain.vectorstores import FAISS

vectorstore = FAISS.from_documents(
documents=docs,
embedding=embeddings
)

# 3. Pinecone(云端)
from langchain.vectorstores import Pinecone

vectorstore = Pinecone.from_documents(
documents=docs,
embedding=embeddings
)

5. 检索器 (Retriever)

搜索相关文档:

1
2
3
4
5
6
7
8
# 创建检索器
retriever = vectorstore.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 4} # 返回前4个结果
)

# 使用
docs = retriever.get_relevant_documents("什么是LangChain?")

🎨 RAG应用模式

模式1:基础问答

1
2
3
4
5
6
7
8
9
from langchain.chains import RetrievalQA

qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 把所有文档塞给LLM
retriever=retriever
)

answer = qa_chain.invoke("你的问题")

模式2:带来源的问答

1
2
3
4
5
6
7
8
9
10
11
12
from langchain.chains import RetrievalQAWithSourcesChain

qa_chain = RetrievalQAWithSourcesChain.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)

result = qa_chain.invoke("问题")
print(result["answer"])
print(result["sources"]) # 答案来源

模式3:对话式RAG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)

qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
memory=memory
)

# 多轮对话
qa_chain.invoke({"question": "LangChain是什么?"})
qa_chain.invoke({"question": "它有什么优势?"}) # 记住上下文

📊 参数调优

chunk_size(块大小)

1
2
3
4
5
6
7
8
9
10
# 太小:上下文不完整
chunk_size=100 # ❌ 可能切断重要信息

# 太大:噪音多,成本高
chunk_size=5000 # ❌ 检索不精确

# 推荐:根据内容类型调整
- 技术文档:500-1000
- 法律文档:800-1500
- 对话记录:200-500

chunk_overlap(重叠大小)

1
2
3
# 一般为chunk_size的10-20%
chunk_size=1000
chunk_overlap=200 # 20%

k(检索数量)

1
2
3
4
5
6
7
8
9
10
11
12
retriever = vectorstore.as_retriever(
search_kwargs={"k": 4}
)

# 太少:可能漏掉重要信息
k=1 # ❌

# 太多:噪音多,token消耗大
k=20 # ❌

# 推荐:3-5个
k=4 # ✅

🔍 常见问题

Q1: 检索出的内容不相关怎么办?

优化策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. 优化文档质量
- 清理无用内容
- 添加元数据

# 2. 调整chunk大小
splitter = RecursiveCharacterTextSplitter(
chunk_size=800, # 尝试不同大小
chunk_overlap=150
)

# 3. 使用更好的embedding模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

# 4. 添加元数据过滤
retriever = vectorstore.as_retriever(
search_kwargs={
"k": 4,
"filter": {"category": "technical"} # 只搜索技术类
}
)

# 5. 调整检索策略
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximum Marginal Relevance
search_kwargs={"k": 4, "fetch_k": 20}
)

Q2: 如何更新知识库?

1
2
3
4
5
6
7
8
9
10
11
12
# 方案1:重建(简单但慢)
vectorstore = Chroma.from_documents(
documents=new_docs,
embedding=embeddings
)

# 方案2:增量添加(推荐)
vectorstore.add_documents(new_docs)

# 方案3:删除后添加
vectorstore.delete(ids=["doc1", "doc2"])
vectorstore.add_documents(new_docs)

Q3: 如何处理多种格式的文档?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from langchain.document_loaders import (
PyPDFLoader,
Docx2txtLoader,
TextLoader
)

def load_documents(directory: str) -> List[Document]:
"""加载多种格式的文档"""
all_docs = []

for file in os.listdir(directory):
if file.endswith('.pdf'):
loader = PyPDFLoader(file)
elif file.endswith('.docx'):
loader = Docx2txtLoader(file)
elif file.endswith('.txt'):
loader = TextLoader(file)
else:
continue

docs = loader.load()
all_docs.extend(docs)

return all_docs

Q4: 成本太高怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 使用本地embedding(不用API)
from langchain.embeddings import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)

# 2. 减少chunk数量(增大chunk_size)
chunk_size=1500 # 更大的块

# 3. 减少检索数量
k=3 # 只取3个

# 4. 使用缓存
from langchain.cache import InMemoryCache
import langchain
langchain.llm_cache = InMemoryCache()

📝 练习任务

练习1:个人文档问答

创建个人知识库:

  • 上传你的笔记、文档
  • 实现问答功能
  • 显示答案来源

练习2:技术文档助手

创建技术文档助手:

  • 加载多个技术文档
  • 支持多轮对话
  • 代码示例提取

练习3:企业知识库

创建企业知识库系统:

  • 支持多种文档格式
  • 分类管理文档
  • 权限控制
  • 知识库更新

🎯 检查清单

完成本课后,你应该能够:

  • 理解RAG的原理和价值
  • 加载和分割文档
  • 使用向量数据库
  • 创建检索链
  • 优化检索效果
  • 构建完整的问答系统

💡 核心要点

  1. RAG让AI访问私有数据
    • 不需要重新训练模型
    • 实时更新知识
  2. 文档处理是关键
    • 合理的chunk大小
    • 适当的overlap
    • 清理无用内容
  3. 检索质量影响答案
    • 好的embedding模型
    • 合适的检索参数
    • 元数据利用
  4. 成本和效果的平衡
    • chunk大小影响成本
    • k值影响精度和成本
    • 可以使用本地embedding

本节示例代码

01_simple_rag.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
"""
简单RAG实现
演示:文档加载 → 分割 → 向量化 → 查询
"""

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
from langchain.schema import Document
from dotenv import load_dotenv
import os

load_dotenv()


def create_sample_documents() -> list[Document]:
"""创建示例文档"""
texts = [
"""
LangChain是什么?
LangChain是一个开源框架,用于开发由大语言模型(LLM)驱动的应用程序。
它提供了标准化的接口和工具,简化了AI应用的开发过程。
LangChain的核心组件包括:模型、提示词、输出解析器、链、记忆、Agent等。
""",
"""
RAG技术介绍
RAG(Retrieval-Augmented Generation)是检索增强生成技术。
它通过在生成回答之前先检索相关文档,让AI能够访问外部知识库。
RAG的优势包括:准确度高、可更新知识、减少幻觉、成本较低。
RAG的流程:文档加载 → 分割 → 向量化 → 检索 → 生成回答。
""",
"""
向量数据库
向量数据库是专门用于存储和检索向量的数据库。
常见的向量数据库有:Chroma、FAISS、Pinecone、Weaviate等。
向量数据库的核心功能是相似度搜索,通过计算向量间的距离找到最相似的内容。
在RAG应用中,向量数据库用于存储文档的向量表示并快速检索相关内容。
""",
"""
Agent和工具
Agent是能够自主决策和使用工具的AI系统。
与预定义流程的Chain不同,Agent可以根据情况决定使用哪些工具。
常见工具包括:搜索、计算器、数据库查询、API调用等。
Agent的工作流程:接收任务 → 思考 → 决策 → 执行工具 → 观察结果 → 判断是否完成。
"""
]

return [Document(page_content=text.strip()) for text in texts]


def example1_document_processing() -> None:
"""示例1:文档处理流程"""
print("=== 示例1:文档处理 ===\n")

# 1. 创建文档
documents = create_sample_documents()
print(f"📄 原始文档数量:{len(documents)}\n")

# 2. 分割文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 每块200字符
chunk_overlap=50, # 重叠50字符
separators=["\n\n", "\n", "。", ",", " ", ""]
)

splits = text_splitter.split_documents(documents)
print(f"✂️ 分割后chunks数量:{len(splits)}\n")

# 查看前两个chunk
print("📋 前两个chunks:")
for i, chunk in enumerate(splits[:2], 1):
print(f"\nChunk {i}:")
print(chunk.page_content[:100] + "...")


def example2_vector_store() -> None:
"""示例2:创建向量数据库"""
print("\n=== 示例2:向量数据库 ===\n")

# 1. 准备文档
documents = create_sample_documents()

text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)

# 2. 创建embeddings
embeddings = OpenAIEmbeddings(
base_url=os.getenv("ALIBABA_BASE_URL"),
api_key=os.getenv("ALIBABA_API_KEY"),
)

# 3. 创建向量数据库
print("🔄 正在创建向量数据库...")
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./demo_chroma_db" # 持久化存储
)
print("✅ 向量数据库创建完成\n")

# 4. 测试检索
print("🔍 测试相似度搜索:")
query = "什么是RAG?"
results = vectorstore.similarity_search(query, k=2)

print(f"\n查询:{query}")
print(f"找到 {len(results)} 个相关文档:\n")

for i, doc in enumerate(results, 1):
print(f"结果 {i}:")
print(doc.page_content[:150])
print()


def example3_simple_qa() -> None:
"""示例3:简单问答系统"""
print("\n=== 示例3:RAG问答系统 ===\n")

# 1. 准备数据
documents = create_sample_documents()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)

# 2. 创建向量数据库
embeddings = OpenAIEmbeddings(
base_url=os.getenv("ALIBABA_BASE_URL"),
api_key=os.getenv("ALIBABA_API_KEY"),
)

vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings
)

# 3. 创建LLM
llm = ChatOpenAI(
base_url=os.getenv("ALIBABA_BASE_URL"),
api_key=os.getenv("ALIBABA_API_KEY"),
model="qwen-plus",
temperature=0,
)

# 4. 创建问答链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # "stuff"表示把所有文档塞给LLM
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True, # 返回来源文档
verbose=True
)

# 5. 测试问答
questions = [
"LangChain有哪些核心组件?",
"RAG技术的优势是什么?",
"Agent和Chain有什么区别?"
]

for question in questions:
print(f"\n{'='*60}")
print(f"❓ 问题:{question}")
print('='*60 + "\n")

result = qa_chain.invoke({"query": question})

print(f"✅ 回答:{result['result']}\n")

print("📚 参考文档:")
for i, doc in enumerate(result['source_documents'], 1):
print(f"\n文档 {i}:")
print(doc.page_content[:100] + "...")


def example4_custom_prompt() -> None:
"""示例4:自定义提示词"""
print("\n\n=== 示例4:自定义RAG提示词 ===\n")

from langchain.prompts import PromptTemplate

# 准备数据
documents = create_sample_documents()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)

embeddings = OpenAIEmbeddings(
base_url=os.getenv("ALIBABA_BASE_URL"),
api_key=os.getenv("ALIBABA_API_KEY"),
)

vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings
)

llm = ChatOpenAI(
base_url=os.getenv("ALIBABA_BASE_URL"),
api_key=os.getenv("ALIBABA_API_KEY"),
model="qwen-plus",
temperature=0,
)

# 自定义提示词
custom_prompt = PromptTemplate(
template="""
你是一个专业的AI助手。请基于以下参考资料回答问题。

参考资料:
{context}

问题:{question}

回答要求:
1. 只基于参考资料回答,不要编造信息
2. 如果参考资料中没有相关信息,请说"根据提供的资料,我无法回答这个问题"
3. 回答要简洁明了

回答:
""",
input_variables=["context", "question"]
)

# 创建问答链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(),
chain_type_kwargs={"prompt": custom_prompt}
)

# 测试
question = "向量数据库有哪些?"
print(f"❓ 问题:{question}\n")

result = qa_chain.invoke({"query": question})
print(f"✅ 回答:{result['result']}")


def main() -> None:
"""主函数"""
example1_document_processing()
print("\n" + "="*70 + "\n")

example2_vector_store()
print("\n" + "="*70 + "\n")

example3_simple_qa()
print("\n" + "="*70 + "\n")

example4_custom_prompt()


if __name__ == "__main__":
main()

📚 下一步

现在你已经掌握了RAG技术,接下来把所有知识整合到实战项目中:


继续学习: 第8课:实战项目 →