在构建强大的检索增强生成(RAG)应用中,LangChain 提供了一套强大的工具,包括 Prompt 模板、Chain 链和 Output Parser 输出解析器。本文将深入探讨如何利用这些组件构建结构化、可靠的大语言模型(LLM)工作流,重点介绍如何编写可复用的 Prompt,使用基于 Runnable 的 Chain 构建逻辑流程,以及将原始 LLM 输出解析为结构化结果。通过结合实际案例和最佳实践,我们将展示如何利用 LangChain 打造可扩展且易于调试的 RAG 应用。
Prompt 工程:构建模块化的提示语
在 LangChain 中,Prompt 工程采用模块化的方式,允许开发者定义清晰的角色、指令和上下文。LangChain 提供了以下核心组件:
- SystemMessage: 定义 LLM 的行为和角色,例如指定其为某个领域的专家。
- HumanMessage: 代表用户的提问,即输入给 LLM 的指令。
- AIMessage: LLM 生成的回复,可以是可选的,用于提供示例或上下文。
- ChatPromptTemplate: 将上述组件组合成完整的消息列表,并支持占位符,实现 Prompt 的复用。
例如,以下代码展示了如何创建一个 ChatPromptTemplate,使其可以根据不同的领域和主题生成不同的提示语:
from langchain.prompts.chat import ChatPromptTemplate, SystemMessage, HumanMessage
prompt = ChatPromptTemplate.from_messages([
SystemMessage(content="你是一位乐于助人的{domain}专家。"),
HumanMessage(content="请告诉我关于{topic}的内容。")
])
formatted_prompt = prompt.invoke({"domain": "体育", "topic": "足球"})
print(formatted_prompt.to_string())
在这个例子中,我们创建了一个 Prompt 模板,其中 domain
和 topic
是占位符。通过调用 invoke
方法并传入不同的参数,我们可以生成针对不同领域和主题的提示语。这种模块化的设计使得 Prompt 更加易于维护和复用,极大地提高了开发效率。
另一个例子展示了如何在RAG流程中使用Prompt:
def ask_query(query):
#get retriever
retriever = vectorstore.as_retriever(search_type='similarity', search_kwargs={'k': 2})
results = retriever.invoke(query)
context = "\n".join([document.page_content for document in results])
prompt = f"""
你是一位FAQ助理。请使用以下内容准确、简洁地回答用户的问题:
{context}
Q: {query}
A:
"""
response = model.invoke(prompt)
return response.content
这个例子中,我们根据从向量数据库中检索到的上下文信息动态生成 Prompt,并将其与用户查询结合,为 LLM 提供更丰富的上下文,从而提高回答的准确性。
Chain:连接组件构建逻辑流程
LangChain Chain 使用 Runnable 将多个步骤组合在一起,用于管理逻辑和流程。Runnable 提供了多种类型,以满足不同的需求:
- RunnableSequence: 顺序执行 Runnable,例如:Prompt -> LLM -> Output Parser。
- RunnableParallel: 并行执行多个分支,例如:获取元数据 + 摘要。
- RunnableLambda: 将自定义函数作为 Chain 的一部分,例如:预处理输入。
- RunnableBranch: 实现 if/else 风格的分支逻辑,例如:条件路由。
- RunnablePassthrough: 将输入原样传递,例如:默认输入/返回 identity。
以下代码展示了如何使用 RunnableSequence
创建一个简单的 Chain:
from langchain.schema.runnable import RunnableSequence
from langchain.output_parsers import StrOutputParser
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
template = "你是一位 {domain} 专家,请告诉我关于 {topic} 的内容。"
prompt = PromptTemplate.from_template(template)
chain = RunnableSequence([
prompt, # PromptTemplate
OpenAI(), # LLM
StrOutputParser() # 清理输出
])
result = chain.invoke({"domain": "历史", "topic": "二战"})
print(result)
在这个例子中,我们首先定义了一个 Prompt 模板,然后使用 RunnableSequence
将其与 OpenAI 模型和 StrOutputParser
组合成一个 Chain。当调用 invoke
方法时,数据会依次通过 Prompt 模板、LLM 和 Output Parser,最终返回一个干净的字符串。
LangChain Expression Language (LCEL) 提供了更简洁的语法来定义 Chain:
chain = prompt | OpenAI() | StrOutputParser()
这种语法更加直观,易于阅读和理解。
使用 Chain 的好处在于它可以将复杂的逻辑分解为多个简单的步骤,每个步骤都可以独立测试和调试。这使得 Chain 更加易于维护和扩展。
Output Parser:将 LLM 输出转换为结构化数据
默认情况下,LLM 返回的是纯文本。LangChain Output Parser 可以将这些文本转换为结构化数据,例如列表、字典或自定义对象。LangChain 提供了多种 Output Parser:
- StrOutputParser: 将 LLM 输出转换为干净的字符串。
- CommaSeparatedListOutputParser: 从 CSV 文本中提取列表。
- PydanticOutputParser: 将 LLM 输出转换为 Pydantic 对象。
- JsonOutputParser: 将 LLM 输出转换为 JSON 字典。
以下代码展示了如何使用 PydanticOutputParser
将 LLM 输出转换为 Pydantic 对象:
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel
class Answer(BaseModel):
topic: str
summary: str
parser = PydanticOutputParser(pydantic_object=Answer)
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
template = "请用一句话总结一下关于{topic}的内容。主题: {topic}\n{format_instructions}\n"
prompt = PromptTemplate(
template=template,
input_variables=["topic"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
chain = prompt | OpenAI() | parser
result = chain.invoke({"topic": "量子力学"})
print(result)
print(type(result))
在这个例子中,我们首先定义了一个 Pydantic 模型 Answer
,用于描述输出的结构。然后,我们创建了一个 PydanticOutputParser
,并将其与 Prompt 和 OpenAI 模型组合成一个 Chain。当调用 invoke
方法时,LLM 的输出会被解析成 Answer
对象,我们可以方便地访问其中的 topic
和 summary
属性。
使用 Output Parser 的好处在于它可以确保 LLM 的输出符合预期的结构,方便后续处理。例如,我们可以将解析后的数据用于 API、UI 或数据分析。
RAG 工作流实例
将 Prompt、Chain 和 Output Parser 结合起来,我们可以构建复杂的 RAG 工作流。例如,我们可以构建一个文档问答系统,它可以根据用户的问题从文档中检索相关信息,并生成简洁的答案。
以下是一个简化的示例:
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
# 1. 加载文档
loader = TextLoader("your_document.txt")
documents = loader.load()
# 2. 分割文本
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 3. 创建 Embedding
embeddings = OpenAIEmbeddings()
# 4. 创建向量数据库
db = Chroma.from_documents(texts, embeddings)
# 5. 创建检索器
retriever = db.as_retriever()
# 6. 创建 QA Chain
qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=retriever)
# 7. 提问
query = "什么是 RAG?"
result = qa.run(query)
print(result)
在这个例子中,我们首先加载文档,然后将其分割成小块。接着,我们使用 OpenAIEmbeddings 创建文本的 Embedding,并将其存储在 Chroma 向量数据库中。然后,我们创建一个检索器,用于根据用户的问题从向量数据库中检索相关信息。最后,我们使用 RetrievalQA
Chain 将检索器与 OpenAI 模型组合在一起,用于生成答案。
这个例子展示了如何将 LangChain 的多个组件组合在一起,构建一个完整的 RAG 应用。
最佳实践
以下是一些构建 RAG 应用的最佳实践:
- 使用
ChatPromptTemplate
实现 Prompt 的模块化和复用。 - 永远不要信任原始的 LLM 输出,使用 Output Parser 将其转换为结构化数据。
- 使用
RunnableSequence
将复杂的逻辑分解为多个简单的步骤,方便测试和调试。 - 使用
RunnableBranch
管理复杂的逻辑分支。 - 独立测试每个组件,确保其正常工作。
通过遵循这些最佳实践,您可以构建可扩展、易于调试且高质量的 RAG 应用。
总结
本文深入探讨了如何利用 LangChain 构建 RAG 应用,重点介绍了 Prompt 工程、Chain 构建和 Output Parser 的使用。通过结合实际案例和最佳实践,我们展示了如何利用 LangChain 打造结构化、可靠且可扩展的 LLM 工作流。理解并掌握这些概念对于构建强大的 RAG 应用至关重要。 LangChain 的这些工具为开发者提供了强大的灵活性和控制力,使得他们能够构建出能够理解和生成复杂文本的智能应用。 未来,我们可以期待 LangChain 社区不断推出新的组件和工具,进一步简化 RAG 应用的开发过程。