Multi-Agent 这几年被讲得很多。讲概念时,它通常会被描述成一组智能体分工协作:有的负责规划,有的负责检索,有的负责执行,有的负责总结。听起来很自然,但真正做成产品时,问题会落到更具体的层面。
用户在页面上怎么配置多个 Agent?这些 Agent 之间的关系怎么表达?主 Agent 怎么知道自己可以调用哪些子 Agent?子 Agent 执行完以后,结果怎么回到主 Agent?如果过程出错,系统又怎么知道错在哪一步?
这篇文章不讨论 Multi-Agent 的宏大叙事,也不把它包装成某种神秘能力。我们只看 AIWorks 里Multi-Agent 是如何实现的。
为什么不把所有事情交给一个 Agent
单个 Agent 能处理很多事。最直接的做法,是把背景、角色、业务规则、任务描述、工具使用说明、输出格式都写进一个很长的 prompt 里,让模型自己判断下一步该做什么。
简单应用这么做没问题。甚至很多内部工具一开始就是这么做的:一个 Agent,一个 prompt,几个工具,能跑就行。但应用复杂以后,问题会很快出现。
- 第一,prompt 会越来越长。一个 Agent 既要理解用户问题,又要判断是否需要知识库,又要决定是否调用工具,还要组织最终输出。职责越多,prompt 越难维护。后面想改一个能力,很容易影响另一个能力。
- 第二,调试边界会变得模糊。一次回答不符合预期,到底是需求理解错了,知识检索结果不对,工具参数生成错了,还是最终总结出了问题?如果所有逻辑都混在一个 Agent 里,排查时只能反复改 prompt。
- 第三,产品配置不够清楚。对应用搭建者来说,一个复杂 Agent 到底包含哪些能力,哪些能力可以单独调整,哪些能力应该被复用,很难从一大段 prompt 里看出来。
Multi-Agent 的意义不在于“多放几个模型就更智能”。更现实的理解是:它是一种拆分复杂度的方式。主 Agent 负责理解用户输入和协调任务,子 Agent 承担更明确的专项任务。每个 Agent 有自己的 prompt、工具和知识库,职责边界更容易看见,也更容易调试。AIWorks 的Multi-Agent 编排页面就是围绕这个思路设计的。
AIWorks 的Multi-Agent可视化编排界面
在Agent编排页面里,我们设计的用户能够操作的节点类型很克制,只有start和agent两类节点。start表示应用的入口,agent表示一个可配置、可运行的智能体。每个 Agent 面板里可以选择模型、编写 prompt、配置工具、知识库和子Agent。多个 Agent 之间通过连线表达可调用关系。
这个产品形态的选择很重要,它不仅会影响用户理解系统的方式,也会影响后端的实现。从产品视角看,这种设计形态相当于让用户绘制一张 Agent 协作图,从而也能更直观地看到整体拓扑图。
Multi-Agent 可视化编排强调的是调用关系,而不是流程步骤的表达,因此与普通流程图有所不同。在 Agent 可视化编排页面中,多个 Agent 之间使用虚线连接,虚线表示“可被调用”,而不是“必然调用”。这一点区别很重要,普通工作流更强调确定性,节点 A 跑完,满足条件就去节点 B,否则去节点 C,执行路径由流程规则决定。Multi-Agent应用则多了一层模型决策。主 Agent 会根据用户问题、自己的 prompt、可用工具和可用子 Agent,判断这次是否需要调用某个子 Agent。也就是说,前端连线定义了“能力边界”,模型输出决定了“本次调用”。
可视化编排背后的数据结构
前端页面上的节点和连线,保存时会被序列化为一个包含nodes和edges的JSON结构,结构大体如下:
{
"nodes": [
{"id": "node id1", "type": "start", "data": {...}},
{"id": "node id2", "type": "agent", "data": {
"name":"agent node name",
"instruction": "...",
"model_name": "...",
"tools": [...],
"sub_agents": [...]
}},
{"id": "node id3", "type": "agent", "data": {...}}
],
"edges": [
{"source": "node id1", "target": "node id2"},
{"source": "node id2", "target": "node id3"}
]
}
后端收到这份数据结构后进行解析,生成Graph对象,Graph对象包含以下信息:
nodes:节点列表。多 Agent 编排页面里主要是start和agent。edges:节点之间的连线,包含 source 和 target。node.data:节点配置。对 Agent 来说,这里会包含模型、prompt、工具、知识库、子 Agent 等信息。root_node_id:执行入口,通常来自 start 节点。node_id_config_mapping:节点配置映射。edge_mapping:某个节点有哪些下游节点的映射。reverse_edge_mapping:某个节点有哪些上游节点的映射。
解析生成Graph是把节点配置、边关系、执行入口打包成一个 Graph 数据结构,供后续GraphEngine使用。
GraphEngine:两种模式,一套底座
GraphEngine 是 AIWorks 的图执行引擎,基于 LangGraph 构建。
它支持两种模式:
- Workflow 模式:普通工作流,节点按拓扑顺序执行,支持 IF-ELSE 条件分支,执行路径在构建时基本确定。
- Agent 模式:Multi-Agent 场景,主 Agent 可以在运行时动态决定调用哪个子 Agent,执行路径不是静态固定的。
两种模式用同一套底座——LangGraph 的 StateGraph——来组织节点和边。区别在于 Agent 模式里多了一套专门处理子 Agent 动态路由的机制。
为什么选 LangGraph 做底座?
LangGraph 的核心能力是基于状态图的流程控制,它天然支持三件事:
- 状态在节点之间流转并合并(并行节点执行完后,各自产生的状态能被正确归并)
- 条件边(节点执行完后,根据状态决定走哪条路)
- 动态并行分发(一个节点可以同时把任务发给多个下游节点)
这三个能力,正好覆盖了 Multi-Agent 调度的核心场景。
GraphEngine 负责把Graph这张图跑起来,在Agent模式下,GraphEngine.build_agent_app() 会基于 Graph 构建 LangGraph 应用,它会注册Start和Agent节点,使用SubAgentBranchNode来表示Agent与子Agent的关系,SubAgentBranchNode分支节点会根据父 Agent 的状态和 pending 子任务决定下一步。
跨节点的「公共内存」:VariablePool
多个 Agent 协作,必须有个地方存放跨节点传递的数据。AIWorks 用 VariablePool 解决这个问题。
它的结构很简单:一个装用户输入的区域,一个装系统内置变量(query、user_id、app_id 等)的区域,加上一个通用的 pool 字典——节点的输出写进这里,下游节点从这里读取。
变量命名遵循 {节点id}.{变量名} 的规范。比如,主 Agent 的消息历史存在 agent-main.messages,主 Agent 发起的子 Agent 调用信息存在 agent-main.subagent_calls,某个子 Agent 的输入 query 存在 agent-spot.sys.query。这个命名空间设计避免了不同节点之间的变量污染。
GraphRuntimeState 是 LangGraph 的运行时状态对象,它把 VariablePool、节点状态映射等关键字段包在一起,在整个图执行过程中流转。当多个节点并行执行时,LangGraph 负责把各自产生的状态合并回主状态,VariablePool 的更新也通过这个机制统一同步。
Agent 节点内部:ReAct 循环
单个 AgentNode 的运行逻辑是一个 ReAct(Reasoning + Acting)循环,结构很经典:
开始
↓
调用模型(携带历史消息和可用工具列表)
↓
模型返回了 tool_call?
├─ 是 → 执行对应工具,把结果追加到消息历史,回到「调用模型」
└─ 否 → 生成最终回答,结束
执行前,Agent 会先做两件准备工作:
知识库检索在消息构造阶段完成。 如果 Agent 配置了知识库,系统会先用用户 query 做向量检索,把检索结果拼进系统提示词。模型看到的是一个内容更丰富的 system prompt,它不需要知道这些内容是怎么来的。
工具列表在调用前统一构建。 普通工具和子 Agent 都会进入这个列表,具体机制下面会展开说。
循环过程中,每次拿到 tool_call,系统会先判断:这是普通工具调用,还是子 Agent 调用?
- 普通工具调用:直接执行,把结果追加到消息,继续循环。
- 子 Agent 调用:不在当前循环里执行,而是把调用信息写入 VariablePool,然后退出循环,把控制权交还给图引擎。
还有一个细节值得注意:只有最顶层 Agent 才会把内容流式推给前端。子 Agent 的执行结果不会直接展示给用户,而是作为工具结果返回给父 Agent。Agent 节点会检查自己的上游是否是另一个 Agent,以此判断自己是否处于子 Agent 角色。
最核心的设计:子 Agent 也是一种工具
AIWorks Multi-Agent 最关键的一个设计决策是:子 Agent 对主 Agent 的大模型来说,和普通工具没有区别——都通过 function calling 暴露。
构建工具列表时,系统会同时处理两类条目:
- 普通工具:从工具管理器获取运行时,转换成模型可识别的 function definition。
- 子 Agent:也被包装成 function definition,名字由「Agent 名称 + 节点 id」拼成(防止同名冲突),描述来自 Agent 节点的 description 配置,参数只有一个核心字段
request(字符串类型,传入给子 Agent 的任务描述)。
所以对主 Agent 的模型来说,它看到的可用工具是如这样一个列表:
可用工具:
- current_time() → 获取当前时间
- query_weather() → 查询天气
- 旅游景点推荐_agent-spot(request) → 景点推荐专家
- 旅游规划_agent-plan(request) → 行程规划专家
模型根据当前对话上下文和工具描述,自主判断该不该调用某个能力。子 Agent 在模型眼里就是一个可调用函数,它不需要知道背后还有一套完整的 Agent 执行系统。
这个设计把职责分得很清:模型负责判断要不要调用、调用时 request 是什么;AgentNode 负责解析 tool_calls,区分普通工具和子 Agent;GraphEngine 负责接管子 Agent 调用,完成路由和调度。
子 Agent 跑完之后,结果怎么回来
子 Agent 执行完之后,结果不能直接返回给用户。它要先回填给父 Agent,让父 Agent 基于这个结果继续推理,决定下一步——要么再调用另一个子 Agent,要么已经有足够信息可以生成最终回答了。
回填过程:
- 找到当前节点(子 Agent)对应的父 Agent 节点。
- 把 VariablePool 里该子 Agent 对应的调用状态更新为
succeeded。 - 把子 Agent 的输出结果包装成一条 tool message,追加进父 Agent 的消息历史。
完成后,SubAgentBranchNode 会把执行流路由回父 Agent。父 Agent 被重新调度时,它的消息历史里已经有了子 Agent 的结果,可以继续下一轮推理。
这个闭环是:
父 Agent 决策
→ 子 Agent 执行
→ 结果以 tool message 回填
→ 父 Agent 继续推理
→ 直到模型不再触发任何 tool call,输出最终答案
这延续了 function calling 的标准语义。父 Agent 不需要知道子 Agent 的实现细节,只需要知道「我调用了这个能力,它给我返回了这个结果」。
事件流:让执行过程对人可见
Multi-Agent 应用如果只返回最终答案,出了问题基本只能猜。不知道主 Agent 有没有调子 Agent,不知道子 Agent 给回了什么,不知道哪一步失败了。
AIWorks 用 SSE(Server-Sent Events)把执行过程实时推送出来。整个执行链路的每个关键步骤都有对应事件:
| 事件类型 | 含义 |
|---|---|
flow_start | 一次执行开始 |
agent_start | Agent 开始执行 |
agent_message | Agent 产生了模型调用或工具调用记录 |
response_delta | 流式文本片段(打字机效果) |
agent_end | Agent 执行完成 |
node_start / node_end | 普通节点开始或结束(工作流模式下) |
flow_end | 整次执行结束 |
前端订阅这个事件流,可以实时展示:当前在执行哪个 Agent、是在调用模型还是执行工具、有没有子 Agent 被触发、整体是成功还是失败。
事件的推送机制是这样串起来的:节点内部执行时,通过 LangGraph 提供的 stream_writer 把自定义事件写出去;外层 GraphEngine 以 custom 模式消费 LangGraph 的事件流,把这些事件转发给 API 层;API 层用 EventSourceResponse 以 SSE 格式推给前端。
每次执行还会被持久化成运行记录,保存 graph 结构和各节点执行状态,方便事后复盘。
结语
AIWorks 的 Multi-Agent 可以理解成一条从“编排图”到“执行图”的链路。
前端用 start 和 agent 节点表达多 Agent 应用结构。Agent 面板里配置 prompt、工具和知识库。Agent 之间的连线表达可调用关系。后端把这些节点和连线解析成 Graph。运行时,子 Agent 被包装成 function tool,由父 Agent 通过模型决策触发,再由 GraphEngine 完成路由、状态管理和结果回填。
这套设计没有把 Multi-Agent 处理成纯提示词技巧,而是落到了可配置、可执行、可观察的工程机制上。
它也没有让大模型接管所有控制权。模型负责判断要调用哪个能力,系统负责维护边界、执行流程和状态。这种分工对真正做 AI 应用会更稳一些。
如果只看产品界面,多 Agent 编排像是在画图。如果往后端实现里看,这张图背后其实是一套运行协议:Agent 定义能力,连线定义可调用关系,function calling 表达模型意图,图执行引擎负责调度,变量池和消息回填保证协作闭环。
这也是 AIWorks 这套 Multi-Agent 实现最值得关注的地方。它没有把智能体协作停留在概念上,而是把协作关系放进了产品配置和运行时系统里。对 AI 应用开发来说,这比单纯堆 prompt 更接近可维护的工程形态。