难点 : 并行分支的状态合并与顺序不确定
两个并行节点同时写同一个列表字段,结果顺序时而是 A→B,时而是 B→A。
为什么难?同一 super-step 内的并行节点看不到彼此的写入(都拿到该步开始时的快照), 合并发生在 super-step 结束时,且顺序不保证。很多人误以为并行节点可以像串行一样接力。
攻克方法:
① 接受顺序不确定,不写依赖顺序的逻辑;
② 需要稳定顺序就给元素加序号字段,最后排序;
③ 需要"各自写各自的"就改用 dict + 自定义深合并 reducer;
④ 需要接力就必须串行(用边连接,别用 fan-out)。
假设我们有三个并行检索节点(搜索网页、查询数据库、查找PDF),它们各自返回一个结果,然后合并到一个列表中。如果不加序号,合并后的顺序可能每次不同。
不加序号:顺序不确定
import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
query: str
results: Annotated[list, operator.add] # 累积,但顺序不确定
def search_web(state: State):
return {"results": ["网页结果:LangGraph教程"]}
def search_db(state: State):
return {"results": ["数据库结果:LangGraph教程"]}
def search_pdf(state: State):
return {"results": ["PDF结果:LangGraph教程"]}
def merge(state: State):
print(f"合并后的顺序:{state['results']}")
# 可能输出:['网页结果', '数据库结果', 'PDF结果']
# 也可能: ['PDF结果', '网页结果', '数据库结果']
# 或任意排列
return state
# 建图:三个并行分支
builder = StateGraph(State)
builder.add_node("web", search_web)
builder.add_node("db", search_db)
builder.add_node("pdf", search_pdf)
builder.add_node("merge", merge)
builder.add_edge(START, "web")
builder.add_edge(START, "db")
builder.add_edge(START, "pdf")
builder.add_edge("web", "merge")
builder.add_edge("db", "merge")
builder.add_edge("pdf", "merge")
builder.add_edge("merge", END)
graph = builder.compile()
graph.invoke({"query": "LangGraph", "results": []})
# 每次运行,results 的顺序可能不同!
✅ 加序号字段:顺序稳定
核心思路:每个节点返回的结果带上序号,最后在合并节点中按序号排序。
import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
query: str
# 不再直接用 list[str],而是用 list[dict],每个 dict 包含序号和数据
results: Annotated[list, operator.add]
def search_web(state: State):
# 返回带序号的数据
return {"results": [{"seq": 1, "data": "网页结果:LangGraph教程"}]}
def search_db(state: State):
return {"results": [{"seq": 2, "data": "数据库结果:LangGraph教程"}]}
def search_pdf(state: State):
return {"results": [{"seq": 3, "data": "PDF结果:LangGraph教程"}]}
def merge(state: State):
# 按 seq 排序,确保顺序稳定
sorted_results = sorted(state["results"], key=lambda x: x["seq"])
print(f"排序前的顺序:{state['results']}")
print(f"排序后的顺序:{sorted_results}")
# 提取数据
final_data = [item["data"] for item in sorted_results]
print(f"最终结果:{final_data}")
return {"results": []} # 清空,避免重复累积
# 建图相同
builder = StateGraph(State)
builder.add_node("web", search_web)
builder.add_node("db", search_db)
builder.add_node("pdf", search_pdf)
builder.add_node("merge", merge)
builder.add_edge(START, "web")
builder.add_edge(START, "db")
builder.add_edge(START, "pdf")
builder.add_edge("web", "merge")
builder.add_edge("db", "merge")
builder.add_edge("pdf", "merge")
builder.add_edge("merge", END)
graph = builder.compile()
graph.invoke({"query": "LangGraph", "results": []})
# 输出示例(每次运行都一致):
# 排序前的顺序:[{'seq': 1, 'data': '网页结果'}, {'seq': 3, 'data': 'PDF结果'}, {'seq': 2, 'data': '数据库结果'}]
# 排序后的顺序:[{'seq': 1, 'data': '网页结果'}, {'seq': 2, 'data': '数据库结果'}, {'seq': 3, 'data': 'PDF结果'}]
# 最终结果:['网页结果:LangGraph教程', '数据库结果:LangGraph教程', 'PDF结果:LangGraph教程']
更通用的模式:封装成 reducer
如果你经常需要“带序号的列表合并”,可以封装成一个自定义 reducer:
from typing import Any
def ordered_append(left: list[dict], right: list[dict]) -> list[dict]:
"""带序号的追加,最后可通过排序稳定顺序"""
return left + right
def sort_by_seq(items: list[dict], key: str = "seq") -> list[dict]:
"""工具函数:按序号排序"""
return sorted(items, key=lambda x: x[key])
# 在 State 中使用
class State(TypedDict):
results: Annotated[list[dict], ordered_append] # 稳定的合并
# 在合并节点中统一排序
def merge(state: State):
ordered = sort_by_seq(state["results"])
# 提取数据
data = [item["data"] for item in ordered]
return {"final_output": data}
259

被折叠的 条评论
为什么被折叠?



