LangGraph 的核心概念
LangGraph 是 LangChain 生态系统中的关键组件,专门用于构建有状态、多参与者的应用程序。它基于图论概念,允许开发者创建复杂的代理工作流,特别适合构建需要循环、分支和协作的智能系统。
1. 图结构的Agent编排
在 LangGraph 中,应用被建模为一个有向图(Directed Graph):
-
节点(Node):代表一个操作步骤,如调用 LLM、执行工具函数等。
-
边(Edge):定义节点之间的执行顺序和条件逻辑,支持循环和分支。
这种结构使得复杂的工作流(如多轮对话、任务分解与执行)可以被清晰地表达和管理。
2. 状态管理与持久化
LangGraph 内置了状态管理机制,允许在图的每一步之后自动保存状态。这支持:
-
错误恢复:在出现异常时从最近的检查点恢复执行。
-
人工干预:在关键节点暂停,等待人工审核或输入。
-
“时间旅行”:回溯到某个状态,修改后重新执行。
状态的持久化使得应用具有更高的可靠性和可调试性。
3. 人机协作(Human-in-the-Loop)
LangGraph 支持在执行过程中引入人工决策,特别适用于需要专家判断的场景,如医疗诊断、金融分析等。系统可以在某些节点暂停,等待人工输入后再继续执行。
4. 流式输出与实时反馈
通过支持逐个令牌的流式传输,LangGraph 提供了更好的用户体验。用户可以实时看到代理的思考过程和中间结果,增强了交互性和透明度。
核心组件
组件 | 描述 |
---|---|
State | 工作流中传递的共享数据结构(TypedDict) |
Nodes | 执行特定任务的函数(LLM调用、工具执行等) |
Edges | 定义节点间转移关系的条件 |
Condition | 决定工作流走向的分支条件 |
Checkpoint | 保存工作流状态,支持暂停/恢复 |
Channels | 节点间通信的数据管道 |
简单的一个聊天智能体:
from typing import TypedDict, Annotated
from langgraph.constants import START, END
from langgraph.graph import add_messages, StateGraph
from model.deepseek import deepseek_llm
# 聊天状态
#add_messages 是一个特殊的 ** reducer 函数 **,用于在状态图(StateGraph)中管理消息历史。
#它告诉 LangChain:当有新的消息加入时,应该以何种方式更新状态中的消息列表。
#默认行为是将新消息追加到已有消息列表中。
class ChatSate(TypedDict):
messages: Annotated[list, add_messages]
#定义一个流程图
graph = StateGraph(ChatSate)
#定义一个节点
def chatbot(state: ChatSate) -> ChatSate:
return {"messages": deepseek_llm.invoke(state["messages"])}
#添加节点
graph.add_node("chatbot", chatbot)
#添加边
graph.add_edge(START, "chatbot")
graph.add_edge("chatbot", END)
graph = graph.compile()
#生成流程图
#image = graph.get_graph().draw_mermaid_png()
#with open("graph1.png", "wb") as f:
# f.write(image)
def loop_graph_invoke(user_input: str):
for chunk in graph.stream({'messages': [('user', user_input)]}):
for value in chunk.values():
print('AI:', value['messages'].content)
if __name__ == '__main__':
while True:
user_input = input('User:')
if user_input.lower() in ['q', 'exit', 'bye', 'quit']:
print('AI:', 'Bye')
break
else:
loop_graph_invoke(user_input)
生成图:
测试:
START 节点
START 节点是一个特殊节点,它代表将用户输入发送到图形的节点。引用此节点的主要目的是确定哪些节点应该首先被调用。
from langgraph.graph import START
graph.add_edge(START, "my_node")
graph.add_edge("my_node", "other_node")
END 节点
END 节点是一个特殊节点,它代表一个终端节点。当您想要指定哪些边在完成操作后没有动作时,会引用此节点。一个流程通常只有一个开始节点和结束节点。
from langgraph.graph import END
graph.add_edge("other_node", END)
边(Edges)
边定义了逻辑如何路由以及图形如何决定停止。这是您的代理如何工作以及不同节点如何相互通信的重要部分。有一些关键类型的边。
-
普通边:直接从一个节点到下一个节点。
-
条件边:调用一个函数来确定下一个要转到的节点。
-
入口点:用户输入到达时首先调用的节点。
-
条件入口点:调用一个函数来确定用户输入到达时首先调用的节点。
一个节点可以有多个输出边。如果一个节点有多个输出边,则所有这些目标节点将在下一个超级步骤中并行执行。
普通边
如果您总是想从节点 A 到节点 B,您可以直接使用add_edge方法。
graph.add_edge("node_a", "node_b")
条件边
如果您想选择性地路由到一个或多个边(或选择性地终止),您可以使用add_conditional_edges方法。此方法接受节点的名称和一个“路由函数”,该函数将在该节点执行后被调用。
graph.add_conditional_edges("node_a", routing_function)
类似于节点, routing_function 接受图形的当前 state 并返回一个值。
默认情况下,返回值 routing_function 用作要将状态发送到下一个节点的节点名称(或节点列表)。
所有这些节点将在下一个超级步骤中并行运行。
您可以选择提供一个字典,该字典将 routing_function 的输出映射到下一个节点的名称。
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False:"node_c"})
入口点
入口点是图形启动时运行的第一个节点。您可以从虚拟的 START 节点使用 add_edge 方法到要执行的第一 个节点,以指定进入图形的位置。
from langgraph.graph import START
graph.add_edge(START, "my_node")
条件入口点
条件入口点允许您根据自定义逻辑从不同的节点开始。您可以从虚拟的 START 节点使用add_conditional_edges 来实现这一点。
from langgraph.graph import START
graph.add_conditional_edges(START, routing_function)
您可以选择提供一个字典,该字典将 routing_function 的输出映射到下一个节点的名称。
graph.add_conditional_edges(START, routing_my,{True: "my_node", False: "other_node"})
对刚才的聊天智能体进一步改进,加入一个网络搜索工具,
import os
from typing import Annotated, TypedDict
from langchain_tavily import TavilySearch
from langgraph.checkpoint.memory import MemorySaver
from langgraph.constants import START, END
from langgraph.graph import add_messages, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition
from model.deepseek import deepseek_llm
class ChatSate(TypedDict):
# messages:状态中保存数据的key
messages: Annotated[list, add_messages]
graph = StateGraph(ChatSate)
# 定义一个互联网搜索工具
os.environ["TAVILY_API_KEY"] = ""
search_tool = TavilySearch(max_result=5)
runnable = deepseek_llm.bind_tools([search_tool])
# 定义第一个节点
def chatbot(state: ChatSate) -> ChatSate:
return {"messages": runnable.invoke(state["messages"])}
# 定义第二个节点工具节点
too_node = ToolNode([search_tool])
# 添加边
graph.add_node("agent", chatbot)
graph.add_node("tools", too_node)
# 加入条件边
graph.add_conditional_edges(
"agent",
#条件变量,根据智能体的回答决定是否调用工具
tools_condition
)
graph.add_edge("tools", "agent") # 流程从tools 到 chatbot
graph.add_edge(START, "agent") # 流程从start 到 chatbot
graph.add_edge("agent", END) # 流程从chatbot 到 END
graph.set_entry_point("agent") # 设置入口节点
#保存对话记录到内存中
memory_checkpointer = MemorySaver()
graph = graph.compile(checkpointer=memory_checkpointer)
# png = graph.get_graph().draw_mermaid_png()
# with open("graph2.png", "wb") as f:
# f.write(png)
def loop_graph_invoke(user_input: str):
#加入会话配置
result = graph.invoke({'messages': [('user', user_input)]}, config={"configurable": {"thread_id": "1234"}})
if type(result['messages']) is list:
print('AI:', result['messages'][-1].content)
else:
print('AI:', result['messages'].content)
while True:
user_input = input('User:')
if user_input.lower() in ['q', 'exit', 'bye', 'quit']:
print('AI:', 'Bye')
break
else:
loop_graph_invoke(user_input)
生成的流程图