文章目录
在大模型(LLM)应用开发中,RAG(检索增强生成) 已成为解决模型“知识幻觉”和“知识时效性”问题的标准范式。通过将私有数据或实时数据挂载到大模型上,我们可以构建出专业的知识库问答系统。
本文将深入剖析RAG的核心组件,并演示如何使用 LangChain、Chroma 向量数据库以及 DeepSeek 大模型构建一个完整的 RAG 应用。
📚 一、理论架构:RAG 的两大核心
一个典型的 RAG 应用程序由两个主要流程组成:索引(Indexing) 和 检索与生成(Retrieval and Generation)。
1.1 索引阶段 (Indexing)
这是一个数据“摄取”的过程,通常在离线状态下完成,目的是为后续的搜索做好准备。
- 加载 (Load):通过 文档加载器 (Document Loaders) 将非结构化数据(如 PDF、网页、Markdown)加载为标准化的文档对象。
- 分割 (Split):使用 文本分割器 (Text Splitters) 将大型文档切分为较小的块(Chunk)。
- 为什么要分割? 大块文本难以精确搜索,且容易超出 LLM 的上下文窗口限制。
- 存储 (Store):通过 嵌入模型 (Embedding Model) 将文本块转化为向量,并存储在 向量数据库 (VectorStore) 中。
1.2 检索与生成阶段 (Retrieval & Generation)
这是 RAG 链在运行时与用户交互的实际过程。
- 检索 (Retrieve):根据用户的自然语言查询,检索器 (Retriever) 在向量数据库中计算相似度,找到最相关的 Top-K 个文本块。
- 生成 (Generate):将用户问题与检索到的文本块合并,填入 Prompt 模板,发送给 LLM 生成最终答案。
💻 二、实战演练:构建 DeepSeek 文档助手
我们将使用 LangChain 构建一个 RAG 应用,该应用能够读取 Lilian Weng 的技术博客,并回答关于“AI Agent”的相关问题。
2.1 环境准备
请确保安装了以下核心依赖库:
pip install langchain langchain-chroma langchain-openai bs4
2.2 核心代码实现
以下是完整的源码。我们使用 ChatOpenAI 适配 DeepSeek 模型,使用 Chroma 作为本地向量数据库。
import bs4
from langchain import hub
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
import os
# 1. 初始化 LLM (DeepSeek)
# DeepSeek 兼容 OpenAI SDK,只需配置 base_url 和 api_key
llm = ChatOpenAI(
model_name="deepseek-chat",
temperature=0.7,
max_tokens=2000,
api_key=os.environ["DS_AI_API_KEY"],
base_url="https://api.deepseek.com/v1",
)
# 2. 索引流程:加载 -> 分割 -> 向量化存储
# A. 加载 (Load): 使用 WebBaseLoader 抓取网页
# bs_kwargs 用于过滤网页噪音(如侧边栏、页脚),只保留正文
loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("post-content", "post-title", "post-header")
)
),
)
docs = loader.load()
print(">>> 加载的网页内容预览:")
print(docs[0].page_content[:500])
print("-" * 20)
# B. 分割 (Split): 使用递归字符分割器
# chunk_size=1000: 每块约1000字符
# chunk_overlap=200: 块之间重叠200字符,保证上下文连贯性
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
# C. 存储 (Store): 使用 Chroma 向量库和 OpenAI Embeddings
# 注意:实际生产中建议使用本地 Embedding 模型或专门的 Embedding API
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
# 3. 检索与生成流程
# 将向量库转换为检索器对象
retriever = vectorstore.as_retriever()
# 从 LangChain Hub 拉取标准的 RAG 提示词模板
# 模板内容大致为: "Answer the question based only on the following context: {context} \n Question: {question}"
prompt = hub.pull("rlm/rag-prompt")
def format_docs(docs):
"""将检索到的多个文档块拼接成一个字符串"""
return "\n\n".join(doc.page_content for doc in docs)
# 构建 LCEL (LangChain Expression Language) 链
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 4. 执行提问
question = "What is Task Decomposition?"
print(f">>> 提问: {question}")
response = rag_chain.invoke(question)
print(">>> 回答结果:")
print(response)
print("-" * 20)
# 5. 查看检索到的来源(调试用)
print(">>> 生成所依据的来源文档片段:")
docs = retriever.get_relevant_documents(question)
for i, document in enumerate(docs):
print(f"[Source {i+1}]: {document.page_content[:200]}...\n")
2.3 执行结果分析
运行上述代码后,控制台将输出以下内容。我们可以清晰地看到 RAG 的工作流:先加载了原始文章,然后根据问题“什么是任务分解”,模型给出了精准的定义。
>>> 加载的网页内容预览:
LLM Powered Autonomous Agents
Date: June 23, 2023 | Estimated Reading Time: 31 min | Author: Lilian Weng
Building agents with LLM (large language model) as its core controller is a cool concept. Several proof-of-concepts demos, such as AutoGPT, GPT-Engineer and BabyAGI, serve as inspiring examples. The potentiality of LLM extends beyond generating well-written copies, stories, essays and programs; it can be framed as a powerful general problem solver.
Agent System Overview#
In
--------------------
>>> 提问: What is Task Decomposition?
>>> 回答结果:
Task decomposition is the process of breaking down a complex task into smaller, manageable steps or subgoals. Techniques like Chain of Thought (CoT) and Tree of Thoughts (ToT) use prompting to guide models in decomposing tasks step-by-step. It can also involve human inputs, task-specific instructions, or external tools like classical planners.
--------------------
>>> 生成所依据的来源文档片段:
[Source 1]: Component One: Planning#
A complicated task usually involves many steps. An agent needs to know what they are and plan ahead.
Task Decomposition#
Chain of thought (CoT; Wei et al. 2022) has become a s...
[Source 2]: Task decomposition can be done (1) by LLM with simple prompting like "Steps for XYZ.\n1.", "What are the subgoals for achieving XYZ?", (2) by using task-specific instructions; e.g. "Write a story outl...
[Source 3]: Illustration of how HuggingGPT works. (Image source: Shen et al. 2023)
The system comprises of 4 stages:
(1) Task planning: LLM works as the brain and parses the user requests into multiple tasks. Th...
[Source 4]: Resources:
1. Internet access for searches and information gathering.
2. Long Term memory management.
3. GPT-3.5 powered Agents for delegation of simple tasks.
4. File output.
Performance Evaluation:...
🔍 三、关键技术点解析
3.1 为什么需要 chunk_overlap?
在代码中我们设置了 chunk_overlap=200。
- 问题:如果直接切分,可能会把一句话或一个完整的逻辑切断,导致
Chunk A只有前半句,Chunk B只有后半句。 - 解决:重叠部分确保了语义的连续性。当检索器检索到边缘信息时,这部分重叠能让模型获得完整的上下文。
3.2 LCEL 链式调用
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
这是 LangChain 强大的表达式语言(LCEL):
- 并行处理:字典中的
context和question是并行获取的。context:调用retriever找文档 ->format_docs拼成字符串。question:RunnablePassthrough直接传递用户的问题。
- Prompt 填充:将获取的上下文和问题填入 Prompt。
- 推理与解析:LLM 生成结果,
StrOutputParser将结果转为纯文本。
3.3 DeepSeek 的集成
LangChain 的灵活性在于其组件化。虽然我们使用的是 ChatOpenAI 类,但通过修改 base_url 指向 https://api.deepseek.com/v1,我们轻松地将后端大模型替换为了 DeepSeek,实现了更低成本、更高性能的生成体验。
本文演示了 RAG 应用最基础也最核心的形态:基于向量数据库的文档问答。
- 索引:让数据“入库”,变成机器可理解的向量。
- 检索:让问题“寻址”,找到最相关的知识片段。
- 生成:让模型“开卷考试”,基于参考资料生成准确答案。
:RAG应用构建(向量数据库)&spm=1001.2101.3001.5002&articleId=156944144&d=1&t=3&u=bf5b1eaecf9d43dbb83d86ff8478c3a8)
5677

被折叠的 条评论
为什么被折叠?



