LangChain 的核心思想:链式思维 (Chaining)
想象一下,你要完成一个复杂的任务,比如“总结我上传的这篇PDF,并根据内容回答我的问题”。这个任务可以拆解为:
- 加载 PDF 文件。
- 读取 文件内容。
- 拆分 内容成小块,以便模型处理。
- 将文本块转换为向量(一种数学表示)。
- 将向量存储起来,方便快速检索。
- 当用户提问时,将问题也转换为向量。
- 用问题的向量去检索最相关的内容块。
- 将问题和检索到的内容组合成一个提示(Prompt)。
- 把这个提示喂给大语言模型(LLM)。
- 解析并返回模型的答案。
LangChain 的作用就是将这些步骤(组件)标准化,然后像链条一样把它们串联起来,自动化整个流程。你不需要手动去写每一步的胶水代码,只需要选择并配置好合适的“积木”即可。
一、 LangChain 的核心组件 (The “LEGO Bricks”)
这是 LangChain 的基石,理解了它们,你就理解了 LangChain 的一切。
1. Models (模型) - 大脑
这是驱动一切的核心。LangChain 集成了各种模型。
- LLMs: 普通的语言模型,输入是字符串,输出也是字符串。
from langchain_openai import OpenAI # 需要在环境变量中设置 OPENAI_API_KEY llm = OpenAI(model_name="gpt-3.5-turbo-instruct") response = llm.invoke("请给我写一首关于宇宙的诗") print(response)
- Chat Models: 对话模型,为多轮对话优化,输入是消息列表(System, Human, AI),输出是 AI 消息。这是目前更常用的类型。
from langchain_openai import ChatOpenAI from langchain.schema import HumanMessage, SystemMessage chat = ChatOpenAI(model_name="gpt-4o") messages = [ SystemMessage(content="你是一个专业的菜谱助手。"), HumanMessage(content="你好,请问西红柿炒鸡蛋怎么做?") ] response = chat.invoke(messages) print(response.content)
2. Prompts (提示) - 指示
如何与模型沟通?你需要一个好的提示模板。
- PromptTemplate: 用于简单的、变量化的提示。
from langchain.prompts import PromptTemplate prompt_template = PromptTemplate.from_template( "请为我写一个关于 {topic} 的故事大纲。" ) prompt = prompt_template.format(topic="星际旅行") print(prompt) # 输出: "请为我写一个关于星际旅行的故事大纲。"
- ChatPromptTemplate: 用于对话模型,可以定义系统、用户、AI等不同角色的消息模板。
from langchain.prompts import ChatPromptTemplate chat_template = ChatPromptTemplate.from_messages([ ("system", "你是一个翻译官,能将{input_language}翻译成{output_language}。"), ("human", "{text}") ]) messages = chat_template.format_messages( input_language="中文", output_language="英文", text="我爱编程" ) # chat.invoke(messages) # 可以直接把这个messages列表传给Chat Model
3. Chains (链) - 胶水和流水线
这是 LangChain 的灵魂,用于将模型和提示等组件粘合在一起。
- 简单的
LLMChain
(传统方式)from langchain.chains import LLMChain # 1. 模型 llm = OpenAI(model_name="gpt-3.5-turbo-instruct") # 2. 提示 prompt_template = PromptTemplate.from_template("世界上最高的山是哪座?请用中文回答。") # 3. 链接起来 chain = LLMChain(llm=llm, prompt=prompt_template) # 4. 运行 response = chain.run({}) # 因为模板里没有变量,所以传入空字典 print(response)
- LCEL (LangChain Expression Language) - 现代化的方式 (强烈推荐!)
LCEL 使用管道符
|
来连接组件,更灵活、更强大,支持流式传输、异步、并行等。这是未来的方向。LCEL 的from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser # 1. 提示 prompt = ChatPromptTemplate.from_template("请告诉我关于 {topic} 的一个冷知识。") # 2. 模型 model = ChatOpenAI() # 3. 输出解析器 (将模型的输出转为字符串) output_parser = StrOutputParser() # 4. 使用管道符 | 链接 chain = prompt | model | output_parser # 5. 调用 response = chain.invoke({"topic": "章鱼"}) print(response)
|
符号,你可以理解为:把左边的输出,作为右边的输入。
4. Output Parsers (输出解析器) - 格式化
LLM 的输出是文本,但我们常常需要结构化的数据(如 JSON、列表)。输出解析器就是用来干这个的。
- PydanticOutputParser: 定义一个 Pydantic 模型,让 LLM 输出符合该模型结构的 JSON。
from pydantic import BaseModel, Field from langchain.output_parsers import PydanticOutputParser # 1. 定义你的数据结构 class Joke(BaseModel): setup: str = Field(description="笑话的铺垫") punchline: str = Field(description="笑话的点睛之笔") # 2. 创建解析器 parser = PydanticOutputParser(pydantic_object=Joke) # 3. 创建带格式化指令的提示 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个很棒的笑话机器人。{format_instructions}"), ("human", "请给我讲一个关于程序员的笑话。") ]) chain = prompt | model | parser # 获取格式化指令并注入提示 response = chain.invoke({ "format_instructions": parser.get_format_instructions() }) print(response) print(f"铺垫: {response.setup}") print(f"笑点: {response.punchline}")
5. Indexes and Retrievers (索引与检索器) - 外部知识库 (RAG核心)
这是构建问答机器人、文档分析等应用的关键。它让 LLM 能“看到”你的私有数据。
这个过程通常分为 Indexing (索引) 和 Retrieval (检索) 两步。
-
Indexing 流程:
- Document Loaders (文档加载器): 加载各种来源的数据(PDF, TXT, Web, …)。
- Text Splitters (文本分割器): 将长文本切分成适合模型处理的小块。
- Embeddings (词嵌入): 将文本块转换成向量。这需要一个 Embedding Model。
- Vector Stores (向量数据库): 存储这些向量,并提供高效的相似性搜索。
-
Retrieval 流程:
- 用户提问。
- 将问题也转换成向量。
- 去向量数据库中检索与问题向量最相似的文本块向量。
- 将这些文本块(即上下文
context
)连同问题一起交给 LLM。
一个完整的 RAG 示例 (使用 LCEL):
import os
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.vectorstores import FAISS # 一种简单的本地向量数据库
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# --- 1. 索引 (Indexing) ---
# 假设我们有一个 'state_of_the_union.txt' 文件
# a. 加载文档
loader = TextLoader("./state_of_the_union.txt")
docs = loader.load()
# b. 分割文本
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
# c. 创建向量数据库
# 这会调用 OpenAI Embedding API 将文本转为向量并存储在 FAISS 中
vectorstore = FAISS.from_documents(documents=splits, embedding=OpenAIEmbeddings())
# --- 2. 检索与生成 (Retrieval and Generation) ---
# a. 创建检索器
retriever = vectorstore.as_retriever()
# b. 创建 RAG 提示模板
template = """
根据以下上下文来回答问题:
{context}
问题: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# c. 创建 RAG 链
model = ChatOpenAI()
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# d. 调用链
question = "What did the president say about Ketanji Brown Jackson"
response = rag_chain.invoke(question)
print(response)
代码解释:
{"context": retriever, "question": RunnablePassthrough()}
是 LCEL 的一个技巧。它会创建一个字典,question
的值直接来自invoke
的输入,context
的值则是通过retriever
处理invoke
的输入(即问题)得到的。- 这个字典随后被传递给
prompt
进行格式化,然后是模型,最后是解析器。
6. Agents (代理) - 思考与行动
如果说 Chain 是固定的流水线,那 Agent 就是一个拥有工具箱的智能工人。它能根据你的问题,自己决定调用哪个工具(如谷歌搜索、计算器、数据库查询等),然后根据工具返回的结果,思考下一步该怎么做,直到问题解决。
from langchain.agents import tool, AgentExecutor, create_react_agent
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI
from langchain import hub
# 1. 准备工具 (需要安装 langchain-community 和 tavily-python)
# 需要设置 TAVILY_API_KEY 环境变量
search = TavilySearchResults()
tools = [search]
# 2. 获取 Agent 的核心提示 (ReAct 范式)
prompt = hub.pull("hwchase17/react") # 从 LangChain Hub 拉取一个成熟的提示模板
# 3. 创建 Agent
llm = ChatOpenAI(model_name="gpt-4o", temperature=0)
agent = create_react_agent(llm, tools, prompt)
# 4. 创建 Agent 执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # verbose=True 可以看到思考过程
# 5. 运行
agent_executor.invoke({"input": "今天北京的天气怎么样?2024年诺贝尔物理学奖得主是谁?"})
当你运行这个 Agent,verbose=True
会打印出它的思考过程,你会看到它先决定使用搜索工具查天气,得到答案;然后再次决定使用搜索工具查诺贝尔奖得主,最后汇总答案。
二、如何开始你的第一个 LangChain 项目
- 明确目标: 你想做什么?一个简单的问答机器人?一个能帮你写代码的助手?还是一个能查询公司内部文档的系统?
- 选择模式:
- 如果任务流程是固定的(比如,总结文本、翻译),用 Chain。
- 如果需要与外部世界互动,且步骤不固定(比如,查询实时信息、执行代码),用 Agent。
- 如果需要基于你的私有数据进行问答,核心就是 RAG (Retrieval-Augmented Generation),它本身就是一个特殊的 Chain。
- 搭建你的链 (使用 LCEL):
- 输入: 你的 Chain 需要什么输入?(一个字符串?一个字典?)
- 提示: 设计你的
ChatPromptTemplate
。 - 模型: 选择合适的
ChatOpenAI
或其他模型。 - 检索 (如果需要): 准备你的文档,配置
DocumentLoader
,TextSplitter
,Embedding
,VectorStore
,并创建retriever
。 - 输出: 你希望得到什么格式的输出?字符串(
StrOutputParser
)还是 JSON(PydanticOutputParser
)? - 用
|
把它们串起来!
三、调试与观察:LangSmith
在开发过程中,你会想知道你的链或 Agent 内部到底发生了什么。LangSmith 是 LangChain 官方提供的调试、监控和评估平台。强烈建议你使用它!
你只需要设置几个环境变量,LangChain 就会自动将每次运行的详细步骤、输入输出、耗时等信息记录到 LangSmith 平台,可视化地展示给你,这对于调试复杂的链和 Agent 来说是无价的。
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..." # 在 LangSmith 网站上获取
# export LANGCHAIN_PROJECT="My First Project" # (可选) 指定项目名称