第 3 篇:让 Agent 学会分工,LangGraph 构建多 Agent系统
系列简介:从零搭建一个多 Agent AI 助手,覆盖原理、实现、部署全链路。不讲空话,每篇都有可运行的代码。
项目地址:https://github.com/CodeMomentYY/LangGraph-Agent
本篇目标:用 LangGraph 搭建一个多 Agent 协作系统,支持意图识别、动态路由、串行/并行执行。
前言
大家好,我是一名前端工程师。都说前端“已死”,那与其担心被 AI 替代,不如打入敌人内部,于是我开始折腾 Agent 开发。
折腾下来发现,Agent 的核心不是算法,而是“工程能力”(怎么设计架构、怎么串联服务、怎么把 LLM 的能力落地成产品)。这些恰好是我们擅长的事。
这个系列记录我从零搭建多 Agent 系统的完整过程。只聊技术知识和设计思路,代码交给 AI 写。如果你也想从应用层切入 AI,希望这个系列对你有帮助。
读完本篇你将学到:
- 为什么需要多 Agent,单 Agent 的瓶颈在哪;
- LangGraph 是什么,为什么选它;
- 多 Agent 架构设计:Dispatcher + 专业 Agent;
- 串行和并行两种执行模式的实现;
背景与动机
上两篇我们用 30 行代码实现了单 Agent,也了解了三种范式。但当你想让 Agent 同时具备“查天气”、“写文章”、“闲聊”这些能力时,那么问题来了:
全塞到一个 Agent 里,Prompt 会越来越臃肿。你得在一个 System Prompt 里描述所有工具、所有规则、所有场景。LLM 看到一大坨指令,容易搞混,该调工具的时候不调,不该调的时候乱调。
这就像一个人同时当厨师、服务员、收银员——忙得过来,但质量堪忧。
解决方案很自然:分工。让不同的 Agent 各管一摊,再加一个“调度员”决定把任务交给谁。
但分工带来了新问题:多个 Agent 之间怎么传递数据?执行顺序怎么控制?什么条件走哪条路?如果还用前两篇的原生写法(手写 for 循环 + if-else),代码会迅速变成一团意大利面。
这就是为什么我们需要一个框架来管理流程——不是因为原生写法不行,而是当节点多了、路由复杂了,框架能帮你把“流程控制”这件事做得更清晰。
核心概念
单 Agent vs 多 Agent
我们看一下两者对比:
| 单 Agent | 多 Agent | |
|---|---|---|
| Prompt | 一个巨大的 System Prompt | 每个 Agent 一个精简 Prompt |
| 职责 | 什么都干 | 各司其职 |
| 调试 | 出错不知道哪个环节的问题 | 哪个 Agent 出错一目了然 |
| 扩展 | 加功能就改 Prompt(越改越乱) | 加功能就加一个新 Agent |
| 速度 | 还行 | 多了 Dispatcher 的开销 |
设计多 Agent 架构
架构图:
三个专业 Agent:
- tools 路径:需要调工具的任务(查天气、获取网页、查语雀文档),内部是 ReAct 循环;
- writer 路径:写作类任务(邮件、文案、翻译),纯 LLM 生成,temperature 调高一点增加创造力;
- chat 路径:闲聊和知识问答,纯 LLM 对话,会从知识库检索相关内容(RAG);
使用 LangGraph 来构建
为什么选 LangGraph 而不是 LangChain?很多人分不清 LangChain 和 LangGraph 的关系。简单来说:
- LangChain:工具箱。提供了调 LLM、解析输出、连接向量库等基础能力,像是一堆零件。
- LangGraph:施工图。在 LangChain 的零件基础上,定义了"谁先执行、谁后执行、什么条件走哪条路"的流程控制。
感兴趣的可以看看这篇文章:https://www.datacamp.com/tutorial/langchain-vs-langgraph-vs-langsmith-vs-langflow
LangChain 本身也能做 Agent(AgentExecutor),但它的问题是:流程是黑盒的。你把工具和 Prompt 丢进去,它内部自己循环,你很难控制“什么时候该停”、“中间结果怎么传递”、“多个 Agent 怎么协作”。
LangGraph 把流程显式化了——你画一张图,节点是什么、边怎么连、条件是什么,一目了然。对于多 Agent 协作这种需要精确控制流程的场景,LangGraph 比 LangChain 的 AgentExecutor 好用得多。
| LangChain AgentExecutor | LangGraph | |
|---|---|---|
| 流程控制 | 黑盒,框架内部循环 | 白盒,你画图定义 |
| 多 Agent | 不原生支持 | 天然支持(多节点) |
| 条件路由 | 难实现 | 一行代码 |
| 状态管理 | 手动传递 | 自动维护 |
| 调试 | 难(看不到中间过程) | 易(逐节点 stream) |
| 适合场景 | 简单单 Agent | 复杂多 Agent 协作 |
其实在项目里,LangChain 的东西还是在用的(消息格式、工具定义、OpenAI SDK),只是不用它的 AgentExecutor,改用 LangGraph 来编排流程。
动手实现
LangGraph 基础用法
很多小伙伴不熟悉 LangGraph,所以在搭建多 Agent 之前,先看一个最简单的 LangGraph 示例,理解它的三个核心概念:State(状态)、Node(节点)、Edge(边)。
其实就是把代码调度更具体化了,像是在画“流程图”。
fromlanggraph.graphimportStateGraph,ENDfromtyping_extensionsimportTypedDict# 1. 定义状态:所有节点共享的数据classMyState(TypedDict):message:strcount:int# 2. 定义节点:就是普通函数,接收 state,返回要更新的字段defsay_hello(state):return{"message":f"你好!你已经来了{state['count']}次"}defadd_count(state):return{"count":state["count"]+1}# 3. 构建图:注册节点 + 连接边graph=StateGraph(MyState)graph.add_node("counter",add_count)graph.add_node("greeter",say_hello)graph.set_entry_point("counter")# 入口graph.add_edge("counter","greeter")# counter → greetergraph.add_edge("greeter",END)# greeter → 结束app=graph.compile()# 4. 运行result=app.invoke({"message":"","count":0})print(result)# {"message": "你好!你已经来了 1 次", "count": 1}就这么简单:定义状态 → 写几个函数 → 用边连起来 → Compile → Invoke(调用)。
LangGraph 真正强大的地方在于条件边——根据状态动态决定下一步走哪:
# 条件边:根据 count 决定走哪条路defshould_continue(state):ifstate["count"]>=3:return"done"return"again"graph.add_conditional_edges("greeter",should_continue,{"again":"counter",# count < 3 → 回到 counter(循环)"done":END,# count >= 3 → 结束})有了条件边,就能实现循环、分支、路由——这正是多 Agent 系统需要的能力。
Step 1:定义状态
LangGraph 的核心是 State——所有节点共享的数据结构。每个节点读取 state、处理、写回 state。
fromtypingimportAnnotated,Sequencefromtyping_extensionsimportTypedDictfromlangchain_core.messagesimportBaseMessageimportoperatorclassAgentState(TypedDict):"""所有节点共享的状态"""# 对话历史(自动追加)messages:Annotated[Sequence[BaseMessage],operator.add]# 意图列表(dispatcher 填写)intents:list[str]# 执行模式:sequential 或 parallelmode:str# 当前执行到第几个意图current_step:int关键设计:messages用operator.add标注,意味着每个节点返回的消息会追加到列表里,而不是覆盖,这样对话历史自动累积。
Step 2:实现 Dispatcher(意图分类)
Dispatcher 是整个系统的入口,负责判断用户想干什么:
DISPATCHER_PROMPT="""你是一个意图分类器。 分类规则: - tools:需要调用工具的请求(查天气、获取网页、查文档) - writer:写作类(写邮件、文案、翻译) - chat:其他(闲聊、知识问答) 如果需要多个步骤,按顺序列出。 回复格式:意图1,意图2|模式(sequential 或 parallel) 示例: - "上海天气" → tools|sequential - "查天气然后写文案" → tools,writer|sequential - "翻译hello,顺便查天气" → tools,writer|parallel """defdispatcher_node(state):last_user_msg=...# 取最后一条用户消息response=invoke_llm([SystemMessage(DISPATCHER_PROMPT),...])# 解析:intents + modeintents,mode=parse_response(response)return{"intents":intents,"mode":mode,"current_step":0}Dispatcher 的输出决定了后续走哪条路。注意它支持多意图。比如:“查天气然后写文案”会被拆成["tools", "writer"],串行执行。
Step 3:构建 LangGraph 图
这是最关键的部分——把所有节点和路由规则组装成一张图,我们先看流程设计:
最终代码实现:
fromlanggraph.graphimportStateGraph,ENDdefbuild_graph():graph=StateGraph(AgentState)# 注册节点graph.add_node("dispatcher",dispatcher_node)graph.add_node("mode_router",lambdastate:{})# 路由跳板graph.add_node("step_router",lambdastate:{})# 串行步骤跳板graph.add_node("router",router_node)# 工具决策graph.add_node("tool_executor",tool_executor_node)graph.add_node("writer_agent",writer_agent_node)graph.add_node("chat_agent",chat_agent_node)graph.add_node("advance_step",advance_step)# 推进到下一步graph.add_node("parallel_executor",parallel_executor_node)# 入口graph.set_entry_point("dispatcher")# dispatcher → mode_routergraph.add_edge("dispatcher","mode_router")# 根据 mode 分流graph.add_conditional_edges("mode_router",route_mode,{"sequential":"step_router","parallel":"parallel_executor",})# 串行:根据当前 intent 路由graph.add_conditional_edges("step_router",route_current_step,{"tools":"router","writer":"writer_agent","chat":"chat_agent","done":END,})# tools 路径:ReAct 循环graph.add_conditional_edges("router",should_use_tools,{"tools":"tool_executor","next":"advance_step",})graph.add_edge("tool_executor","router")# writer/chat 完成后推进graph.add_edge("writer_agent","advance_step")graph.add_edge("chat_agent","advance_step")# 检查是否还有下一步graph.add_conditional_edges("advance_step",has_next_step,{"continue":"step_router","done":END,})# 并行路径graph.add_edge("parallel_executor",END)returngraph.compile()Step 4:串行 vs 并行
串行:有依赖关系的任务。“查天气然后写文案”——writer 需要天气结果才能写。
并行:互不依赖的任务。“翻译hello world,顺便查天气”——两个任务没关系。
并行模式用ThreadPoolExecutor同时执行多个 Agent,最后让 LLM 把结果整合成一个连贯的回答。
Step 5:验证效果
# 单意图curl-XPOST /api/chat-d'{"message": "你好"}'# → dispatcher: chat → chat_agent → "你好!有什么可以帮你的?"# 单意图 + 工具curl-XPOST /api/chat-d'{"message": "上海天气"}'# → dispatcher: tools → router → get_weather → router → "上海晴天24°C"# 多意图串行curl-XPOST /api/chat-d'{"message": "查上海天气,写个朋友圈文案"}'# → dispatcher: [tools, writer] sequential# → tools(查天气)→ writer(基于天气写文案)→ 最终回复# 多意图并行curl-XPOST /api/chat-d'{"message": "翻译hello world,顺便查北京天气"}'# → dispatcher: [tools, writer] parallel# → 并发执行 → 整合回复刨根问底
| 序号 | 问题 |
|---|---|
| 1️⃣ | Q:Dispatcher 的意图分类准确吗?模糊场景怎么办? |
| A:不一定准。"帮我写个出行计划"到底是 tools 还是 writer?靠 Prompt 里的规则和示例来约束。模糊时兜底到 chat(最安全的路径)。后续可以加 few-shot 示例提升准确率。 | |
| 2️⃣ | Q:并行模式怎么整合多个 Agent 的结果? |
| A:用 LLM 整合。把多个 Agent 的输出拼在一起,让 LLM 生成一个连贯的最终回答。类似于"你有两段信息,请合并成一段自然的回复"。 | |
| 3️⃣ | Q:LangGraph 和直接写 Python 函数调用有什么区别? |
| A:小项目没区别。但当图变复杂(10+ 节点、多种条件路由、需要流式输出)时,LangGraph 的状态管理和条件边比手写 if-else 清晰得多。而且它内置了 stream 模式,后面做 SSE 推送时直接用。 |
本篇小结
- 多 Agent 的核心是分工——Dispatcher 做路由,专业 Agent 各管一摊;
- LangGraph 用图来描述 Agent 的执行流程,节点是函数,边是条件;
- 支持串行(有依赖)和并行(无依赖)两种多意图执行模式;
- dispatcher 的意图分类质量决定了整个系统的上限;
写在最后
多 Agent 架构看起来复杂,但核心思想很朴素:把大问题拆成小问题,让专业的人做专业的事。这和微服务架构的理念一模一样——单体应用拆成多个服务,每个服务职责单一,通过 API 协作。
如果你做过前端的组件化拆分,多 Agent 的设计思路你一定不陌生。
下一篇预告:核心服务架构搭好了,接下来让它真正能完整跑起来——Web 界面 + SSE 流式输出,把 Agent 从命令行变成一个能用的产品。
