Appearance
边 Edge
节点是"干活的人",边就是"流程图上的箭头"——决定执行完一个节点后去哪。本篇讲普通边的语义和用法,条件边在下一篇单独讲。
一、边是什么
边(Edge)是节点之间的连线,表示"执行完 source 节点后,下一步去 target 节点"。
mermaid
graph LR
A[节点A] -->|边| B[节点B]API:
python
graph.add_edge(source, target)source和target都是字符串,对应add_node注册时的名字。- 特殊节点
START和END也能作为 source/target。
二、关键认知:边本身不传数据
新手最容易误解的点:边不传数据,数据通过 State 传递。
mermaid
graph LR
A[节点A] --"边只决定去哪"--> B[节点B]
A -.读/写.-> S[(State)]
B -.读/写.-> S边只是一个"路由指令":执行完 A,去 B。它不带参数、不传值。节点 B 拿到的是当前 State 的快照,而不是 A 显式传过来的东西。
如果用过 LangChain4j 或工作流引擎:这跟"消息在节点间通过共享上下文传递"是一回事,不是"上一个节点的输出当参数传给下一个"。
三、START 和 END
python
from langgraph.graph import StateGraph, START, ENDSTART:图的唯一入口。任何图必须有add_edge(START, "某节点")。END:图的出口。某节点连到 END 表示"到这里结束"。
一个图可以有多个出口都指向 END:
mermaid
graph LR
S([START]) --> A
A --> B
A --> C
B --> E([END])
C --> E但通常入口只有一个(从 START 出发到第一个节点)。多个入口要用条件边(条件边本质还是从 START 后的某节点分叉)。
四、最基本的串联
python
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
value: int
def double(state: State) -> dict:
return {"value": state["value"] * 2}
def add_one(state: State) -> dict:
return {"value": state["value"] + 1}
g = StateGraph(State)
g.add_node("double", double)
g.add_node("add_one", add_one)
# 串联:START -> double -> add_one -> END
g.add_edge(START, "double")
g.add_edge("double", "add_one")
g.add_edge("add_one", END)
app = g.compile()
result = app.invoke({"value": 3})
print(result["value"]) # (3 * 2) + 1 = 7输出:
text
7执行流程:
mermaid
graph LR
S([START]) --> D[double 乘2]
D --> A[add_one 加1]
A --> E([END])五、为什么要显式 add_edge(START, ...)
很多框架会"自动"找到入口节点。LangGraph 故意不自动——它要求你显式声明 add_edge(START, ...)。原因:
- 明确意图:图可能有多个候选入口节点,自动猜容易猜错。
- 可校验:编译时能检查"是否所有节点都接到了边"。
- 可读性:看图代码一眼就知道从哪开始。
如果忘了连 START,编译时报错:
text
ValueError: Graph must have an entrypoint (an edge from START to a node)六、END 表示结束
任何指向 END 的边都意味着"流程到此结束"。
python
g.add_edge("last_node", END)如果某节点没有出边、也没连 END,编译时可能会警告或报错(视版本而定)。最好显式连到 END。
注意:节点可以有多个出边吗?——可以,但需要用条件边(下一篇讲)。普通边一个节点只能有一条出边,否则框架不知道该走哪条。
七、串联多个节点示例
来个稍微复杂的"流水线":
python
# edges_demo.py
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
raw: str
cleaned: str
parsed: dict
output: str
def clean(state: State) -> dict:
"""清洗输入:去空格、转小写。"""
text = state["raw"].strip().lower()
return {"cleaned": text}
def parse(state: State) -> dict:
"""把清洗后的文本解析成结构化数据。"""
text = state["cleaned"]
parts = text.split("=")
if len(parts) == 2:
return {"parsed": {"key": parts[0], "value": parts[1]}}
return {"parsed": {"error": "格式不对"}}
def format_output(state: State) -> dict:
"""格式化输出。"""
p = state["parsed"]
if "error" in p:
return {"output": f"错误:{p['error']}"}
return {"output": f"{p['key']} = {p['value']}"}
g = StateGraph(State)
g.add_node("clean", clean)
g.add_node("parse", parse)
g.add_node("format", format_output)
g.add_edge(START, "clean")
g.add_edge("clean", "parse")
g.add_edge("parse", "format")
g.add_edge("format", END)
app = g.compile()
print(app.invoke({"raw": " Name=LangGraph "})["output"])
# 输出:name = langgraph执行流程:
mermaid
graph LR
S([START]) --> C[clean 清洗]
C --> P[parse 解析]
P --> F[format 格式化]
F --> E([END])注意每个节点只关心 state 里自己要读写的字段,节点之间通过 state 协作——这是边的"不传数据"哲学的体现。
八、分支与多出口
如果想根据状态走不同路径,需要条件边(ConditionalEdge),见 条件边 ConditionalEdge。普通边只能表达"确定地走某条路"。
下面这个写法会报错(一个节点不能有两条普通出边):
python
# 错误示范:一个节点连两条普通出边
g.add_edge("decide", "path_a")
g.add_edge("decide", "path_b")
# ValueError: 节点 decide 已经有出边了想分叉必须用条件边。
九、自环与循环
普通边可以构成自环(虽然没意义)或更长的环:
python
# 自环(没意义,仅演示)
g.add_edge("loop", "loop")更有用的是用条件边在"是否继续"上做循环,比如 ReAct 的"思考-行动-观察"反复直到得出答案。这里只要记住:普通边也能构成环,但一定要有结束条件,否则死循环。
mermaid
graph LR
A --> B
B --> A
A -.缺少出口,会死循环.-> X[?]十、与 LangGraph4j 对比
| 维度 | Python | Java (LangGraph4j) |
|---|---|---|
| API | add_edge(src, dst) | addEdge(src, dst) |
| START/END | START / END 常量 | START / END 常量 |
| 入口要求 | 必须显式连 START | 必须显式连 START |
| 多出口 | 必须用条件边 | 必须用条件边 |
| 数据传递 | 通过 State | 通过 State |
完全对应。
十一、常见踩坑
- 忘了
add_edge(START, ...):编译报错Graph must have an entrypoint。 - 忘了连 END:图没有出口,要么编译失败,要么跑到
recursion_limit默认 25 次后被强制中断。 - 节点名拼写错误:
add_edge("retrive", "generate")把 retrieve 写成 retrive——编译时报"找不到节点"。 - 一个节点两条普通出边:报错。想分叉用条件边。
- 形成没有出口的环:
A->B->A但没有边到 END——会一直循环直到recursion_limit,然后抛RecursionError。一定配条件边在某条件下跳到 END。 - 以为执行顺序由
add_node决定:不——由边决定。add_node顺序只是注册顺序。 - 重复加边:同一条边加两次不会出错但会浪费。
- 节点没注册就加边:
add_edge("foo", "bar")但 foo 没注册过——编译时报错。 - 直接连 START 到 END:
add_edge(START, END)形成空图——没节点执行,invoke 直接返回输入。一般没意义。 - 节点太多边难追踪:图复杂时用
app.get_graph().draw_mermaid()画出来肉眼检查。
十二、可视化检查
写完图不确定连对没?打印 mermaid 看一眼:
python
print(app.get_graph().draw_mermaid())把输出贴到 mermaid 渲染器(如 mermaid.live)就能可视化检查。这是排查"图连错了"问题最快的方法。
十三、小结
- 边
add_edge(source, target)表示"执行完 source 去 target"。 - 边本身不传数据——数据通过 State 传递。
START是入口,必须add_edge(START, ...);END是出口。- 一个节点只能有一条普通出边,想分叉必须用条件边。
- 普通边能构成环,但必须有结束条件。
- 用
app.get_graph().draw_mermaid()可视化检查图结构。
下一篇讲 条件边 ConditionalEdge——让图能分叉和循环。