系列简介:从零搭建一个多 Agent AI 助手,覆盖原理、实现、部署全链路。不讲空话,每篇都有可运行的代码。
项目地址:github.com/CodeMomentY…
本篇目标:用 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 的零件基础上,定义了"谁先执行、谁后执行、什么条件走哪条路"的流程控制。
感兴趣的可以看看这篇文章:www.datacamp.com/tutorial/la…
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(边)。
其实就是把代码调度更具体化了,像是在画“流程图”。
from langgraph.graph import StateGraph, END
from typing_extensions import TypedDict
# 1. 定义状态:所有节点共享的数据
class MyState(TypedDict):
message: str
count: int
# 2. 定义节点:就是普通函数,接收 state,返回要更新的字段
def say_hello(state):
return {"message": f"你好!你已经来了 {state['count']} 次"}
def add_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 → greeter
graph.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 决定走哪条路
def should_continue(state):
if state["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。
from typing import Annotated, Sequence
from typing_extensions import TypedDict
from langchain_core.messages import BaseMessage
import operator
class AgentState(TypedDict):
"""所有节点共享的状态"""
# 对话历史(自动追加)
messages: Annotated[Sequence[BaseMessage], operator.add]
# 意图列表(dispatcher 填写)
intents: list[str]
# 执行模式:sequential 或 parallel
mode: 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
"""
def dispatcher_node(state):
last_user_msg = ... # 取最后一条用户消息
response = invoke_llm([SystemMessage(DISPATCHER_PROMPT), ...])
# 解析:intents + mode
intents, mode = parse_response(response)
return {"intents": intents, "mode": mode, "current_step": 0}
Dispatcher 的输出决定了后续走哪条路。注意它支持多意图。比如:“查天气然后写文案”会被拆成 ["tools", "writer"],串行执行。
Step 3:构建 LangGraph 图
这是最关键的部分——把所有节点和路由规则组装成一张图,我们先看流程设计:
最终代码实现:
from langgraph.graph import StateGraph, END
def build_graph():
graph = StateGraph(AgentState)
# 注册节点
graph.add_node("dispatcher", dispatcher_node)
graph.add_node("mode_router", lambda state: {}) # 路由跳板
graph.add_node("step_router", lambda state: {}) # 串行步骤跳板
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_router
graph.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)
return graph.compile()
Step 4:串行 vs 并行
串行:有依赖关系的任务。“查天气然后写文案”——writer 需要天气结果才能写。
并行:互不依赖的任务。“翻译hello world,顺便查天气”——两个任务没关系。
并行模式用 ThreadPoolExecutor 同时执行多个 Agent,最后让 LLM 把结果整合成一个连贯的回答。
Step 5:验证效果
# 单意图
curl -X POST /api/chat -d '{"message": "你好"}'
# → dispatcher: chat → chat_agent → "你好!有什么可以帮你的?"
# 单意图 + 工具
curl -X POST /api/chat -d '{"message": "上海天气"}'
# → dispatcher: tools → router → get_weather → router → "上海晴天24°C"
# 多意图串行
curl -X POST /api/chat -d '{"message": "查上海天气,写个朋友圈文案"}'
# → dispatcher: [tools, writer] sequential
# → tools(查天气)→ writer(基于天气写文案)→ 最终回复
# 多意图并行
curl -X POST /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 从命令行变成一个能用的产品。