LangChain 框架深度解析:从 LCEL 到 Agent 架构的核心原理
摘要
本文深入剖析 LangChain 框架的核心架构与实现原理,涵盖 LCEL(LangChain Expression Language)的声明式链式组合机制、Runnable 统一接口设计、Agent 工作流模式(ReAct、Tool Calling),以及 LangGraph 状态图编排系统。通过源码级分析揭示其设计思想,帮助开发者掌握构建生产级 AI 应用架构的关键技术。
引言
LangChain 自 2022 年发布以来,已成为构建 LLM 应用最广泛使用的开源框架之一。2025 年 LangChain 1.0 和 LangGraph 1.0 正式发布,标志着框架进入生产就绪阶段。
核心问题:
- LangChain 如何实现模块化组件的统一编排?
- LCEL 的声明式语法背后隐藏着什么设计模式?
- Agent 如何实现自主决策与工具调用的闭环?
- LangChain 与 LangGraph 的定位有何不同?
文章结构:首先解析核心组件与 Runnable 接口,深入 LCEL 组合机制,然后剖析 Agent 架构与工作流模式,最后介绍 LangGraph 的状态编排系统。
核心组件架构
组件层次结构
LangChain 采用分层架构设计:
| 层次 | 组件 | 职责 |
|---|---|---|
| 编排层 | LCEL / LangGraph | 组合、流程控制 |
| 逻辑层 | Agents | 决策、工具调用 |
| 模型层 | Models | LLM 接口封装 |
| 数据层 | Memory / Retrievers | 状态持久化、数据检索 |
| 工具层 | Tools | 外部能力扩展 |
Runnable 统一接口
Runnable 是 LangChain 的基石接口,定义了组件执行的标准协议:
python
class Runnable(Generic[Input, Output]):
# 核心方法
def invoke(self, input: Input, config: RunnableConfig) -> Output
def batch(self, inputs: List[Input], config: RunnableConfig) -> List[Output]
def stream(self, input: Input, config: RunnableConfig) -> Iterator[Output]
# 异步方法
async def ainvoke(self, input: Input, config: RunnableConfig) -> Output
async def abatch(self, inputs: List[Input]) -> List[Output]
async def astream(self, input: Input) -> AsyncIterator[Output]
# 组合方法
def pipe(self, other: Runnable) -> RunnableSequence
def bind(self, **kwargs) -> Runnable
def with_retry(self, retry_policy) -> Runnable
def with_fallbacks(self, fallbacks: List[Runnable]) -> Runnable
设计思想:
- 统一协议:所有组件(PromptTemplate、LLM、Retriever、Tool)都实现 Runnable,实现"可插拔"架构
- 多态执行:invoke(单次)、batch(批量)、stream(流式)三种执行模式
- 异步原生:所有方法都有 async 版本,支持高并发场景
- 组合优先:pipe 方法支持链式组合,bind 方法支持参数绑定
RunnableSequence 与 RunnableParallel
Runnable 的组合通过两个核心原语实现:
RunnableSequence(顺序执行):
python
# 使用 | 运算符构建顺序链
sequence = RunnableLambda(lambda x: x + 1) | RunnableLambda(lambda x: x * 2)
sequence.invoke(1) # 输出: 4
sequence.batch([1, 2, 3]) # 输出: [4, 6, 8]
RunnableParallel(并行执行):
python
# 使用字典构建并行分支
sequence = RunnableLambda(lambda x: x + 1) | {
"mul_2": RunnableLambda(lambda x: x * 2),
"mul_5": RunnableLambda(lambda x: x * 5),
}
sequence.invoke(1) # 输出: {'mul_2': 4, 'mul_5': 10}
关键要点
- Runnable 接口实现了"一切皆可组合"的设计哲学
- 组合后的 Chain 自动继承 invoke/batch/stream/async 能力
- | 运算符语法糖使链式组合直观简洁
LCEL:声明式链式编排
LCEL 语法解析
LangChain Expression Language (LCEL) 是 Runnable 的声明式 DSL:
python
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# 定义组件
prompt = PromptTemplate.from_template("Question: {question}
Answer: Let's think step by step.")
llm = ChatOpenAI(model="gpt-4", temperature=0)
parser = StrOutputParser()
# 使用 LCEL 组合
chain = prompt | llm | parser
# 执行
chain.invoke({"question": "How much is 2+2?"})
LCEL 内部实现机制
当执行 prompt | llm | parser 时,实际发生:
- 第一次 pipe :
prompt.pipe(llm)→ 创建RunnableSequence([prompt, llm]) - 第二次 pipe :
sequence.pipe(parser)→ 创建RunnableSequence([prompt, llm, parser]) - invoke 执行:依次调用各组件,前一组件输出作为后一组件输入
流式执行优化:
LCEL 的 stream 方法不是简单迭代,而是实现管道式流式传输:
python
# 流式输出:每个组件产生的中间结果立即传递给下游
for chunk in chain.stream({"question": "What is AI?"}):
print(chunk, end="", flush=True)
内部实现使用 RunnableSequence.stream(),每个组件的 stream() 方法产生迭代器,上游迭代器元素直接传递给下游。
LCEL 高级组合模式
1. 参数绑定(bind):
python
# 绑定固定参数
llm_with_stop = llm.bind(stop=["
"])
chain = prompt | llm_with_stop | parser
2. 条件分支(RunnableBranch):
python
from langchain_core.runnables import RunnableBranch
branch = RunnableBranch(
(lambda x: x["type"] == "math", math_chain),
(lambda x: x["type"] == "text", text_chain),
default_chain
)
3. 回退机制(with_fallbacks):
python
robust_chain = (prompt | llm | parser).with_fallbacks([
prompt | backup_llm | parser
])
4. 重试策略(with_retry):
python
retry_chain = chain.with_retry(
stop_after_attempt=3,
wait_exponential_multiplier=1000
)
关键要点
- LCEL 实现了声明式、可组合的链式编排
- | 运算符语法糖背后是 RunnableSequence 组合
- 组合后的 Chain 自动支持流式、批量、异步执行
Agent 架构深度解析
Agent 核心概念
Agent 是具有自主决策能力的系统,核心特征:
| 特征 | 描述 |
|---|---|
| 自主决策 | LLM 决定执行哪些动作、顺序 |
| 工具调用 | 通过 Tool 接口调用外部能力 |
| 状态感知 | 记忆对话历史、中间结果 |
| 反闭环 | 观察工具返回结果,调整后续决策 |
Agent 工作流模式
1. ReAct 模式(Reasoning + Acting)
ReAct 是经典的 Agent 模式,遵循"思考-行动-观察"循环:
Thought: 我需要查询天气信息
Action: search_weather("北京")
Observation: 北京今天晴,气温 25°C
Thought: 我已获得天气信息,可以回答用户
Answer: 北京今天天气晴朗,气温 25°C
ReAct Agent 实现:
python
from langchain.agents import load_tools, initialize_agent, AgentType
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4", temperature=0)
tools = load_tools(["serpapi", "llm-math"], llm=llm)
agent = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
agent.run("北京今天天气如何?计算25的平方")
2. Tool Calling 模式
现代 LLM(如 GPT-4、Claude)原生支持 Function Calling,Agent 可直接调用:
python
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.tools import Tool
# 定义工具
def get_weather(city: str) -> str:
return f"{city}今天晴,气温25°C"
tools = [
Tool(name="get_weather", func=get_weather,
description="获取指定城市的天气信息")
]
# 创建 Agent
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_executor.invoke({"input": "北京天气如何?"})
Agent 执行循环
Agent 的核心是执行循环(Agent Loop):
┌─────────────┐
│ 用户输入 │
└──────┬──────┘
↓
┌─────────────┐
│ LLM 决策 │ ← 分析输入,决定是否调用工具
└──────┬──────┘
↓
有工具调用?
├─ Yes ──→ ┌─────────────┐
│ │ 执行工具 │
│ └──────┬──────┘
│ ↓
│ ┌─────────────┐
│ │ 观察结果 │ → Tool Message
│ └──────┬──────┘
│ ↓
│ ┌─────────────┐
│ │ 继续决策 │ ← 将观察结果加入对话
│ └──────┬──────┘
│ │
└─────────────────┘
└─ No ──→ ┌─────────────┐
│ 返回答案 │
└──────┬──────┘
↓
┌─────────────┐
│ 执行结束 │
└─────────────┘
Tool 接口设计
Tool 是 Agent 与外部世界交互的标准接口:
python
from langchain.tools import tool
@tool
def calculate(expression: str) -> float:
"""计算数学表达式。
Args:
expression: 数学表达式,如 '2+2' 或 'sqrt(16)'
"""
return eval(expression)
# Tool 的核心属性
# - name: 工具名称
# - description: 工具描述(LLM 用于决策)
# - args_schema: 参数 Schema(JSON Schema 格式)
关键要点
- Agent 通过 LLM 自主决定执行路径
- ReAct 模式:显式的思考-行动-观察循环
- Tool Calling 模式:利用 LLM 原生 Function Calling
- 执行循环:LLM 决策 → 工具执行 → 观察反馈 → 继续决策
LangGraph:状态图编排系统
LangChain vs LangGraph 定位
| 特性 | LangChain | LangGraph |
|---|---|---|
| 定位 | 高层抽象框架 | 低层编排引擎 |
| 组合方式 | 链式(LCEL) | 图式(StateGraph) |
| 控制流 | 线性为主 | 支持复杂循环、分支 |
| 状态管理 | 隐式(Memory) | 显式(TypedDict State) |
| 持久化 | 需额外配置 | 内置 Checkpointer |
| 适用场景 | 快速原型、简单流程 | 生产级复杂 Agent |
LangGraph 核心概念
LangGraph 将 Agent 工作流建模为状态图:
python
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict, Annotated
import operator
# 1. 定义状态
class AgentState(TypedDict):
messages: Annotated[list, operator.add] # 消息累加
llm_calls: int # LLM 调用次数
# 2. 定义节点(Node)
def llm_node(state: AgentState) -> dict:
"""LLM 决策节点"""
response = llm.invoke(state["messages"])
return {
"messages": [response],
"llm_calls": state.get("llm_calls", 0) + 1
}
def tool_node(state: AgentState) -> dict:
"""工具执行节点"""
results = []
for tool_call in state["messages"][-1].tool_calls:
result = tools[tool_call["name"]].invoke(tool_call["args"])
results.append(ToolMessage(content=result, tool_call_id=tool_call["id"]))
return {"messages": results}
# 3. 定义边(Edge)
def should_continue(state: AgentState) -> str:
"""条件边:决定下一节点"""
if state["messages"][-1].tool_calls:
return "tool_node"
return END
# 4. 构建图
graph = StateGraph(AgentState)
graph.add_node("llm", llm_node)
graph.add_node("tool", tool_node)
graph.add_edge(START, "llm")
graph.add_conditional_edges("llm", should_continue, ["tool", END])
graph.add_edge("tool", "llm") # 工具执行后返回 LLM
# 5. 编译并执行
agent = graph.compile()
result = agent.invoke({"messages": [HumanMessage("计算 25 的平方")]})
LangGraph 架构要素
1. State(状态):
python
class State(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
# operator.add 表示新消息累加到列表
2. Node(节点):
- 函数接收当前 State,返回 State 更新(dict)
- 是 Agent 逻辑的封装单元
3. Edge(边):
- 普通边 :固定流转路径
graph.add_edge("A", "B") - 条件边 :根据状态动态路由
graph.add_conditional_edges("A", router, ["B", "C", END])
LangGraph 执行模型
LangGraph 的执行模型:
START → llm_node → [条件判断]
├─ tool_calls 存在 → tool_node → llm_node (循环)
└─ 无 tool_calls → END
关键特性:
- 循环支持 :
tool_node → llm_node形成闭环,实现 Agent 的多轮决策 - 状态持久化:Checkpointer 可保存执行状态,支持暂停恢复
- Human-in-the-Loop:可在任意节点插入人工干预
关键要点
- LangGraph 使用显式 State + Node + Edge 模型
- 条件边实现复杂路由逻辑
- 循环边支持 Agent 的多轮决策闭环
Memory 与 Retrieval 系统
Memory 架构
LangChain Memory 系统管理对话上下文:
python
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
# Memory 自动管理对话历史
chain = ConversationChain(llm=llm, memory=memory)
Memory 类型对比:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| ConversationBufferMemory | 保存完整对话 | 短对话 |
| ConversationBufferWindowMemory | 保留最近 N 轮 | 控制长度 |
| ConversationSummaryMemory | LLM 摘要历史 | 长对话 |
| VectorStoreMemory | 向量检索相关历史 | 大规模历史 |
Retriever 接口
Retriever 是 Runnable 的特化实现,用于数据检索:
python
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
vectorstore = FAISS.from_texts(documents, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# Retriever 本身是 Runnable
docs = retriever.invoke("query") # 返回相关文档
LCEL 中集成 Retriever:
python
from langchain_core.runnables import RunnableParallel
# RAG Chain 示例
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| parser
)
实战案例:构建完整 Agent
场景描述
构建一个具备计算、搜索、记忆能力的多功能 Agent。
解决方案
python
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
# 1. 定义工具
@tool
def calculator(expression: str) -> float:
"""计算数学表达式"""
return eval(expression)
@tool
def search(query: str) -> str:
"""搜索信息"""
# 实际实现调用搜索 API
return f"搜索结果: {query}"
# 2. 配置 LLM
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)
tools = [calculator, search]
llm_with_tools = llm.bind_tools(tools)
# 3. 使用 LangGraph 构建持久化 Agent
from langgraph.graph import MessagesState
def agent_node(state: MessagesState):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def tool_node(state: MessagesState):
results = []
for tc in state["messages"][-1].tool_calls:
tool = {t.name: t for t in tools}[tc["name"]]
results.append(ToolMessage(content=str(tool.invoke(tc["args"])), tool_call_id=tc["id"]))
return {"messages": results}
def should_continue(state):
if state["messages"][-1].tool_calls:
return "tools"
return END
# 构建图
builder = StateGraph(MessagesState)
builder.add_node("agent", agent_node)
builder.add_node("tools", tool_node)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue)
builder.add_edge("tools", "agent")
# 添加持久化
checkpointer = MemorySaver()
agent = builder.compile(checkpointer=checkpointer)
# 执行(带线程 ID)
result = agent.invoke(
{"messages": [HumanMessage("计算 15 * 3,然后搜索 Python 教程")]},
config={"configurable": {"thread_id": "session-1"}}
)
效果评估
- 工具调用正确性:Agent 依次调用 calculator 和 search
- 状态持久化:可通过 thread_id 恢复对话
- 流式输出:支持实时输出中间结果
总结
核心要点回顾
- Runnable 接口:LangChain 的统一执行协议,实现 invoke/batch/stream/async 多态执行
- LCEL 语法:声明式链式组合,| 运算符语法糖,自动继承执行能力
- Agent 架构:自主决策 + 工具调用 + 反馈闭环,ReAct 与 Tool Calling 两种模式
- LangGraph:状态图编排系统,显式 State + Node + Edge,支持循环、持久化
最佳实践建议
- 简单链优先 LCEL:线性流程使用 | 运算符快速组合
- 复杂 Agent 用 LangGraph:多轮决策、循环、条件路由用 StateGraph
- 工具定义规范:提供清晰的 description 和 args_schema
- 流式优先:长输出场景使用 stream 方法提升用户体验
- 持久化配置:生产环境使用 LangGraph Checkpointer 或外部存储