LangGraph核心组件
引言
LangGraph 是一个用于构建有状态的多参与者应用程序的库,利用大语言模型(LLM)创建代理和多代理工作流。与其他LLM框架相比,它提供了三个核心优势:循环性、可控性和持久性。本教程将详细介绍LangGraph的核心组件及其工作原理,帮助开发者理解如何构建复杂的代理系统。
1. LangGraph概述
LangGraph的灵感来源于Pregel和Apache Beam,其公共接口受NetworkX的启发。虽然LangGraph是由LangChain Inc开发的,但它可以在不使用LangChain的情况下独立使用。
1.1 主要特性
- 循环和分支:在应用程序中实现循环和条件逻辑
- 持久性:在图中的每一步自动保存状态,支持暂停和恢复图的执行
- 人机协作:中断图的执行以批准或编辑代理计划的下一个动作
- 流式支持:在每个节点产生输出时进行流式传输(包括令牌流式传输)
- 与LangChain的集成:与LangChain和LangSmith无缝集成(但不需要它们)
1.2 LangGraph平台
LangGraph平台是一个商业解决方案,用于将代理应用程序部署到生产环境,构建于开源的LangGraph框架之上。它由多个组件组成:
- LangGraph服务器(API)
- LangGraph SDK(API客户端)
- LangGraph CLI(构建服务器的命令行工具)
- LangGraph Studio(用户界面/调试器)
LangGraph平台解决了复杂部署中的常见问题:
- 多种流式模式,优化以满足各种应用需求
- 在后台异步运行代理
- 支持长时间运行的代理
- 处理双重文本(用户在代理回复之前收到两条消息的情况)
- 通过任务队列确保请求在高负载下也能始终如一地处理
2. Graph(图):LangGraph的核心
LangGraph的核心是将代理工作流建模为图。你可以使用三个关键组件来定义代理的行为:
2.1 核心组件概览
-
状态(State):一个共享的数据结构,表示应用程序的当前快照。它可以是任何Python类型,但通常是TypedDict或Pydantic BaseModel。
-
节点(Nodes):编码代理逻辑的Python函数。它们接收当前状态作为输入,执行一些计算,并返回一个更新的状态。
-
边(Edges):Python函数,根据当前状态确定要执行的下一个节点。它们可以是条件分支或固定转换。
通过组合节点和边,你可以创建复杂的循环工作流,随着时间的推移发展状态。简而言之:节点完成工作,边指示下一步要做什么。
2.2 消息传递机制
LangGraph的底层图算法使用消息传递来定义一个通用程序。当一个节点完成其操作时,它会沿着一条或多条边向其他节点发送消息。这些接收节点然后执行其函数,将结果消息传递给下一组节点,并且该过程继续进行。
受到Google的Pregel系统的启发,该程序以离散的"超级步骤"进行:
- 超级步骤可以被认为是图节点上的单个迭代
- 并行运行的节点属于同一个超级步骤,而顺序运行的节点则属于不同的超级步骤
- 在图执行开始时,所有节点都处于inactive状态
- 当节点在任何传入边上收到新消息(状态)时,它将变为active状态
- 活动节点运行其函数并响应更新
- 在每个超级步骤结束时,没有传入消息的节点通过将其标记为inactive来投票halt
- 当所有节点都处于inactive状态且没有消息在传输中时,图执行终止
3. StateGraph类
StateGraph类是使用的主要图类。它由用户定义的状态对象参数化。
3.1 基本定义
python
class StateGraph(Graph):
"""
一个图,其节点通过读取和写入共享状态进行通信。
每个节点的签名是 State -> Partial。
每个状态键可以选择性地使用一个reducer函数进行注释,
该函数将用于聚合从多个节点接收到的该键的值。
reducer函数的签名是 (Value, Value) -> Value。
"""
3.2 参数
- state_schema (Type[Any], 默认值:None):定义状态的模式类。
- config_schema (Optional[Type[Any]], 默认值:None):定义配置的模式类。用于在API中公开可配置参数。
3.3 示例
python
from typing import TypedDict, Annotated, Sequence
from langgraph.graph import StateGraph, START
# 定义状态类型
class AgentState(TypedDict):
messages: Sequence
next_steps: list[str]
# 创建图
graph = StateGraph(AgentState)
# 定义节点函数
def agent(state: AgentState):
messages = state["messages"]
# 处理消息并生成响应
response = "这是代理的响应"
return {"messages": messages + [{"role": "assistant", "content": response}]}
def tool_executor(state: AgentState):
# 执行工具并更新状态
return {"next_steps": ["完成任务"]}
# 添加节点
graph.add_node("agent", agent)
graph.add_node("tool_executor", tool_executor)
# 添加边
graph.add_edge(START, "agent")
graph.add_edge("agent", "tool_executor")
graph.add_edge("tool_executor", "agent")
# 编译图
app = graph.compile()
4. 编译你的图
要构建你的图,你首先定义状态,然后添加节点和边,最后进行编译。
4.1 编译过程
编译是一个非常简单的步骤。它对图的结构进行一些基本检查(没有孤立的节点等等)。它也是你可以指定运行时参数的地方,例如检查点和断点。
python
# 基本编译
app = graph.compile()
# 带有检查点的编译
from langgraph.checkpoint import MemoryCheckpointer
checkpointer = MemoryCheckpointer()
app = graph.compile(checkpointer=checkpointer)
# 带有断点的编译
app = graph.compile(interrupt_before=["critical_node"])
4.2 运行编译后的图
编译后的图可以像函数一样调用,接受初始状态并返回最终状态:
python
# 运行图
initial_state = {"messages": [{"role": "user", "content": "你好"}], "next_steps": []}
final_state = app.invoke(initial_state)
5. State(状态)
定义图时,你做的第一件事是定义图的状态。状态包含图的模式以及归约器函数,它们指定如何将更新应用于状态。
5.1 Schema(模式)
指定图模式的主要文档化方法是使用TypedDict:
python
from typing import TypedDict, List, Dict
class ChatMessage(TypedDict):
role: str
content: str
class AgentState(TypedDict):
messages: List[ChatMessage]
tools: List[Dict]
tool_results: List[Dict]
LangGraph也支持使用Pydantic BaseModel作为图状态,以添加默认值和其他数据验证:
python
from pydantic import BaseModel, Field
from typing import List, Dict
class ChatMessage(BaseModel):
role: str
content: str
class AgentState(BaseModel):
messages: List[ChatMessage] = Field(default_factory=list)
tools: List[Dict] = Field(default_factory=list)
tool_results: List[Dict] = Field(default_factory=list)
5.2 Reducers(归约器)
归约器是理解节点更新如何应用于状态的关键。状态中的每个键都有其自己的独立归约器函数。
5.2.1 Default Reducer(默认归约器)
如果未显式指定归约器函数,则假设对该键的所有更新都应该覆盖它:
python
from langgraph.graph import StateGraph
# 定义状态类型
class SimpleState(TypedDict):
foo: int
bar: List[str]
# 创建图
graph = StateGraph(SimpleState)
# 假设图的输入是 {"foo": 1, "bar": ["hi"]}
# 如果第一个节点返回 {"foo": 2}
# 状态变为 {"foo": 2, "bar": ["hi"]}
# 如果第二个节点返回 {"bar": ["bye"]}
# 状态变为 {"foo": 2, "bar": ["bye"]}
5.2.2 自定义归约器
你可以为状态中的特定键指定自定义归约器函数:
python
from typing import TypedDict, List, Annotated
from langgraph.graph import StateGraph
# 定义状态类型
class MessageState(TypedDict):
messages: List[dict]
counter: int
# 定义一个归约器函数,用于合并消息列表
def append_reducer(existing_messages: List[dict], new_messages: List[dict]) -> List[dict]:
return existing_messages + new_messages
# 定义一个归约器函数,用于累加计数器
def sum_reducer(a: int, b: int) -> int:
return a + b
# 创建图,并指定归约器
graph = StateGraph(
state_schema=MessageState,
channels={
"messages": append_reducer, # 使用append_reducer合并消息
"counter": sum_reducer # 使用sum_reducer累加计数器
}
)
6. Nodes(节点)
在LangGraph中,节点通常是Python函数(同步或async),其中第一个位置参数是状态,(可选地),第二个位置参数是"配置",包含可选的可配置参数。
6.1 添加节点
python
from langgraph.graph import StateGraph
# 定义状态类型
class AgentState(TypedDict):
messages: List[dict]
tools: List[dict]
# 创建图
graph = StateGraph(AgentState)
# 定义节点函数
def agent(state: AgentState):
messages = state["messages"]
# 处理消息
return {"messages": messages + [{"role": "assistant", "content": "响应"}]}
def tool_executor(state: AgentState):
tools = state["tools"]
# 执行工具
return {"tools": tools + [{"name": "executed_tool", "result": "结果"}]}
# 添加节点
graph.add_node("agent", agent)
graph.add_node("tool_executor", tool_executor)
6.2 特殊节点
6.2.1 START节点
START节点是一个特殊节点,它代表将用户输入发送到图的节点。引用此节点的主要目的是确定哪些节点应该首先被调用。
python
from langgraph.graph import StateGraph, START
graph = StateGraph(AgentState)
graph.add_node("agent", agent)
# 指定agent节点作为起始节点
graph.add_edge(START, "agent")
6.2.2 END节点
END节点是一个特殊节点,它代表一个终端节点。当你想要指定哪些边在完成操作后没有动作时,会引用此节点。
python
from langgraph.graph import StateGraph, END
graph = StateGraph(AgentState)
graph.add_node("agent", agent)
# 指定agent节点完成后结束
graph.add_edge("agent", END)
7. Edges(边)
边定义了逻辑如何路由以及图如何决定停止。这是你的代理如何工作以及不同节点如何相互通信的重要部分。
7.1 普通边
如果你总是想从节点A到节点B,你可以直接使用add_edge方法:
python
from langgraph.graph import StateGraph, START
graph = StateGraph(AgentState)
graph.add_node("agent", agent)
graph.add_node("tool_executor", tool_executor)
# 添加普通边
graph.add_edge(START, "agent")
graph.add_edge("agent", "tool_executor")
graph.add_edge("tool_executor", "agent")
7.2 条件边
如果你想选择性地路由到一个或多个边(或选择性地终止),你可以使用add_conditional_edges方法:
python
from langgraph.graph import StateGraph, START, END
graph = StateGraph(AgentState)
graph.add_node("agent", agent)
graph.add_node("tool_executor", tool_executor)
graph.add_node("final_response", final_response)
# 定义路由函数
def route_based_on_agent_decision(state: AgentState):
last_message = state["messages"][-1]
if "tool" in last_message:
return "tool_executor"
else:
return "final_response"
# 添加条件边
graph.add_edge(START, "agent")
graph.add_conditional_edges(
"agent",
route_based_on_agent_decision
)
graph.add_edge("tool_executor", "agent")
graph.add_edge("final_response", END)
7.3 入口点
入口点是图启动时运行的第一个节点。你可以从虚拟的START节点使用add_edge方法到要执行的第一个节点,以指定进入图的位置:
python
from langgraph.graph import StateGraph, START
graph = StateGraph(AgentState)
graph.add_node("agent", agent)
# 指定agent节点作为入口点
graph.add_edge(START, "agent")
7.4 条件入口点
条件入口点允许你根据自定义逻辑从不同的节点开始:
python
from langgraph.graph import StateGraph, START
graph = StateGraph(AgentState)
graph.add_node("text_agent", text_agent)
graph.add_node("image_agent", image_agent)
# 定义入口路由函数
def route_based_on_input_type(state: AgentState):
first_message = state["messages"][0]
if "image_url" in first_message:
return "image_agent"
else:
return "text_agent"
# 添加条件入口点
graph.add_conditional_edges(
START,
route_based_on_input_type
)
8. 实际应用示例
下面是一个完整的LangGraph应用示例,展示了如何构建一个简单的对话代理,该代理可以根据用户的查询执行不同的工具:
python
from typing import TypedDict, List, Annotated
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
# 定义状态类型
class AgentState(TypedDict):
messages: List[dict]
tools: List[dict]
tool_results: List[dict]
# 创建LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 定义节点函数
def agent(state: AgentState):
"""代理节点,决定下一步行动"""
messages = state["messages"]
# 构造提示
prompt = f"""
你是一个有用的助手。根据用户的查询,决定是否需要使用工具。
可用的工具:
- 计算器: 用于数学计算
- 搜索: 用于查找信息
如果需要使用工具,返回格式为: ACTION: 工具名称, 参数
如果不需要工具,直接回答用户问题。
"""
# 调用LLM
response = llm.invoke([
HumanMessage(content=prompt),
HumanMessage(content=messages[-1]["content"])
])
return {"messages": messages + [{"role": "assistant", "content": response.content}]}
def router(state: AgentState):
"""路由节点,决定下一步去哪个节点"""
last_message = state["messages"][-1]
content = last_message["content"]
if content.startswith("ACTION:"):
# 解析工具名称
parts = content.split(",", 1)
tool_name = parts[0].replace("ACTION:", "").strip()
if tool_name == "计算器":
return "calculator"
elif tool_name == "搜索":
return "search"
# 如果不需要工具,直接结束
return END
def calculator(state: AgentState):
"""计算器工具"""
last_message = state["messages"][-1]
content = last_message["content"]
# 提取参数
param = content.split(",", 1)[1].strip() if "," in content else ""
# 执行计算(简化版)
try:
result = str(eval(param))
except:
result = "计算错误"
return {"tool_results": state.get("tool_results", []) + [{"tool": "calculator", "result": result}]}
def search(state: AgentState):
"""搜索工具"""
last_message = state["messages"][-1]
content = last_message["content"]
# 提取参数
param = content.split(",", 1)[1].strip() if "," in content else ""
# 模拟搜索结果
result = f"关于'{param}'的搜索结果:这是一些模拟的搜索信息。"
return {"tool_results": state.get("tool_results", []) + [{"tool": "search", "result": result}]}
def tool_response_formatter(state: AgentState):
"""格式化工具响应"""
tool_results = state.get("tool_results", [])
if not tool_results:
return {}
last_result = tool_results[-1]
response = f"工具 '{last_result['tool']}' 的结果:\n{last_result['result']}"
return {"messages": state["messages"] + [{"role": "system", "content": response}]}
# 创建图
graph = StateGraph(AgentState)
# 添加节点
graph.add_node("agent", agent)
graph.add_node("calculator", calculator)
graph.add_node("search", search)
graph.add_node("tool_response_formatter", tool_response_formatter)
# 添加边
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", router)
graph.add_edge("calculator", "tool_response_formatter")
graph.add_edge("search", "tool_response_formatter")
graph.add_edge("tool_response_formatter", "agent")
# 编译图
app = graph.compile()
# 运行图
initial_state = {
"messages": [{"role": "user", "content": "计算123 + 456是多少?"}],
"tools": [],
"tool_results": []
}
final_state = app.invoke(initial_state)
9. 总结
LangGraph是一个强大的框架,用于构建有状态的多参与者应用程序。它的核心组件包括:
- StateGraph:主要的图类,由用户定义的状态对象参数化
- State(状态):包含图的模式和归约器函数,指定如何将更新应用于状态
- Nodes(节点):编码代理逻辑的Python函数,接收当前状态作为输入并返回更新的状态
- Edges(边):定义逻辑如何路由以及图如何决定停止的Python函数
通过组合这些组件,你可以创建复杂的循环工作流,构建强大的代理系统。LangGraph的设计使其特别适合构建需要状态管理、循环逻辑和人机协作的LLM应用。