使用过Dify的同学都知道,你可以在上面拖动方框和箭头来编排大模型的逻辑,如下图所示。

这种拖动框图编排工作流的方式,确实非常简单方便,以至于不会代码的人也可以用来编排大模型Agent。但你有没有考虑过一个问题——你作为一个工程师,有没有可能通过写代码的形式来编排工作流?否则你和不懂代码的人相比有什么竞争力?
CrewAI是一个Agent开发框架,通过它可以非常方便地开发Agent。它提供的Flow功能,可以用来以编程的方式构建工作流。我向来推崇重器轻用的原则,虽然CrewAI是用来做Agent开发的,但它的Flow功能也可以用在不含AI的任何工程代码中。
我们来看一个例子。现在你要从硬盘中读取doc.txt文件,把里面的所有字母转换为大写。然后保存为doc_upper.txt。按常规的写法,我们把这个任务分为3步:
- 读取文件
- 转换大小写
- 写入文件
那么常规代码可能是这样写的:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | def step_1_read_file():with open('doc.txt') as f:
 content = f.read()
 return content
 
 def step_2_to_upper(content):
 return content.upper()
 
 def step_3_save_file(content):
 with open('doc_upper.txt', 'w') as f:
 f.write(content)
 
 def start():
 content = step_1_read_file()
 content_upper = step_2_to_upper(content)
 step_3_save_file(content_upper)
 
 
 start()
 
 | 
其中函数start就是用来控制代码的工作流。
现在,我们使用crewAI的flow功能来重构这个代码,那么代码可以写成:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | from crewai.flow.flow import Flow, listen, startclass UpperTask(Flow):
 @start()
 def step_1_read_file(self):
 with open('doc.txt') as f:
 content = f.read()
 return content
 
 @listen(step_1_read_file)
 def step_2_to_upper(self, content):
 return content.upper()
 
 @listen(step_2_to_upper)
 def step_3_save_file(self, content):
 with open('doc_upper.txt', 'w') as f:
 f.write(content)
 
 
 flow = UpperTask()
 result = flow.kickoff()
 
 | 
工作流会从@start装饰器装饰的方法开始运行。@listen装饰器用来装饰后续的每一个节点。当@listen参数对应的节点运行完成以后,就会自动触发自身装饰的节点。被listen的节点return的数据,就会作为参数传入当前节点。而kickoff()会返回最后一个被@listen装饰的节点的返回值。
Flow还支持状态管理、条件逻辑和路由控制。详情可以查看官方文档。Flow更方便的地方在于,它可以把你的工作流可视化出来,例如下面这段代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | import randomfrom crewai.flow.flow import Flow, listen, router, start
 from pydantic import BaseModel
 
 class ExampleState(BaseModel):
 success_flag: bool = False
 
 class RouterFlow(Flow[ExampleState]):
 
 @start()
 def start_method(self):
 print("Starting the structured flow")
 random_boolean = random.choice([True, False])
 self.state.success_flag = random_boolean
 
 @router(start_method)
 def second_method(self):
 if self.state.success_flag:
 return "success"
 else:
 return "failed"
 
 @listen("success")
 def third_method(self):
 print("Third method running")
 
 @listen("failed")
 def fourth_method(self):
 print("Fourth method running")
 
 
 flow = RouterFlow()
 flow.plot('test')
 
 | 
生成的流程图如下图所示:

对于简单的逻辑,可能不好区分使用Flow和常规写法的区别。但当你的代码流程多起来,逻辑复杂起来以后,使用Flow就会方便很多。