151.Agent-LangGraph-Send发送(Map-Reduce模式)

摘要:本文讲解 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创建多个任务

上图红框不使用匿名函数的写法如下,它返回一个列表,这个列表中的内容是要执行的节点

python 复制代码
def 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/


相关推荐
装不满的克莱因瓶38 分钟前
【SpringAI Alibaba】ReactAgent结构化输出OutputType与OutputSchema实战
java·人工智能·ai·springai·springaialibaba
楚山之石1 小时前
【Radiology】影像组学的标准化与可重复性:IBSI 卷积滤波器共识及其未能覆盖的环节
人工智能·学习·机器学习·mri
东姬AI1 小时前
挖出948个内核漏洞、自动写补丁:GPT-6 Cyber把AI推进“实战岗“,内容行业还卡在哪一关?
人工智能·网络安全·openai·gpt-6
用户2991422196801 小时前
GitHub 热榜项目:日榜(2026-11-10)
人工智能·开源·github
用户2991422196801 小时前
AI 日报(2026年11月10日)
人工智能
QXWZ_IA1 小时前
定位数据怎么推给 IoT 平台?消息协议选型与接入准备
网络·人工智能·物联网·安全
java_logo1 小时前
Docker 部署 ReMe:轻松搭建 AI Agent 记忆管理平台
人工智能·docker·容器·ai agent·轩辕镜像·reme·agentscope-ai
合尘猫2 小时前
图片表格变结构化数据:OCR 后的三个坑
python·ocr
小小张说故事2 小时前
Python 里弱引用到底弱在哪:拆开 weakref.ref、回调陷阱和 WeakValueDictionary
python
硅谷秋水2 小时前
从VLM规划器到机器人智能体:面向依赖历史信息的操控任务自演化框架
人工智能·机器学习·语言模型·机器人