摘要:本文讲解 LangGraph 中 Send 机制与 Map-Reduce 模式。普通边只能串行传递完整 State,无法并行;Send 可在运行时动态创建多个任务,向指定节点发送独立 State,实现并行处理。文章通过两个实例演示:一是相同节点并行执行(数字分发求平方和),二是多个节点并行执行(大模型动态规划任务分发),并附完整可运行代码与效果图。
内容参考于:图灵AI大模型全栈
通过边没办法做到并行执行,比如上传了5个文档,需要对这5个文档做摘要,通过边的方式,只能让一个节点跳转到另外一个节点,它没办法同时运行,这样就导致了5个文档只能排队一个一个的处理,这个Send就是为了解决这个问题,Send可以在运行的时候动态的创建多个任务,还可以向指定的节点发到一个独立的State数据,从而实现并行处理
如下图普通边部分,它的走向是代码中写死的,并且State里面的内容是上一个节点中完整的内容,不管下一节点用不用得到,上一个节点都会把完整的State发给下一个节
然后Send部分,首先节点A是一个分发器,分发器里面就是通过Send创建多个任务,当任务执行完成后通过归并函数(Reduce)整合结果

Map-Reduce模式
Map表示把大任务拆成多个小任务,并行处理
Reduce表示把所有小任务的结果整合成最终的结果
相同节点并行执行实例,代码说明和代码
下图红框是要处理的数据
下图红框是分发器,lambda s: s是匿名函数
lambda s: s不匿名的写法如下,直接把State传递给下一个节点
def xxx(state): """透传函数:不修改状态,直接原样返回""" return state然后当lambda s: s执行完后,执行 split_numbers 函数,这个 "worker" 是一个白名单,如果split_numbers 函数返回的内容不在白名单中会报错
split_numbers 函数它的逻辑就是Map阶段调用Send创建多个任务
上图红框不使用匿名函数的写法如下,它返回一个列表,这个列表中的内容是要执行的节点
pythondef split_numbers(state: State): """把数字分发给不同的worker""" numbers = state["numbers"] print(f"分发数字: {numbers}") # 手动逐个构建 Send 对象,存入列表 send_tasks = [] for num in numbers: # 先创建 worker 的独立状态 worker_input = WorkerState(number=num) # 再封装成 Send 任务,加入任务列表 send_tasks.append(Send("worker", worker_input)) return send_tasks下图红框 summer 是整合结果的,它的调用时机是上一个节点返回了一个Send列表,这个列表都是指向worker节点,只有当所有的Send的worker节点全部执行完,这个 summer 节点才会执行,就算说Send会根据传入的节点名创建一个计数器,当计数器归零时才可以执行节点指向的下一个节点
效果图:
代码
python""" LangGraph Map-Reduce 简单案例:数字求和 把一堆数字分给多个worker算平方,然后把结果加起来 """ from typing import Annotated import operator from langgraph.graph import StateGraph, START, END from langgraph.types import Send from typing import TypedDict, List # 状态定义 class State(TypedDict): numbers: List[int] # 输入的数字 results: Annotated[list[int], operator.add] # worker的结果 final_sum: int # 最终求和 # 创建一个send的状态 class WorkerState(TypedDict): number: int # 1. Map阶段:分发数字 def split_numbers(state: State): """把数字分发给不同的worker""" numbers = state["numbers"] print(f"分发数字: {numbers}") # 每个数字发给一个worker return [Send("worker", WorkerState(number=num)) for num in numbers] # 2. Worker阶段:计算平方 def calculate_square(state: WorkerState): """每个worker计算一个数字的平方""" number = state["number"] square = number * number print(f"Worker: {number}² = {square}") return {"results": [square]} # 3. Reduce阶段:求和 def sum_results(state: State): """把所有结果加起来""" results = state.get("results", []) total = sum(results) print(f"求和: {results} = {total}") return {"final_sum": total} # 构建图 def create_simple_graph(): graph = StateGraph(State) # 添加节点 graph.add_node("splitter", lambda s: s) # 分发器 graph.add_node("worker", calculate_square) # 工作节点 graph.add_node("summer", sum_results) # 求和器 # 连接节点 graph.add_edge(START, "splitter") graph.add_conditional_edges("splitter", split_numbers, ["worker"]) # Map阶段 graph.add_edge("worker", "summer") # Worker完成后求和 graph.add_edge("summer", END) return graph.compile() # 运行例子 def run_example(): app = create_simple_graph() # 测试数据 initial_state = { "numbers": [1, 2, 3, 4, 5], "results": [], "final_sum": 0 } print("开始计算...") print("任务:计算每个数字的平方,然后求和") print() # 运行 result = app.invoke(initial_state) print(result) if __name__ == "__main__": run_example()
多个节点并行执行实例,代码说明和代码,个别详细的说明都写在上方了
它和上方的区别是加入了大模型,让大模型通过问题动态的返回一个结构化的数据,然后调用Send
如下图红框,它的分发函数会返回三种Send,这个三种Send会根据节点名分别计数,当节点名绑定的计数归零时才会执行下一个节点
效果图:下图红框是打印的split_tasks函数中大模型返回的内容,下图蓝框是后续节点返回的内容
python""" LangGraph Map-Reduce 简单案例:分解任务-进行并行执行任务-总结答案 """ from typing import Annotated, Any import operator from langgraph.graph import StateGraph, START, END from langgraph.types import Send from typing import TypedDict, List from langchain.chat_models import init_chat_model from dotenv import load_dotenv load_dotenv() # 创建模型 model = init_chat_model("qwen3.7-flash", openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",model_provider="openai",extra_body={"thinking": {"type": "disabled"}}) # 关键配置:关闭思考模式 class MyState(TypedDict): question: str # 用户一次性问多个任务,通过模型进行规划,并行处理任务(计算5*8+9等于多少?;李白是谁?;帮我查询一下langgraph是什么?) tasks_results: Annotated[list[str], operator.add] answer: str class TaskState(TypedDict): task_value: str class SplitTasksSchema(TypedDict): tasks: dict[str, str] # 定义条件边函数-任务分发 def split_tasks(state: MyState): question = state["question"] prompt = f""" 你是一个任务分割助手,擅长将用户问题进行提取分类 分类格式:{{"math_node": 数学问题, "chinese_node": 语文问题, "search_node": "搜索问题"}} 注意:严格返回分类数据格式 用户问题"{question} """ # 绑定结构化输出格式的模型 structured_model = model.with_structured_output(SplitTasksSchema) response = structured_model.invoke(prompt) print(response) tasks = response["tasks"] return [Send(task, TaskState(task_value=value)) for task, value in tasks.items()] # 定义节点-执行不同的任务 def math_node(state: TaskState): task_value = state["task_value"] print("数学任务:", task_value) return {"tasks_results": ["5*8+9=49"]} def chinese_node(state: TaskState): task_value = state["task_value"] print("语文任务:", task_value) return {"tasks_results": ["李白是唐朝的诗人"]} def search_node(state: TaskState): task_value = state["task_value"] print("搜索任务:", task_value) return {"tasks_results": ["langgraph是一个带状态的流程图"]} # 根据并行的任务去进行汇总答案 def summarize_answers(state: MyState): tasks_results = state["tasks_results"] prompt = f""" 你是一个总结多个任务返回结果的专家 用户问题:{state['question']} 多个并行节点返回的答案:{tasks_results} """ answer = model.invoke(prompt) return {"answer": answer} graph = StateGraph(state_schema=MyState) # 添加节点 graph.add_node("splitter", lambda s: s) # 分发器 graph.add_node("math_node", math_node) # 数学任务节点 graph.add_node("chinese_node", chinese_node) # 语文任务节点 graph.add_node("search_node", search_node) # 搜索任务节点 graph.add_node("summarize_answers", summarize_answers) # 总结节点 # 连接节点 graph.add_edge(START, "splitter") graph.add_conditional_edges("splitter", split_tasks, ["math_node", "chinese_node", "search_node"]) # Map阶段 graph.add_edge("math_node", "summarize_answers") graph.add_edge("chinese_node", "summarize_answers") graph.add_edge("search_node", "summarize_answers") graph.add_edge("summarize_answers", END) g = graph.compile() res = g.invoke({"question": "计算5*8+9等于多少?;李白是谁?;帮我查询一下langgraph是什么?"}) print(res) # 打印LangGraph的流程图,它的内容是Mermaid print(g.get_graph().draw_mermaid())
下图红框的内容是下图蓝框的代码打印的

如下图把上图红框的内容复制到下图红框,可以查看流程图,虚线表示多节点,下图的网址:https://mermaid.live/










