来源:互联网 更新时间:2026-08-02 20:49
graph.invoke入参深度解析说到LangGraph,控制流可以说是整个图编排的骨架——它决定了节点什么时候动、往哪跳。今天这篇,我们就来拆解LangGraph里最基础的两种控制流:顺序结构和分支结构。掌握了这些,构建复杂图应用就有了根基。

顺序结构是最直观的控制流形式,节点按定义顺序依次执行,就像流水线上的工序一样。
add_edge用于在两个节点之间添加一条有向边。节点之间的执行顺序、分支跳转、循环控制,最终都靠节点和边一起表达。下面这个例子展示了最基本的链式调用:
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class OverAllState(TypedDict):
username: str
greeting: str
output: str
def node_a(state: OverAllState) -> OverAllState:
return {"greeting": "Dear " + state["username"]}
def node_b(state: OverAllState) -> OverAllState:
return {"output": state["greeting"] + ",你好!"}
builder = StateGraph(state_schema=OverAllState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", END)
graph = builder.compile()
res = graph.invoke({"username": "小黄"})
print(res)
输出:
{"username": "小黄", "greeting": "Dear 小黄", "output": "Dear 小黄,你好!"}
执行流程很简单:START -> node_a -> node_b -> END。
如果需要构建一组按顺序执行的节点,add_sequence可以省去很多重复代码。它会按照列表顺序依次添加节点,并在相邻节点之间自动添加边。
builder = StateGraph(state_schema=OverAllState)
builder.add_edge(START, "node_a")
builder.add_sequence([node_a, node_b])
builder.add_edge("node_b", END)
这段代码等价于手动调用 add_node 和 add_edge:
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_edge("node_a", "node_b")
在LangGraph中,END并不是运行阶段真正执行的节点,它更像一个“终止标记”。在一些简单的线性流程里,即使省略指向 END的边,最后一个节点执行完毕后,如果没有后续节点触发,图也能正常结束。
builder = StateGraph(state_schema=OverAllState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "node_b")
# 省略了 builder.add_edge("node_b", END)
不过需要注意:虽然可以省略,但在分支、条件跳转、循环退出等场景中,显式指向 END通常更利于阅读和维护。而 START通常不能省略,因为它告诉LangGraph图运行时应该从哪些节点开始执行。
分支结构允许图根据运行时状态选择不同的执行路径,这才是图编排真正灵活的地方。
静态分支的核心特征是:节点的下游候选节点在图编译阶段就完全确定,运行时只是根据条件选择哪条边执行。说白了,编译期就知道下游有哪些节点,但具体走哪条路要运行时决定。
并行节点是最简单的静态分支形式。当多个节点都从同一个上游节点触发时,它们会在同一个超步(SuperStep)中被激活。
builder.add_edge(START, "node_a")
builder.add_edge(START, "node_b")
来看一个完整的例子:两个节点并行执行,一个写诗,一个讲笑话。
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
from langchain_deepseek import ChatDeepSeek
from langchain.messages import HumanMessage
from dotenv import load_dotenv
load_dotenv(override=True)
model = ChatDeepSeek(model="deepseek-v4-flash", extra_body={"thinking": {"type": "disabled"}})
class OverAllState(TypedDict):
topic: str
poem: str
joke: str
def node_a(state: OverAllState) -> OverAllState:
poem = model.invoke([HumanMessage(f"写一首关于 {state['topic']} 的七言绝句")]).content
return {"poem": poem}
def node_b(state: OverAllState) -> OverAllState:
joke = model.invoke([HumanMessage(f"写一个关于 {state['topic']} 的笑话")]).content
return {"joke": joke}
builder = StateGraph(state_schema=OverAllState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_edge(START, "node_a")
builder.add_edge(START, "node_b")
builder.add_edge("node_a", END)
builder.add_edge("node_b", END)
graph = builder.compile()
res = graph.invoke({"topic": "猫咪"})
print(res)
输出:
{"topic": "猫咪", "poem": "《猫咪》\n夜巡檐角步轻悄,昼卧花阴晒暖毛。...", "joke": "有一只猫走进了一家披萨店..."}
这里有两个关键点:
InvalidUpdateError。StateGraph提供了 add_conditional_edges方法,用于从某个上游节点出发,根据运行时状态选择下游节点。
方法签名:
def add_conditional_edges(
self,
source: str,
path: Callable[..., Hashable | Sequence[Hashable]],
path_map: dict[Hashable, str] | list[str] | None = None,
) -> Self:
核心参数:
source:条件分支的起始节点。path:路由规则,是一个可执行对象(通常是函数)。path_map:路由规则的返回值到真实节点名之间的映射关系。如果不传 path_map,路由函数的返回值应当直接是图中的节点名称。
def router(state: OverAllState) -> Literal["node_a", "node_b"]:
if "诗" in state["content_type"]:
return "node_a"
return "node_b"
builder.add_conditional_edges(START, router)
完整案例:
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
from langchain.messages import HumanMessage
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv
load_dotenv(override=True)
model = ChatDeepSeek(model="deepseek-v4-flash", extra_body={"thinking": {"type": "disabled"}})
class OverAllState(TypedDict):
topic: str
content_type: str
poem: str
joke: str
def node_a(state: OverAllState) -> OverAllState:
poem = model.invoke([HumanMessage(f"写一首关于 {state['topic']} 的七言绝句")]).content
return {"poem": poem}
def node_b(state: OverAllState) -> OverAllState:
joke = model.invoke([HumanMessage(f"写一个关于 {state['topic']} 的笑话")]).content
return {"joke": joke}
def router(state: OverAllState) -> Literal["node_a", "node_b"]:
if "诗" in state["content_type"]:
return "node_a"
return "node_b"
builder = StateGraph(state_schema=OverAllState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_conditional_edges(START, router)
builder.add_edge("node_a", END)
builder.add_edge("node_b", END)
graph = builder.compile()
poem_res = graph.invoke({"topic": "布偶狗", "content_type": "诗"})
joke_res = graph.invoke({"topic": "布偶狗", "content_type": "笑话"})
如果不希望路由函数直接返回节点名,而是返回业务语义更强的标识,可以使用 path_map进行映射。
def router(state: OverAllState) -> Literal["a", "b"]:
if "诗" in state["content_type"]:
return "a"
return "b"
builder.add_conditional_edges(START, router,
path_map={"a": "node_a", "b": "node_b"})
这种写法的好处是:
add_conditional_edges也支持一次路由到多个下游节点。
from collections.abc import Sequence
def router(state: OverAllState) -> Sequence[Literal["node_a", "node_b", "node_c"]]:
if "诗" in state["content_type"]:
return ["node_a", "node_c"]
return ["node_b", "node_c"]
builder.add_conditional_edges(START, router,
path_map=["node_a", "node_b", "node_c"])
在这个例子中:
content_type包含"诗"时,同时触发 node_a和 node_c。node_b和 node_c。| 控制流类型 | 方法 | 说明 |
|---|---|---|
| 顺序执行 | add_edge(A, B) | A执行完后执行B |
| 批量顺序 | add_sequence([A, B, C]) | 自动添加节点和边 |
| 并行执行 | add_edge(X, A)+ add_edge(X, B) | 同一超步中同时执行 |
| 条件分支 | add_conditional_edges(X, router) | 根据运行时状态选择下游 |
| 多目标路由 | router返回序列 | 一次路由到多个下游节点 |
add_edge(START, node_a)和 add_conditional_edges(START, router)有什么区别?参考答案:
add_edge(START, node_a)表示从START无条件地连接到 node_a,node_a一定会被执行。add_conditional_edges(START, router)表示从START出发,根据 router函数的返回值动态决定执行哪个节点。前者是固定路径,后者是条件路径。在渲染图结构时,条件边通常会显示为虚线,并且边上会标注路由标签。
参考答案:
如果两个节点都更新同一个状态字段,行为取决于该字段是否配置了Reducer:
Annotated[list[str], add]):两个节点的更新会按照Reducer规则合并。例如 addReducer会将两个列表拼接在一起。InvalidUpdateError异常。最佳实践:对于可能被多个节点同时更新的字段,一定要配置合适的Reducer。
add_sequence和手动调用 add_node+ add_edge相比有什么优势?参考答案:
add_sequence的优势在于:
add_node和 add_edge调用。缺点是灵活性较低,不支持在节点之间插入条件分支或其他逻辑。
path_map参数的作用是什么?什么时候需要使用它?参考答案:
path_map的作用是将路由函数的返回值映射到实际的节点名称。
使用场景:
"poem"、"joke"),通过 path_map映射到节点名(如 "node_a"、"node_b")。path_map后,渲染图结构时边上会显示路由标签,使图更容易理解。path_map可以帮助渲染器正确展示条件边。END的边有什么风险?参考答案:
虽然在简单线性流程中省略指向 END的边也能正常运行,但在以下场景中存在风险:
END,可能会意外地继续执行到其他节点。END,循环可能无法正确终止。END会使图结构不完整,其他人阅读代码时难以判断流程何时结束。建议:在所有路径的终点都显式添加指向 END的边,使图结构完整、语义清晰。
参考答案:
路由函数的入参类型决定了它可以访问哪些字段。如果没有显式声明入参类型,路由函数可以访问所有已记录的状态字段。
# 可以访问所有状态字段
def router(state: OverAllState) -> str:
return "node_a" if state["content_type"] == "诗" else "node_b"
# 也可以使用特定的输入状态类型
def router(state: InputState) -> str:
return "node_a" if state["content_type"] == "诗" else "node_b"
注意:路由函数通常只应该读取状态字段来做决策,不应该修改状态。如果需要在路由的同时修改状态,应使用 Command机制。
参考答案:
可以通过组合并行节点和汇聚节点来实现:
# Fan-out: 从START并行分发到多个节点
builder.add_edge(START, "node_a")
builder.add_edge(START, "node_b")
builder.add_edge(START, "node_c")
# 各节点独立执行...
# Fan-in: 多个节点汇聚到同一个汇总节点
builder.add_edge("node_a", "summary_node")
builder.add_edge("node_b", "summary_node")
builder.add_edge("node_c", "summary_node")
# 汇总节点处理所有结果
builder.add_edge("summary_node", END)
在 summary_node中,可以从状态中读取所有并行节点的输出,进行汇总处理。注意需要为可能被多个节点写入的字段配置合适的Reducer。
希望这篇教程能帮助你掌握LangGraph的控制流机制,从简单的顺序执行到复杂的分支路由,构建出灵活高效的图应用!
ThinkBook系列最新价格全解析:2026年选购避坑与实时询价指南
Ondo将于今日上线股票永续合约
暗黑4S14野蛮人终局BD攻略
区块链OTC交易所有哪几家比较正规?
Binance新增15种bStocks代币化证券为杠杆抵押资产
Meme币DOGS今晚上线!开局就解锁91%代币是否带来风险?
忍者必须死3极刃血影角色介绍
晶核艾尔莎角色盘点 晶核艾尔莎强度分析与实战表现
余姚的路虎4s店在哪个位置
彩云天气怎么看分钟级降雨预报 彩云天气精准预报方法【技巧】
五千元以下的笔记本几乎消失!经销商:至少一年看不到涨价尽头
Intel喜讯连连:18A工艺良率提升到85%、CPU将涨价15%
遗忘之海密室通关教程 遗忘之海密室全关卡解谜思路与难点解析
华为Mate 70系列首发的红枫镜头下放至千元档:全员普及原色影像
合集38个项目筹集5.406亿美元 Figure融资2亿
国家养老服务消费补贴上线京东
英伟达机器人团队在京沪深招人,聚焦具身智能等四大领域
一站式PDF转Markdown解决方案PDF3MD
硬刚苹果!华为9月新品阵容出炉:Mate 90系列、全新三折叠
微软Copilot AI漏洞可致敏感数据泄露,企业用户需及时更新
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc