数据截至 (上游 commit fd6b9a6352af)
自动构建:从一句话目标到可运行的多智能体工作流
30 秒导读: 你给一句话目标(比如"生成一个能在浏览器里玩的俄罗斯方块"),
WorkFlowGenerator就自动帮你把它拆成一串带依赖的子任务、给每个子任务配好一个专属 agent,最后拼成第 3 章 讲的那种WorkFlowGraph——直接能跑。本章讲清这条"从 goal 到图"的流水线怎么走通。
本章只讲怎么生成这张图。生成之后如何被评测、如何被优化器反复打磨,是 自演化引擎的事,这里不碰。
1. 这是什么(零基础也能懂)
一句话定义
WorkFlowGenerator 是 EvoAgentX 的自动建图器:输入一个高层目标字符串,输出一张
可执行的多智能体工作流图。中间的"拆任务""配 agent"全交给大模型(LLM)来做。
它解决什么问题
平时你要搭一个多 agent 流程,得自己动手:
- 想清楚这活儿分几步、谁先谁后;
- 每一步写一个 agent,定义它的输入、输出、prompt;
- 把这些步骤的输入输出对上,连成一张图。
这套手工活又慢又容易漏。WorkFlowGenerator 把它自动化:你只说"我要什么",它替你把
"怎么分工、谁配合谁"全排好。
用起来什么样
来自 examples/workflow/workflow_demo.py:23-24 的真实用法——两行就出一张图:
wf_generator = WorkFlowGenerator(llm=llm) # 只需给它一个 LLM
workflow_graph = wf_generator.generate_workflow( # 一句话目标进去
goal="Generate html code for the Tetris game ..." # 一张可执行图出来
)
拿到 workflow_graph 后,交给 AgentManager + WorkFlow 就能真正跑
(workflow_demo.py:33-37)。这一章只负责把 workflow_graph 造出来。
一句话直觉
把它想成一个自动项目经理:你丢给它一个项目目标,它先写出任务分解(WBS), 再给每个任务招一个"专才"(agent),最后画出任务甘特图上的依赖箭头。
2. 顶层全景(它大概怎么转)
三步走的流水线
WorkFlowGenerator 编排三个组件依次干活,核心就是一条从左到右的流水线:
goal (一句话字符串)
│
▼
┌──────────────────┐ "把大目标拆成带依赖的子任务"
│ ① TaskPlanner │ agents/task_planner.py
│ 任务规划器 │──► 产出 子任务列表 (List[WorkFlowNode])
└──────────────────┘ 每个子任务带 name/description/inputs/outputs
│
▼
┌──────────────────┐ "按输入输出名对暗号,自动连边"
│ ② build_workflow │ workflow_generator.py:201
│ 组装成图 │──► 产出 WorkFlowGraph(节点+边,暂无 agent)
└──────────────────┘
│
▼
┌───────── ─────────┐ "给每个子任务现造 / 复用一个 agent"
│ ③ AgentGenerator │ agents/agent_generator.py
│ 智能体生成器 │──► 把 agent 规格塞回每个节点 node.set_agents(...)
└──────────────────┘
│
▼
可执行的 WorkFlowGraph
(第 3 章那种图,每个节点都配好了 agent)
怎么读这张图:从上到下是时间顺序,①②③ 依次执行,数据一路往下流。①出"任务", ②把任务连成"图",③给图上每个任务"配人"。
三个部件各司其职
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
TaskPlanner | 把 goal 拆成带依赖的子任务列表 | agents/task_planner.py:6 |
AgentGenerator | 为每个子任务选已有 agent 或现造新 agent | agents/agent_generator.py:6 |
WorkFlowReviewer | 对生成的图给反馈以迭代改进(当前是占位,未实现) | agents/workflow_reviewer.py:6 |
WorkFlowGenerator | 编排上面三者、把结果拼成 WorkFlowGraph | workflow/workflow_generator.py:20 |
注意最后一栏:图里画的 WorkFlowReviewer 在当前 commit 其实是个占位实现,
后面 §6 会诚实交代——生成流程实际只走了 ①②③ 里的规划和生成两步。
主线走一遍(不进代码)
看 generate_workflow 的骨架(workflow_generator.py:100),整条主线就四步:
- 规划:调
generate_plan,拿到子任务列表(workflow_generator.py:109-116); - 建图:调
build_workflow_from_plan,把子任务连成图(:120-126); - 校验:
workflow._validate_workflow_structure()确认图结构合法(:131); - 配 agent:调
generate_agents,给每个节点塞进 agent 规格(:136-143),再校验每个节点都有 agent(:149-151)。
每一步都被包在 _execute_with_retry(workflow_generator.py:70)里——LLM 输出不稳定,
失败了就指数退避重试,重试次数由 retry 参数控制。
3. 核心原理(逐个机制,由浅入深)
3.1 第一步:把 goal 拆成带依赖的子任务(TaskPlanner)
它要解决的小问题: 一句话目标太笼统,没法直接执行。得先切成一个个"目标明确、 自成一体"的小任务,并且理清谁依赖谁。
思路/直觉: 这活儿本身就是 LLM 擅长的——给它目标 + 一套拆解原则(简单、模块化、 不冗余……),让它吐出结构化的子任务清单。关键设计是:子任务之间不靠显式"连线"来 表达依赖,而是靠"输入输出的名字对暗号"。
规划的产物类型是 TaskPlanningOutput,里面就一个字段(actions/task_planning.py:20-24):
class TaskPlanningOutput(ActionOutput):
sub_tasks: List[WorkFlowNode] = Field(...) # 直接就是一串 WorkFlowNode
也就是说,LLM 吐出的每个子任务,解析后直接就是一个 WorkFlowNode——图的节点在
规划这一步就已经成型了,只是还没连边、还没配 agent。
每个子任务长什么样。 prompt 强制 LLM 按固定 JSON 结构输出每个子任务
(prompts/task_planner.py:141-167),字段如下:
| 字段 | 含义 |
|---|---|
name | 子任务唯一名字 |
description | 这个子任务要干什么 |
reason | 为什么需要它、如何贡献于总目标 |
inputs | 需要哪些输入(每个带 name/type/required/description) |
outputs | 产出哪些输出 |
两条关键规则(依赖关系的命门)。 prompt 里两条硬约 束决定了后面怎么连边:
- 一个子任务的
inputs只能来自用户的goal或它前置子任务的outputs(prompts/task_planner.py:177); - 第一个子任务必须只有一个输入,名字就叫
goal(prompts/task_planner.py:180-190)。
这两条保证了子任务串成一条"输入输出首尾相接"的链——正是下一步自动连边的依据。
真实实现。 generate_plan 把 goal 交给 TaskPlanner 执行,LLM 以 JSON 模式解析出结构化结果:
# workflow_generator.py:155 generate_plan
message = task_planner.execute(
action_name=task_planning_action_name,
action_input_data={"goal": goal, "history": history, "suggestion": suggestion},
return_msg_type=MessageType.REQUEST
)
return message.content # 即 TaskPlanningOutput
底层是 TaskPlanning.execute(actions/task_planning.py:43)把 prompt 填好后调
llm.generate(..., parser=self.outputs_format, parse_mode="json")(:70-75)——
parse_mode="json" 让 LLM 输出被解析进 TaskPlanningOutput。这套"结构化输出解析"
的机制,见 地基章。
TaskPlanner本身是个标准 Agent(agents/task_planner.py:6),它随身带一个TaskPlanningaction(task_planner.py:24),prompt/system_prompt 来自prompts/task_planner.py。Agent 与 Action 的关系见 执行单元章。
3.2 第二步:按输入输出名"对暗号",自动连边(build_workflow_from_plan)
它要解决的小问题: 有了一堆子任务节点,怎么知道该在哪两个之间画箭头?
思路/直觉: 上一步已经埋好线索——子任务的 inputs 只能来自前置子任务的 outputs。
所以只要 A 的某个输出名 出现在 B 的输入名里,就说明 B 依赖 A,连一条 A→B 的边。
不需要再问 LLM,纯靠名字匹配就能推出全部依赖。
图示:
任务A 任务B
outputs: [problem_analysis] ───┐
│ "problem_analysis" 这个名字
│ 同时出现在 A 的输出和 B 的输入里
▼
inputs: [goal, problem_analysis] ==> 连一条边 A ──► B
原理演示(示意,非源码):
# 对每一对不同的子任务,检查"A 的输出名"是否落进"B 的输入名"
for A in nodes:
for B in nodes:
if A is B:
continue
a_outputs = [p.name for p in A.outputs] # A 产出的名字
b_inputs = [p.name for p in B.inputs] # B 需要的名字
if any(name in b_inputs for name in a_outputs):
edges.append(edge(A.name, B.name)) # 有交集 => A 依赖被 B 用 => 连边
真实实现。 与上面几乎一一对应(workflow_generator.py:201-214):
def build_workflow_from_plan(self, goal, plan):
nodes = plan.sub_tasks
edges = []
for node in nodes:
for another_node in nodes:
if node.name == another_node.name:
continue
node_output_params = [param.name for param in node.outputs]
another_node_input_params = [param.name for param in another_node.inputs]
if any([param in another_node_input_params for param in node_output_params]):
edges.append(WorkFlowEdge(edge_tuple=(node.name, another_node.name)))
return WorkFlowGraph(goal=goal, nodes=nodes, edges=edges)
边的表示是 WorkFlowEdge,构造时用一个 (source, target) 元组(workflow/workflow_graph.py:524、
:542)。节点 + 边一起塞进 WorkFlowGraph(goal=..., nodes=..., edges=...)
(workflow/workflow_graph.py:585)——这就是第 3 章的那种图,已经有结构了,只差 agent。
关键细节: 建完图立刻校验结构 _validate_workflow_structure()
(workflow/workflow_graph.py:847)——查输入输出重名、孤立节点、有没有初始节点等。
比如"初始节点"就是那些**只依赖 workflow 输入(即 goal)**的节点(workflow_graph.py:864-877),
正好对应 §3.1 里"第一个子任务只有 goal 输入"那条规则。
3.3 第三步:为每个子任务现造或复用一个 agent(AgentGenerator)
它要解决的小问题: 图有了、任务清楚了,但每个任务得有"人"来干。这个"人"从哪来?
思路/直觉: 逐个子任务问 LLM 两件事——
- 现有的候选 agent 里有没有能直接用的?有就选它(记名字);
- 没有合适的,就照着子任务的输入输出现造一个新 agent,连 prompt 一 起生成。
产出类型 AgentGenerationOutput 正好两个字段,对应这两条路(actions/agent_generation.py:120-123):
class AgentGenerationOutput(ActionOutput):
selected_agents: List[str] # 选中的已有 agent 的名字
generated_agents: List[GeneratedAgent] # 现造的新 agent 规格
逐个子任务地生成。 generate_agents 遍历图里每个节点,把"总目标 + 整张图描述 + 当前
子任务"喂给 AgentGenerator,拿回 agent 规格后塞回该节点(workflow_generator.py:180-197):
for subtask in workflow.nodes: # 逐个子任务
subtask_desc = json.dumps(...) # 把这个子任务序列化成 JSON
agents = agent_generator.execute( # 问 LLM:选谁 / 造谁
action_name=agent_generation_action_name,
action_input_data={"goal": goal, "workflow": workflow_desc, "task": subtask_desc},
...
).content
generated_agents = [a.to_dict(...) for a in agents.generated_agents]
subtask.set_agents(agents=generated_agents) # 把 agent 规格挂到节点上
一个诚实的实现细节: 代码里有句注释
# todo I only handle generated agents(workflow_generator.py:191)——当前只把新造的generated_agents挂回节点,selected_agents(复用已有 agent 的那条路)在这个编排里还没被真正接上。所以实践中, 每个子任务基本都是"现造"出来的。
新造的 agent 长什么样。 每个 GeneratedAgent 是一份完整规格
(actions/agent_generation.py:29-39):name、description、inputs、outputs、prompt、
可选的 tool_names。也就是说,LLM 不只是决定"要个 agent",还把这个 agent 的执行 prompt
都替你写好了。
一个巧妙的自我修复: GeneratedAgent 有个 validate_prompt 校验器
(actions/agent_generation.py:52-117),在 agent 生成后自动做三件事:
- 检查所有输入变量都在 prompt 里被引用了(缺了就报错,
:74-79); - 把 prompt 里
### Instructions段的输入占位符规整成<input>{name}</input>格式(:82-92); - 检查
### Output Format段声明的输出名和 agent 的outputs对得上;对不上时,用词相似度 找最接近的输出名自动替换(:99-115)。
这一步很关键:LLM 写 prompt 时经常把输出名写走样,这个校验器兜底修正,让生成的 agent 更可能真的能跑。
真实执行。 AgentGeneration.execute(actions/agent_generation.py:147)填好 prompt 后同样
调 llm.generate(..., parser=outputs_format, parse_mode="json")(:187-192)。它的 prompt
(prompts/agent_generator.py:170)详细规定了 agent 该怎么选、怎么造、prompt 模板长什么样。
3.4 生成的 agent 规格,如何变成能跑的 agent
上一步挂到节点上的只是字典规格,不是活的 agent 对象。真正"复活"发生在使用侧:
AgentManager.add_agents_from_workflow(agents/agent_manager.py:204)遍历图里每个节点的
node.agents,对每个规格调 add_agent(:174);字典规格最终经
create_agent_from_dict(utils/utils.py:222)被实例化成一个
CustomizeAgent(utils/utils.py:233)。
也就是说:"现造一个 agent"= 生成一份 CustomizeAgent 的配置字典。 CustomizeAgent
这个"用配置就能造出 agent"的机制,正是 执行单元章 的主角——
自动构建之所以能"凭空造 agent",全靠它。
用法上就一行(examples/workflow/workflow_demo.py:33-34):
agent_manager = AgentManager()
agent_manager.add_agents_from_workflow(workflow_graph, llm_config=openrouter_config)
4. 端到端一条真实路径
把一整条路串起来,以 demo 的目标为例(examples/workflow/workflow_demo.py:20):
goal = "Generate html code for the Tetris game ..."
│
│ ① TaskPlanner (task_planning.py) —— LLM 拆解
▼
sub_tasks = [
{name: "requirements_analysis", inputs:[goal],
outputs:[analyzed_requirements]},
{name: "code_generation", inputs:[goal, analyzed_requirements],
outputs:[html_code]},
...
] # 示意结构,实际子任务由 LLM 决定
│
│ ② build_workflow_from_plan (workflow_generator.py:201) —— 名字对暗号连边
▼
WorkFlowGraph:
requirements_analysis ──(analyzed_requirements)──► code_generation ──► ...
│ └ 因为该名字同时是前者输出、后者输入
│
│ ③ AgentGenerator (agent_generation.py) —— 逐节点现造 agent
▼
每个节点挂上一个 GeneratedAgent 规格(name/inputs/outputs/prompt/tool_names)
│
│ 校验:每个节点都必须有 agent(workflow_generator.py:149-151)
▼
可执行 WorkFlowGraph ──► AgentManager 把规格实例化成 CustomizeAgent ──► WorkFlow 执行
对照三种数据形态的转换:
| 阶段 | 数据形态 | 由谁产出 |
|---|---|---|
| 输入 | goal 字符串 | 用户 |
| 规划后 | List[WorkFlowNode](子任务) | TaskPlanner / TaskPlanningOutput |
| 建图后 | WorkFlowGraph(节点+边,无 agent) | build_workflow_from_plan |
| 配 agent 后 | WorkFlowGraph(每节点带 agent 规格) | AgentGenerator / generate_agents |
| 使用侧 | 每节点的规格 → CustomizeAgent 实例 | AgentManager |
5. 巧妙之处(可借鉴的技术)
-
依赖不靠 LLM 显式画,靠"输入输出名对暗号"推出来。 让 LLM 只管拆任务、约定好 "输入只能来自前置输出",连边就退化成一次纯名字匹配(
workflow_generator.py:205-212)。 少问 LLM 一次,还更不容易出错。 -
生成即校验,坏输出当场修。
GeneratedAgent.validate_prompt(actions/agent_generation.py:52) 在 agent 一生成就检查 prompt 里输入输出对不对,用相似度自动纠正走样的输出名(:99-115)—— 把 LLM 的不靠谱在源头兜住。 -
每步都能重试、指数退避。
_execute_with_retry(workflow_generator.py:70)把规划、 建图、配 agent 每一步都包起来,失败按2 ** cur_retries秒退避重试(:95-97), 对付 LLM 偶发的解析失败。 -
"造 agent"复用了
CustomizeAgent这块地基。 生成器只需吐一份配置字典,复活交给create_agent_from_dict(utils/utils.py:222)——生成逻辑和 agent 运行时彻底解耦。
6. 边界与局限(诚实交代)
-
WorkFlowReviewer尚未接入。 类本身只 是占位,execute直接raise NotImplementedError(agents/workflow_reviewer.py:12-13)。WorkFlowGenerator.init_module里初始化它的代码是被注释掉的 TODO(workflow_generator.py:54-58)。所以"生成→给反馈→ 再改进"的 review 迭代当前并未真正运行。 -
num_turns定义了却没用上。 字段声明为"精修迭代次数"(workflow_generator.py:40), 但generate_workflow(:100)的主流程里没有围绕num_turns的循环——真正的多轮 精修依赖尚未实现的 reviewer。当前生成本质是一趟过(规划一次、建图一次、逐节点配 agent 一次)。 -
只处理"新造"的 agent。 如 §3.3 所述,
generate_agents有# todo I only handle generated agents(workflow_generator.py:191),selected_agents(复用已有 agent)在这条 编排链里还没被真正消费。 -
goal 太短会被直接拒。 少于 10 个字符就
raise ValueError(workflow_generator.py:102-103)—— 它要的是一句描述清楚的目标,不是一个词。 -
强依赖 LLM 遵守输出契约。 整条流水线建立在 LLM 能吐出合规 JSON 之上;虽然有重试和 校验兜底,但子任务拆得好不好、agent prompt 写得对不对,最终取决于所用模型的能力。
7. 横向对比(与本组其它章的关系)
| 想了解 | 去哪一章 |
|---|---|
| 生成出来的图长什么样、怎么被调度执行 | 03-workflow-execution.md |
"现造一个 agent"里的 CustomizeAgent 到底怎么工作 | 02-agents-actions.md |
parse_mode="json" 这套结构化输出解析的底层 | 01-core-foundation.md |
| 图生成之后如何被评测、被优化器打磨 | 05-self-evolution-optimizers.md |
| 生成的 agent 如何用工具 / 记忆 / RAG | 06-tools-memory-rag-hitl.md |
一句话定位:本章是 EvoAgentX "自动化"链条的入口——从 goal 造出图;第 3 章负责跑这张图; 第 5 章负责让这张图越跑越好。
8. 代码地图(导航索引)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 编排入口:三步流水线 | evoagentx/workflow/workflow_generator.py:100 | WorkFlowGenerator.generate_workflow |
| 生成器类 & 三个组件字段 | evoagentx/workflow/workflow_generator.py:20 | WorkFlowGenerator(task_planner/agent_generator/workflow_reviewer/num_turns) |
| 第①步:调规划器 | evoagentx/workflow/workflow_generator.py:155 | WorkFlowGenerator.generate_plan |
| 第②步:按名字对暗号连边 | evoagentx/workflow/workflow_generator.py:201 | WorkFlowGenerator.build_workflow_from_plan |
| 第③步:逐节点配 agent | evoagentx/workflow/workflow_generator.py:168 | WorkFlowGenerator.generate_agents |
| 每步重试/退避 | evoagentx/workflow/workflow_generator.py:70 | WorkFlowGenerator._execute_with_retry |
| 规划器 agent | evoagentx/agents/task_planner.py:6 | TaskPlanner |
| 规划 action & 产出结构 | evoagentx/actions/task_planning.py:27 / :20 | TaskPlanning / TaskPlanningOutput |
| 规划 prompt(拆解规则) | evoagentx/prompts/task_planner.py:123 | TASK_PLANNING_ACTION_INST |
| 生成器 agent | evoagentx/agents/agent_generator.py:6 | AgentGenerator |
| 生成 action & 产出结构 | evoagentx/actions/agent_generation.py:126 / :120 | AgentGeneration / AgentGenerationOutput |
| 单个生成 agent + 自修复校验 | evoagentx/actions/agent_generation.py:29 / :52 | GeneratedAgent / GeneratedAgent.validate_prompt |
| 评审器(占位,未实现) | evoagentx/agents/workflow_reviewer.py:6 | WorkFlowReviewer |
| 图/节点/边数据模型 | evoagentx/workflow/workflow_graph.py:585 / :49 / :524 | WorkFlowGraph / WorkFlowNode / WorkFlowEdge |
| 结构校验 | evoagentx/workflow/workflow_graph.py:847 | WorkFlowGraph._validate_workflow_structure |
| 规格→CustomizeAgent 实例化 | evoagentx/agents/agent_manager.py:204 → evoagentx/utils/utils.py:222 | add_agents_from_workflow → create_agent_from_dict |
| 端到端使用示例 | examples/workflow/workflow_demo.py:23-37 | main |