AI创想
标题:
LangGraph入门知识学习笔记
[打印本页]
作者:
米落枫
时间:
3 小时前
标题:
LangGraph入门知识学习笔记
作者:CSDN博客
LangGraph概述
是什么:基于 LangChain 构建的、面向智能体多轮交互 / 状态持久化 / 分支并行执行的
图结构工作流框架,即LangGraph = LangChain + 图编排 + 状态机
LangGraph 是LangChain 生态的一部分,专门用于构建基于大模型(LLM)的复杂、有状态、多智能体应用的框架。核心思想是将应用的工作流程抽象为一个有向图结构,通过节点和边来定义任务的执行步骤和逻辑流,它是对LangChain核心思想的延伸和升级,它把基础单元从”链”换成了”图(Graph)”。相比传统的线性执行模式,LangGraph 支持条件分支、循环、并行等复杂控制流,能够实现状态持久化、断点续跑、时间旅行、人机协作等高级功能,并提供了多智能体协作、层级架构等多种架构模式。
能用来做什么:对于一步步的线性任务适合langchain,但是对于非一步步线性任务的执行,langgraph比较适合
Chain太流水线,无法优雅地处理循环和条件分支,不适合复杂任务。
Agent太自由,像个黑箱,难以控制、调试和保证稳定性。
LangGraph 提供了强大的状态管理机制,允许 Agent 在
不同节点之间传递和维护信息
,从而实现长期的记忆和多轮对话能力。 通过定义
节点和边
,可以精确控制 Agent 的执行逻辑,包括条件分支、循环和并行执行等
LangGraph 能够无缝集成各种外部工具(如搜索引擎、数据库、API 等),让 Agent 能够获取实时信息、执行特定操作,极大地扩展了 LLM 的能力边界。
图结构使得 Agent 的运行路径清晰可见,便于理解 Agent 的决策过程,并在出现问题时进行快速定位和调试。
模块化与可复用性。每个节点都可以是一个独立的、可复用的组件,维护性高且易于扩展。通过子图机制,复杂的工作流可以被分解为多个可独立开发和测试的模块,提高了开发和测试效率
(, 下载次数: 0)
上传
点击文件名下载附件
图的构建流程
初始化一个StateGraph实例
加节点
定义边,将所有的节点连接起来
设置特殊节点,入口和出口(可选)
编译图
执行工作流
总结
LangGraph的灵魂:State(状态) + Nodes(节点) + Edges(边) + Graph(图)
入门案例
"""
LangGraph 简单案例HelloWorld:
构建一个最小的有向图,流程是:START → 模型节点 → END
LangGraph的灵魂:State(状态) + Nodes(节点) + Edges(边) + Graph(图)
"""
import uuid
from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
import os
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
# ========== 1. 定义状态(State) ==========
# 存储对话消息
class AtguiguState(TypedDict):
# messages 是一个消息列表,Annotated + add_messages 表示支持自动追加消息
messages: Annotated[List, add_messages]
# ========== 2. 定义大模型 ==========
llm = init_chat_model(
model="qwen-plus",
model_provider="openai",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# ========== 3. 定义节点函数 ==========
# 节点:调用大模型,并把回复加入到 state["messages"] 里
def model_node(state: AtguiguState):
reply = llm.invoke(state["messages"]) # 输入历史消息,调用模型
return {"messages": [reply]} # 返回新消息,自动加到 state
# ========== 4. 构建图结构 ==========
graph = StateGraph(AtguiguState) # 初始化图,指定 State 类型
graph.add_node("model", model_node) # 添加一个节点,名字叫 "model"
graph.add_edge(START, "model") # 从 START 到 "model"
graph.add_edge("model", END) # 从 "model" 到 END
# 打印图的边和节点信息
#print(graph.edges)
print()
#print(graph.nodes)
# ========== 5. 编译==========
app = graph.compile()
# ========== 6. 运行 ==========
#result = app.invoke({"messages": [HumanMessage(content="请用一句话解释什么是 LangGraph。")]})
result = app.invoke({"messages": "请用一句话解释什么是 LangGraph。"})
# 打印模型的最后一条回复
print("模型回答:", result["messages"][-1].content)
print()
# =========================
#1. 打印图的ascii可视化结构
print(app.get_graph().print_ascii())
print("="*50)
#2. 打印图的Mermaid代码可视化结构并通过https://www.processon.com/mermaid编辑器查看
print(app.get_graph().draw_mermaid())
print("="*50)
#3. 生成 PNG并写入文件
# png_bytes = app.get_graph().draw_mermaid_png()
# output_path = "langgraph" + str(uuid.uuid4())[:8] + ".png"
# with open(output_path, "wb") as f:
# f.write(png_bytes)
# print(f"图片已生成:{output_path}")
复制代码
Graph API
Graph API之Graph(图)
图是一种由节点和边组成的用于描述节点之间关系的数据结构,分为无向图和有向图,有向图是带有方向的图。LangGraph通过有向图定义AI工作流中的执行步骤和执行顺序,从而实现复杂、有状态、可循环的应用程序逻辑。
(, 下载次数: 0)
上传
点击文件名下载附件
Graph API之State(状态)
在LangGraph中,State是一个贯穿整个工作流执行过程中的共享数据的结构,代表当前快照,它存储了从工作流开始到结束的所有必要的信息(历史对话、检索到的文档、工具执行结果等),
在各个节点中共享,且每个节点都可以修改。
状态包含两部分:
一是图的模式(schema),
二是“规约函数”(reducer functions);后者指明如何把更新应用到状态上。
Schema
是langgraph应用的核心数据结构,定义了应用的所有数据,在所有节点穿通,并且被持久化报错保存
构成三要素
state_schema:图的完整内部状态,包含了所有节点可能读写的字段,必须指定,不能为空
input_schema:定义图接受什么输入,是 state_schema 的子集,可选参数,如果不指定,默认等于 state_schema,限制图的输入接口,只能传入这些字段
output_schema:定义图返回什么输出,是 state_schema 的子集,可选参数,如果不指定,默认等于 state_schema,限制图的输出接口,只返回这些字段
注:State可以是TypedDict类型,也可以是pydantic中的BaseModel类型
Reducers
是什么:规约函数决定了节点产生的更新如何作用到 State。State 中的每个字段都拥有自己的独立规约函数。
如果未显式指定,则默认所有对该字段的更新都会直接覆盖旧值。
规约函数有多种类型,首先是默认类型,简单来说就是
状态合并策略(Reducers)
Reducer常用函数
default:未指定Reducer时使用覆盖更新
add_messages:用于消息列表追加
operator.add:将元素追加到现有元素中,支持列表、字符串、数值类型的追加
operator.mul:用于数值相乘(此处因为设计策略原因,会乘以一个默认值0.0导致结果一直为0,采用自定义Reducer可以解决次问题)
自定义Reducer:支持用户自定义合并逻辑
Graph API之Node(节点)
是什么
在LangGraph中,节点(Node)就是是Python函数(可以是同步的,也可以是异步的),它们接受以下参数:
Ø state:图的状态
Ø config:一个RunnableConfig对象,包含诸如thread_id之类的配置信息以及诸如tags之类的跟踪信息
Ø runtime:一个Runtime对象,包含运行时context以及其他信息,如store和stream_writer
定义好node函数后,使用add_node方法将这些节点添加到图中。如果在向图中添加节点时未指定名称,系统会为其分配一个与函数名相同的默认名称。
简单来说:Node是LangGraph中的一个基本处理单元,代表工作流中的一个操作步骤,
可以是一个Agent、调用大模型、工具或一个函数(说白了就是绑定一个python函数,具体逻辑可以干任何事情)
Node的设计原则:
单一职责原则:每个节点应该只负责一项职责,避免功能过于复杂
无状态设计:节点本身不应该保存状态,所有数据都通过输入状态传递
幂等性:相同的输入应该产生相同的输出,确保可重试性
可测试性:节点逻辑应该易于单元测试
节点缓存Node Caching
是什么
LangGraph支持基于节点输入对任务/节点进行缓存。使用缓存的方法如下:
编译图(或指定入口点)时指定缓存。
为节点指定缓存策略。每个缓存策略支持:
Ø key_func用于根据节点的输入生成缓存键。
Ø ttl,即缓存的生存时间(以秒为单位)。如果未指定,缓存将永不过期。
特性
缓存键与命中:
当一个节点开始执行时,系统会使用其配置的 key_func 根据当前节点的输入数据生成一个唯一的键。LangGraph 会检查缓存中是否存在这个键。
如果存在(缓存命中),则直接返回之前存储的结果,跳过该节点的实际执行。
如果不存在(缓存未命中),则正常执行节点函数,并将结果与缓存键关联后存入缓存。
缓存有效期:
ttl 参数能控制缓存的有效期。
错误处理和重试机制
LangGraph还提供了错误处理和重试机制来指定重试次数、重试间隔、重试异常等,用于保证系统的可靠性
为节点添加重试策略,需要在add_node中设置retry_policy参数。retry_policy参数接受一个RetryPolicy命名元组对象。默认情况下,retry_on参数使用default_retry_on函数,该函数会在遇到任何异常时重试
(, 下载次数: 0)
上传
点击文件名下载附件
Graph API之Edge(边)
是什么
Edge定义了节点之间的连接和执行顺序,以及不同节点之间是如何通讯的,
一个节点可以有多个出边(指向多个节点),多个节点也可以指向同一个节点(Map-Reduce)
代码说明
Edge定义了节点之间的连接和执行顺序,以及不同节点之间是如何通讯的,
一个节点可以有多个出边(指向多个节点),多个节点也可以指向同一个节点(Map-Reduce)
如下是添加边的代码:
(, 下载次数: 0)
上传
点击文件名下载附件
关键类型
(, 下载次数: 0)
上传
点击文件名下载附件
Graph API之Send/Command/Runtime context
概述
Send和Command是两种用于实现高级工作流控制的核心机制,用于支持动态地决定下一步执行哪个节点
Send
是什么:动态创建多个执行分支,实现并行处理,每个Send对象都指定了一个执行目标节点和传递给该节点的参数,
LangGraph会并行执行所有的这些任务,常用在Map-Reduce的场景,并行执行多个子节点并最终汇总到一个总节点。
(, 下载次数: 0)
上传
点击文件名下载附件
核心思想:拆分任务 →并行执行 → 统一汇总结果
通俗举例:统计全国所有人的年龄分布,Map 阶段就是「把全国按省份拆分成 34 个分片,
每个省份独立统计本省的年龄→人数(KV 对:20 岁→100 万,21 岁→98 万)」
Send 接受两个参数:第一个是节点的名称,第二个是要传递给该节点的状态
代码案例
"""
SendDemo.py
LangGraph Map-Reduce 模式演示
通过使用 Send 对象,LangGraph 提供了一种优雅的方式来实现这种动态图结构,
使得我们可以根据运行时状态来决定执行路径。
解释:
(1)首先执行 generate_subjects主题列表节点,生成主题列表:['猫', '狗', '程序员']
(2)然后通过条件边函数 map_subjects_to_jokes 为每个主题创建一个 Send 对象
(3)make_joke 节点被并行执行3次,每次处理一个主题
(4)最终将所有生成的笑话合并到一个列表中
这种模式非常适合处理动态数量的任务
"""
from typing import Annotated, List, Sequence
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send
# 定义状态
class OverallState(TypedDict):
subjects: List[str]
jokes: Annotated[List[str], lambda x, y: x + y] # 使用列表合并的方式
# 第一个节点:生成需要处理的主题列表
def generate_subjects(state: OverallState) -> dict:
"""生成需要处理的主题列表"""
print("执行节点: generate_subjects")
subjects = ["猫", "狗", "程序员"]
print(f"生成主题列表: {subjects}")
return {"subjects": subjects}
# Map节点:为每个主题生成笑话
def make_joke(state: OverallState) -> dict:
"""为单个主题生成笑话"""
subject = state.get("subject", "未知")
print(f"执行节点: make_joke,处理主题: {subject}")
# 根据主题生成相应笑话
jokes_map = {
"猫": "为什么猫不喜欢在线购物?因为它们更喜欢实体店!",
"狗": "为什么狗不喜欢计算机?因为它们害怕被鼠标咬!",
"程序员": "为什么程序员喜欢洗衣服?因为他们在寻找bugs!",
"未知": "这是一个关于未知主题的神秘笑话。"
}
joke = jokes_map.get(subject, f"这是一个关于{subject}的即兴笑话。")
print(f"生成笑话: {joke}")
return {"jokes": [joke]}
# 条件边函数:根据主题列表生成Send对象列表
def map_subjects_to_jokes(state: OverallState) -> Sequence[Send]:
"""将主题列表映射到joke生成任务"""
print("执行条件边函数: map_subjects_to_jokes")
subjects = state["subjects"]
print(f"映射主题到joke任务: {subjects}")
# 为每个主题创建一个Send对象,指向make_joke节点
# 每个Send对象包含节点名称和传递给该节点的状态
send_list = [Send("make_joke", {"subject": subject}) for subject in subjects]
print(f"生成Send对象列表: {send_list}")
return send_list
def main():
"""演示Map-Reduce模式"""
print("=== Map-Reduce 模式演示 ===\n")
# 创建图
builder = StateGraph(OverallState)
# 添加节点
builder.add_node("generate_subjects", generate_subjects)
builder.add_node("make_joke", make_joke)
# 添加边
builder.add_edge(START, "generate_subjects")
# 添加条件边,使用Send对象实现map-reduce
builder.add_conditional_edges(
"generate_subjects", # 源节点
map_subjects_to_jokes # 路由函数,返回Send对象列表
)
# 从make_joke到结束
builder.add_edge("make_joke", END)
# 编译图
graph = builder.compile()
print(graph.get_graph().print_ascii())
# 执行图
initial_state = {"subjects": [], "jokes": []}
print("初始状态:", initial_state)
print("\n开始执行图...")
result = graph.invoke(initial_state)
print(f"\n最终结果: {result}")
print("\n=== 演示完成 ===")
if __name__ == "__main__":
main()
复制代码
Command
Command:不仅可以指定下一个节点,还支持更新状态、处理中断恢复,以及在嵌套图之间导航。
常用于复杂的人机交互(Human-in-the-loop)和多智能体协同工作中智能体与智能体之间交接执行权(handoffs)
(, 下载次数: 0)
上传
点击文件名下载附件
Command与条件边的区别是:
条件边只会路由下一个node节点,
而Command不仅路由下一个node节点,还支持状态更新,如果需要同时更新状态和路由到不同的节点时,则使用 Command。
代码案例:
"""
CommandDemo.py | LangGraph 1.0.6 正式版
Command 基础演示:状态更新+流程控制+动态路由
"""
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command
# 全局常量:统一递归限制,便于维护
RECURSION_LIMIT = 50
# 定义状态
class AgentState(TypedDict):
messages: Annotated[list, lambda x, y: x + y] # 自动合并消息
current_agent: str
task_completed: bool
# 决策代理(核心路由节点)
def decision_agent(state: AgentState) -> Command[AgentState]:
"""根据消息内容路由代理,任务完成则直接终止"""
print("执行节点: decision_agent")
# 优先终止流程(核心防循环逻辑)
if state["task_completed"]:
print("✅ 检测到任务已完成,直接终止流程")
return Command(
update={"messages": [("system", "所有任务处理完成,流程正常结束")]},
goto=END
)
# 提取消息文本(兼容空消息)
last_message = state["messages"][-1] if state["messages"] else ("", "")
last_msg_content = last_message[1]
print(f"最新消息文本: {last_msg_content}")
# 动态路由
if "数学" in last_msg_content:
print("✅ 检测到数学任务,路由到数学代理")
return Command(
update={"messages": [("system", "路由到数学代理")], "current_agent": "math_agent"},
goto="math_agent"
)
elif "翻译" in last_msg_content:
print("✅ 检测到翻译任务,路由到翻译代理")
return Command(
update={"messages": [("system", "路由到翻译代理")], "current_agent": "translation_agent"},
goto="translation_agent"
)
else:
print("❌ 未识别任务类型,标记任务完成并终止")
return Command(
update={"messages": [("system", "任务完成")], "task_completed": True},
goto=END
)
# 数学代理(业务节点)
def math_agent(state: AgentState) -> Command[AgentState]:
"""处理数学计算任务,完成后返回决策代理"""
print("执行节点: math_agent")
result = "2 + 2 = 4"
print(f"计算结果: {result}")
return Command(
update={
"messages": [("assistant", f"数学计算结果: {result}")],
"current_agent": "decision_agent",
"task_completed": True
},
goto="decision_agent"
)
# 翻译代理(业务节点)
def translation_agent(state: AgentState) -> Command[AgentState]:
"""处理中英翻译任务,完成后返回决策代理"""
print("执行节点: translation_agent")
translation = "Hello -> 你好"
print(f"翻译结果: {translation}")
return Command(
update={
"messages": [("assistant", f"翻译结果: {translation}")],
"current_agent": "decision_agent",
"task_completed": True
},
goto="decision_agent"
)
def main():
"""演示Command基础用法:状态更新+动态路由+流程终止"""
print("=== Command 基础演示(LangGraph 1.0.6)===\n")
# 1. 构建状态图
builder = StateGraph(AgentState)
builder.add_node("decision_agent", decision_agent)
builder.add_node("math_agent", math_agent)
builder.add_node("translation_agent", translation_agent)
# 2. 定义边(完整节点关系)
builder.add_edge(START, "decision_agent")
builder.add_edge("math_agent", "decision_agent")
builder.add_edge("translation_agent", "decision_agent")
builder.add_edge("decision_agent", END)
# 3. 编译图
graph = builder.compile()
# 测试1:数学任务
print("【测试1: 数学任务】")
initial_state = {"messages": [("user", "我需要计算数学题")], "current_agent": "user", "task_completed": False}
print("初始状态:", initial_state)
result = graph.invoke(initial_state, recursion_limit=RECURSION_LIMIT)
print("最终状态(简化):", {k: v for k, v in result.items() if k != "messages"}) # 简化输出
print("\n" + "-" * 50 + "\n")
# 测试2:翻译任务
print("【测试2: 翻译任务】")
initial_state = {"messages": [("user", "我需要翻译文本")], "current_agent": "user", "task_completed": False}
print("初始状态:", initial_state)
result = graph.invoke(initial_state, recursion_limit=RECURSION_LIMIT)
print("最终状态(简化):", {k: v for k, v in result.items() if k != "messages"})
print("\n" + "-" * 50 + "\n")
# 测试3:未识别任务
print("【测试3: 未识别任务类型】")
initial_state = {"messages": [("user", "你好")], "current_agent": "user", "task_completed": False}
print("初始状态:", initial_state)
result = graph.invoke(initial_state, recursion_limit=RECURSION_LIMIT)
print("最终状态(简化):", {k: v for k, v in result.items() if k != "messages"})
# 新增:可视化图结构(教学演示必备)
print("\n=== 图结构可视化 ===")
print(graph.get_graph().draw_mermaid())
if __name__ == "__main__":
main()
复制代码
(, 下载次数: 0)
上传
点击文件名下载附件
Runtime context运行时上下文
高级特性
A2A:AI架构的协议革命
原文地址:https://blog.csdn.net/qq_39004307/article/details/158455688
欢迎光临 AI创想 (http://llms-ai.com/)
Powered by Discuz! X3.4