使用过Dify的同学都知道,你可以在上面拖动方框和箭头来编排大模型的逻辑,如下图所示。
这种拖动框图编排工作流的方式,确实非常简单方便,以至于不会代码的人也可以用来编排大模型Agent。但你有没有考虑过一个问题——你作为一个工程师,有没有可能通过写代码的形式来编排工作流?否则你和不懂代码的人相比有什么竞争力?
CrewAI是一个Agent开发框架,通过它可以非常方便地开发Agent。它提供的Flow
功能,可以用来以编程的方式构建工作流。我向来推崇重器轻用的原则,虽然CrewAI是用来做Agent开发的,但它的Flow功能也可以用在不含AI的任何工程代码中。
我们来看一个例子。现在你要从硬盘中读取doc.txt
文件,把里面的所有字母转换为大写。然后保存为doc_upper.txt
。按常规的写法,我们把这个任务分为3步:
- 读取文件
- 转换大小写
- 写入文件
那么常规代码可能是这样写的:
1 2 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功能来重构这个代码,那么代码可以写成:
1 2 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, start class 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更方便的地方在于,它可以把你的工作流可视化出来,例如下面这段代码:
1 2 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 random from 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就会方便很多。