基于LangChain框架的RAG(Retrieval-Augmented Generation)过程,以及它如何集成提示词、应用到RAG、其Memory机制,以及基于ReAct的Agent相关内容。
LangChain框架下的RAG(Retrieval-Augmented Generation)详细介绍
RAG是一种强大的范式,它结合了检索(Retrieval)和生成(Generation)模型,以解决大型语言模型(LLM)在面对特定领域知识或最新信息时的局限性。LangChain为构建RAG系统提供了丰富的模块和工具。
RAG的核心过程
RAG的核心思想是:当用户提出问题时,系统首先从一个或多个外部知识源中检索相关信息,然后将这些检索到的信息作为上下文,连同用户问题一起输入到LLM中,由LLM生成最终的回答。
在LangChain中,RAG过程通常包含以下几个关键步骤:
-
数据加载 (Data Loading):
-
目的: 从各种来源加载原始数据。
-
LangChain组件:
DocumentLoader
。 -
示例: 可以加载PDF、网页、文本文件、数据库等。LangChain提供了多种加载器,例如
PyPDFLoader
、WebBaseLoader
、PandasCSVLoader
等。 -
过程:
- 选择合适的
DocumentLoader
。 - 调用加载器的
load()
方法,将数据加载为Document
对象列表。Document
对象通常包含page_content
(文本内容)和metadata
(元数据,如文件路径、页码等)。
- 选择合适的
-
-
文本分割 (Text Splitting):
-
目的: 将加载的原始文档分割成更小、更易于处理的块(chunks)。这是为了适应LLM的输入长度限制,并确保检索到的块具有足够的相关性。
-
LangChain组件:
TextSplitter
。 -
示例:
RecursiveCharacterTextSplitter
、CharacterTextSplitter
。 -
过程:
- 选择合适的
TextSplitter
。RecursiveCharacterTextSplitter
通常是首选,因为它会尝试按段落、句子等语义单元进行分割,然后才回退到字符分割。 - 设置
chunk_size
(每个块的最大长度)和chunk_overlap
(块之间的重叠部分,有助于保留上下文)。 - 调用分割器的
split_documents()
方法,生成一系列文本块。
- 选择合适的
-
-
向量化 (Vectorization) / 嵌入 (Embedding):
-
目的: 将文本块转换为数值向量(嵌入),以便在向量数据库中进行存储和检索。相似的文本块在向量空间中距离更近。
-
LangChain组件:
Embeddings
。 -
示例:
OpenAIEmbeddings
、HuggingFaceEmbeddings
、GoogleGenerativeAIEmbeddings
。 -
过程:
- 选择一个嵌入模型。
- 将文本块传递给嵌入模型,生成对应的向量表示。
-
-
向量存储 (Vector Store):
-
目的: 存储文本块及其对应的向量嵌入,并提供高效的相似性搜索能力。
-
LangChain组件:
VectorStore
。 -
示例:
Chroma
、FAISS
、Pinecone
、Weaviate
、Qdrant
。 -
过程:
- 初始化一个向量存储实例。
- 使用向量存储的
add_documents()
方法将文本块和它们的嵌入添加到数据库中。 - 在需要检索时,使用
similarity_search()
或其他检索方法根据查询向量查找最相关的文本块。
-
-
检索 (Retrieval):
-
目的: 根据用户查询,从向量数据库中检索最相关的文本块。
-
LangChain组件:
Retriever
。 -
示例:
VectorStoreRetriever
、MultiQueryRetriever
、ContextualCompressionRetriever
。 -
过程:
- 用户输入查询。
- 查询首先被嵌入。
- 使用嵌入后的查询在向量数据库中执行相似性搜索,获取与查询最相似的K个文本块。
- 这些文本块就是检索到的上下文。
-
-
生成 (Generation) / 问答 (Question Answering):
-
目的: 将检索到的上下文和用户查询一起传递给LLM,由LLM生成最终的回答。
-
LangChain组件:
LLM
(或ChatModel
)、PromptTemplate
、Runnable
。 -
示例:
ChatOpenAI
、ChatGoogleGenerativeAI
。 -
过程:
-
构建Prompt: 使用
PromptTemplate
将检索到的上下文和用户问题组合成一个清晰的提示,指导LLM生成高质量的回答。- 通常会有一个系统指令,告诉LLM扮演什么角色,以及如何使用提供的上下文。
- 然后是上下文的占位符。
- 最后是用户问题的占位符。
-
调用LLM: 将构建好的提示发送给LLM。
-
获取回答: LLM根据提示生成回答。
-
-
RAG工作流示意图(LangChain视角):
scss
用户查询 -> Retriever (检索相关文档块) -> LLM (结合查询和文档块生成回答) -> 最终回答
^ ^
| |
Vector Store (存储文档嵌入) <--- Embeddings (文档块向量化) <--- Text Splitter (文档分割) <--- Document Loader (加载原始数据)
RAG如何集成提示词 (Prompt Engineering)
提示词工程在RAG中至关重要,它直接影响LLM生成回答的质量和相关性。
-
明确指示: 在提示中明确告诉LLM它的任务是什么,例如"根据提供的上下文回答问题","如果上下文没有提供信息,请说明"。
-
提供上下文: 这是RAG的核心。将检索到的文档块作为上下文,明确地包含在提示中。
-
示例:
css"请根据以下上下文信息回答问题。 上下文: {context} 问题: {question} 回答:"
-
-
约束回答格式: 如果需要特定格式的回答(例如,列表、总结、简短答案),可以在提示中指定。
- 示例: "请以三点总结的方式回答。"
-
角色扮演: 让LLM扮演特定角色,例如"你是一个专业的法律顾问",这有助于引导其生成更符合预期的回答。
-
避免幻觉: 在提示中添加指令,鼓励LLM在缺乏信息时承认,而不是编造答案。
- 示例: "如果上下文没有提供足够的信息来回答问题,请回复'抱歉,我无法根据现有信息回答这个问题。'"
-
少样本学习 (Few-shot Learning): 在提示中提供少量输入-输出对的示例,帮助LLM理解任务。这在RAG中不常用作核心部分,但在需要特定回答风格时可能有用。
在LangChain中,通常使用PromptTemplate
或ChatPromptTemplate
来构建这些提示词。
Python
python
from langchain_core.prompts import ChatPromptTemplate
# 假设 context_docs 是检索到的文档列表
# prompt 结构包含了上下文占位符和问题占位符
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有用的助手,请根据提供的上下文回答问题。如果上下文没有提供相关信息,请说明。"),
("user", "上下文: {context}\n\n问题: {question}"),
]
)
# 在实际应用中,context 会由检索器提供
# chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser()
RAG中的Memory (记忆)
在RAG应用中,Memory通常指的是对话历史的记忆,这对于构建多轮对话的RAG系统至关重要。如果没有记忆,LLM每次都会将当前问题视为一个全新的问题,无法理解上下文或先前的对话轮次。
LangChain中的Memory机制:
LangChain提供了多种Memory类,用于存储和管理对话历史:
-
ConversationBufferMemory
:- 功能: 存储所有对话回合(包括用户输入和AI输出)的原始形式。
- 特点: 简单直接,但随着对话轮次增加,内存会线性增长,可能超出LLM的上下文窗口限制。
- 使用场景: 短对话或LLM上下文窗口较大的情况。
-
ConversationBufferWindowMemory
:- 功能: 只存储最近的N个对话回合。
- 特点: 控制内存大小,避免上下文溢出。
- 使用场景: 需要限制内存大小以适应LLM上下文窗口的场景。
-
ConversationSummaryMemory
:- 功能: 使用LLM自身来总结之前的对话回合,而不是存储原始对话。
- 特点: 压缩内存,只保留关键信息,更节省token。
- 使用场景: 长对话或需要高效利用token的场景。需要额外的LLM调用来生成摘要。
-
ConversationTokenBufferMemory
:- 功能: 存储对话回合,但会根据token数量进行截断,确保总token数不超过设定的阈值。
- 特点: 精确控制token使用量。
- 使用场景: 对token使用有严格限制的场景。
-
ConversationSummaryBufferMemory
:- 功能: 结合了
ConversationBufferWindowMemory
和ConversationSummaryMemory
的优点。它会保留最近的几个回合的原始形式,并对更早的回合进行总结。 - 特点: 在保留近期详细信息的同时,压缩了历史对话。
- 使用场景: 兼顾近期细节和长期上下文的通用场景。
- 功能: 结合了
RAG中集成Memory:
在RAG系统中,Memory通常与一个ConversationalRetrievalChain
或自定义的chain结合使用。
-
ConversationalRetrievalChain
: 这是LangChain提供的一个高级Chain,专门用于处理带有对话历史的RAG任务。它内部会处理以下逻辑:-
结合当前用户问题和对话历史,生成一个新的"独立"问题(
question_generator
)。这个独立问题移除了对历史对话的指代,使得检索器可以更有效地检索。 -
使用这个独立问题进行检索。
-
将检索到的上下文、原始用户问题和对话历史(或摘要)一起传递给LLM,生成最终回答。
- 示例:
Python
inifrom langchain.chains import ConversationalRetrievalChain from langchain.memory import ConversationBufferWindowMemory from langchain_core.prompts import PromptTemplate from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_community.vectorstores import Chroma # 假设你已经有了向量存储 db 和 LLM llm = ChatOpenAI(model="gpt-4o", temperature=0) embeddings = OpenAIEmbeddings() # 假设 document_chunks 是你的文本块 # db = Chroma.from_documents(document_chunks, embeddings) # retriever = db.as_retriever() # 示例向量存储和检索器 (实际中应加载你的数据) from langchain_core.documents import Document docs = [ Document(page_content="苹果公司由史蒂夫·乔布斯、史蒂夫·沃兹尼亚克和罗纳德·韦恩于1976年4月1日创立。"), Document(page_content="iPad是苹果公司设计、开发和销售的平板电脑系列产品。"), Document(page_content="iPhone是苹果公司发布的一系列智能手机。"), Document(page_content="MacBook是苹果公司生产的笔记本电脑。"), ] db = Chroma.from_documents(docs, embeddings) retriever = db.as_retriever() memory = ConversationBufferWindowMemory(memory_key="chat_history", return_messages=True, k=5) # 可以自定义问题生成和QA的prompt # question_generator_prompt = PromptTemplate.from_template("""根据对话历史和当前问题,生成一个独立的问题,该问题可以独立于历史对话被回答。 # 对话历史: # {chat_history} # 原始问题: {question} # 独立问题:""" # ) qa_chain_prompt = PromptTemplate.from_template("""请根据以下对话历史和检索到的上下文回答问题。 对话历史: {chat_history} 上下文: {context} 问题: {question} 回答:""" ) qa_chain = ConversationalRetrievalChain.from_llm( llm=llm, retriever=retriever, memory=memory, # combine_docs_chain_kwargs={"prompt": qa_chain_prompt} # 如果需要自定义QA Chain的prompt ) # 第一次提问 result1 = qa_chain.invoke({"question": "苹果公司是谁创立的?"}) print(f"User: 苹果公司是谁创立的?\nAI: {result1['answer']}") # 第二次提问,利用记忆 result2 = qa_chain.invoke({"question": "他们还生产什么?"}) print(f"User: 他们还生产什么?\nAI: {result2['answer']}") # 检查记忆中的内容 print("\nMemory Content:") for msg in memory.chat_memory.messages: print(f"{msg.type}: {msg.content}")
-
在这个例子中,ConversationalRetrievalChain
会自动处理将对话历史和当前问题合并以生成新的检索查询,并将检索结果、历史和当前问题提供给LLM以生成最终答案。
基于ReAct的Agent的相关内容
RAG主要关注从现有知识库中检索信息来增强LLM的回答,而Agent则更进一步,赋予LLM执行复杂任务的能力,通常通过使用工具(Tools)和进行多步推理来实现。
ReAct (Reasoning and Acting) 是一个强大的Agent范式。 它的核心思想是:LLM在执行任务时,不仅生成行动(Action) ,还生成推理(Thought) 。这种结合使得Agent能够:
- 计划(Plan): 通过生成推理步骤来规划如何解决问题。
- 执行(Execute): 根据推理结果选择并使用合适的工具。
- 观察(Observe): 从工具的输出中获取观察结果。
- 迭代(Iterate): 根据观察结果调整后续的推理和行动,直到任务完成。
LangChain中的ReAct Agent:
LangChain为构建ReAct Agent提供了强大的支持。
核心组件:
-
LLM (Language Model): 作为Agent的"大脑",负责生成推理和行动。
-
Tools (工具): Agent可以调用的外部功能,例如搜索引擎、计算器、API调用、RAG检索器等。
-
定义: 每个工具都有一个名称、描述和输入模式。描述对于LLM理解何时以及如何使用工具至关重要。
-
示例:
Google Search
(搜索网络)calculator
(执行数学运算)RetrievalQA.from_chain(retriever_chain)
(基于RAG检索内部知识)
-
-
Agent Executor (Agent 执行器): 负责驱动Agent的循环过程,即解析LLM的输出(推理、行动),执行工具,并将工具的观察结果反馈给LLM。
- ReAct Agent的Prompt: 通常包含明确的指令,要求LLM遵循ReAct模式(Thought, Action, Action Input, Observation, ...)。
ReAct Agent的工作流程:
-
用户输入问题。
-
Agent Executor 将问题和历史传递给LLM。
-
LLM思考(Thought): LLM生成一个推理步骤,解释它正在尝试做什么以及为什么。
- 示例:
Thought: 我需要搜索关于...的信息。
- 示例:
-
LLM决定行动(Action)和行动输入(Action Input): LLM根据思考的结果,选择一个合适的工具,并生成调用该工具所需的输入。
- 示例:
Action: Google Search
- 示例:
Action Input: "最新手机型号"
- 示例:
-
Agent Executor 执行行动: 调用指定的工具,并传入相应的输入。
-
工具返回观察结果(Observation): 工具执行后返回结果。
- 示例:
Observation: [搜索结果]
- 示例:
-
Agent Executor 将观察结果反馈给LLM。
-
LLM再次思考(Thought)和行动(Action): LLM根据新的观察结果,继续推理,决定下一步的行动。这个循环持续进行,直到LLM认为问题已解决,并生成最终答案。
- 示例:
Thought: 根据搜索结果,我可以回答这个问题了。
- 示例:
Action: Final Answer
- 示例:
Action Input: [最终答案]
- 示例:
ReAct Agent如何与RAG集成:
RAG可以作为ReAct Agent的一个强大工具。
-
场景: 当Agent需要回答特定领域或私有知识库中的问题时,它可以调用一个RAG工具。
-
实现:
- 首先构建一个独立的RAG链(例如,一个
RetrievalQAChain
或自定义的检索问答链)。 - 将这个RAG链封装成一个LangChain
Tool
。 - 将这个RAG工具添加到ReAct Agent的可用工具列表中。
- 首先构建一个独立的RAG链(例如,一个
示例代码结构 (LangChain ReAct Agent with RAG Tool):
Python
ini
from langchain.agents import AgentExecutor, create_react_agent
from langchain_community.tools.tavily_search import TavilySearchResults # 搜索引擎工具
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain_core.prompts import PromptTemplate
from langchain_core.messages import AIMessage, HumanMessage
# 1. 初始化LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)
embeddings = OpenAIEmbeddings()
# 2. 准备 RAG 数据和链 (作为 Agent 的一个工具)
# 假设你已经加载和分割了文档,并创建了向量存储
# 这里我们创建一个简单的示例
docs = [
Document(page_content="关于公司内部政策,请查阅员工手册。"),
Document(page_content="2024年的公司年会将在三亚举行。"),
Document(page_content="技术部门的联系方式是[email protected]。"),
]
db = Chroma.from_documents(docs, embeddings)
retriever = db.as_retriever()
# 构建 RAG 链
qa_prompt = PromptTemplate.from_template("""根据以下上下文回答问题。
上下文: {context}
问题: {question}
回答:""")
rag_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 简单地将所有检索到的文档塞进LLM
retriever=retriever,
# prompt=qa_prompt, # 可以自定义RAG的prompt
return_source_documents=True
)
# 3. 将 RAG 链封装成一个 Tool
from langchain.tools import Tool
rag_tool = Tool(
name="internal_knowledge_base",
func=rag_chain.run, # 注意这里是 .run
description="当问题涉及到公司内部政策、年会、部门联系方式等内部信息时,使用此工具。输入是用户问题。"
)
# 4. 定义其他工具 (例如,搜索引擎)
search_tool = TavilySearchResults() # 确保你设置了TAVILY_API_KEY环境变量
tools = [rag_tool, search_tool]
# 5. 定义 ReAct Agent 的 Prompt (通常包含 Thought, Action, Action Input, Observation 结构)
# LangChain的 create_react_agent 已经内置了 ReAct 范式的prompt
# 我们需要提供一个基本的prompt,它会和agent的system prompt结合
base_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有用的AI助手,可以根据用户的问题使用工具。"),
("placeholder", "{agent_scratchpad}"), # 这个是ReAct Agent的关键,用于放置中间的Thought/Action/Observation
("human", "{input}"),
]
)
# 6. 创建 ReAct Agent
agent = create_react_agent(llm, tools, base_prompt)
# 7. 创建 Agent Executor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
# 8. 运行 Agent
print("Running Agent with RAG and Search Tools...")
# 问内部知识库的问题
print("\n--- Question 1: Internal Knowledge ---")
result1 = agent_executor.invoke({"input": "2024年的公司年会将在哪里举行?"})
print(f"Agent Answer: {result1['output']}")
# 问外部知识的问题
print("\n--- Question 2: External Knowledge ---")
result2 = agent_executor.invoke({"input": "月球是什么时候形成的?"})
print(f"Agent Answer: {result2['output']}")
# 问一个混合问题 (可能先使用内部,再使用外部,或者选择一个)
print("\n--- Question 3: Mixed Question ---")
result3 = agent_executor.invoke({"input": "请告诉我技术部门的联系方式,以及最新的AI技术发展是什么?"})
print(f"Agent Answer: {result3['output']}")
ReAct Agent的优点:
- 可解释性:
Thought
步骤提供了Agent决策过程的透明度,方便调试和理解。 - 鲁棒性: 能够处理复杂的多步推理任务。
- 灵活性: 可以通过添加不同类型的工具来扩展Agent的能力。
- 避免幻觉: 通过调用外部工具获取真实信息,显著减少LLM的幻觉问题。
总结:
LangChain为构建复杂的RAG和Agent系统提供了模块化且强大的框架。RAG专注于通过检索增强LLM的知识,而ReAct Agent则通过多步推理和工具使用,使LLM能够执行更复杂的任务。RAG本身可以作为ReAct Agent的一个强大工具,使得Agent在需要内部或特定领域知识时,能够准确有效地获取信息。这种结合在构建智能、知识驱动的AI应用中具有巨大潜力。