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/


相关推荐
OKkankan2 小时前
python 进阶(三):面向对象进阶——从“会写 class”到设计可维护的后端对象
开发语言·数据结构·python
用户0332126663672 小时前
Python 办公自动化:用 Python 快速生成精美 PPT 演示文稿
python
solidwork_s2 小时前
SOLIDWORKS 2027 Electrical电气新功能汇总
运维·服务器·人工智能·solidworks
语音之家2 小时前
NeurIPS 2026|RAIL:从人类认知能力视角重新审视大型音频语言模型的听觉智能
人工智能·机器人·语⾳agent·音频语言
橙光成长2 小时前
Context Mode 深度解析:用沙盒、SQLite 与钩子路由,把 AI 编码 Agent 的上下文消耗削减 98%
数据库·人工智能·sqlite
打工仔折腾 AI2 小时前
极空间部署Portainer实战:绕过Socket权限、解决9000端口冲突与固定内网访问
人工智能·后端·python·性能优化
遇码2 小时前
Rust 桌面应用自托管自动更新:latest.json 清单 + minisign 签名校验的完整做法
人工智能·ai·rust·大模型·agent
嘻嘻的AI日记2 小时前
涉外涉密文件怎么译?本地化 AI 翻译,原文不出内网
人工智能
魔都吴所谓2 小时前
【Python】 多线程 & 多进程&协程 常见题 + 应用场景整理
开发语言·python