Skip to content

边 Edge

节点是"干活的人",边就是"流程图上的箭头"——决定执行完一个节点后去哪。本篇讲普通边的语义和用法,条件边在下一篇单独讲。

一、边是什么

边(Edge)是节点之间的连线,表示"执行完 source 节点后,下一步去 target 节点"。

mermaid
graph LR
    A[节点A] -->|边| B[节点B]

API:

python
graph.add_edge(source, target)
  • sourcetarget 都是字符串,对应 add_node 注册时的名字。
  • 特殊节点 STARTEND 也能作为 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, END
  • START:图的唯一入口。任何图必须有 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, ...)。原因:

  1. 明确意图:图可能有多个候选入口节点,自动猜容易猜错。
  2. 可校验:编译时能检查"是否所有节点都接到了边"。
  3. 可读性:看图代码一眼就知道从哪开始。

如果忘了连 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 对比

维度PythonJava (LangGraph4j)
APIadd_edge(src, dst)addEdge(src, dst)
START/ENDSTART / END 常量START / END 常量
入口要求必须显式连 START必须显式连 START
多出口必须用条件边必须用条件边
数据传递通过 State通过 State

完全对应。

十一、常见踩坑

  1. 忘了 add_edge(START, ...):编译报错 Graph must have an entrypoint
  2. 忘了连 END:图没有出口,要么编译失败,要么跑到 recursion_limit 默认 25 次后被强制中断。
  3. 节点名拼写错误add_edge("retrive", "generate") 把 retrieve 写成 retrive——编译时报"找不到节点"。
  4. 一个节点两条普通出边:报错。想分叉用条件边。
  5. 形成没有出口的环A->B->A 但没有边到 END——会一直循环直到 recursion_limit,然后抛 RecursionError。一定配条件边在某条件下跳到 END。
  6. 以为执行顺序由 add_node 决定:不——由边决定。add_node 顺序只是注册顺序。
  7. 重复加边:同一条边加两次不会出错但会浪费。
  8. 节点没注册就加边add_edge("foo", "bar") 但 foo 没注册过——编译时报错。
  9. 直接连 START 到 ENDadd_edge(START, END) 形成空图——没节点执行,invoke 直接返回输入。一般没意义。
  10. 节点太多边难追踪:图复杂时用 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——让图能分叉和循环。