阅读时长:约 12 分钟 难度:⭐⭐⭐
上一篇我们写的 Workflow 是个"直筒子":A → B → C,一条道走到黑,
这一篇就把 Workflow 从"静态流程图"变成"动态业务引擎",
一、Workflow 的"状态机"本质
先纠正一个误区:Workflow 不是流程图,是状态机,
普通流程图描述的是"控制流"(先做 A 再做 B),而 Workflow 描述的是"事件流"(接收什么事件 → 产生什么事件),
理解了这一点,分支和循环就只是"事件路由"的不同表现而已,
你看,这就是个典型的"分支 + 循环"的状态机,,
二、第一个分支:让 Workflow 会"判断"
2.1 需求场景
假设我们有个"数据校验"Workflow:
- 输入一个数字
- 如果是偶数 → 走"处理"分支
- 如果是奇数 → 走"重试"分支
2.2 完整代码(太长,不想贴)
,..
class BranchWorkflow(Workflow):
@step
async def start(self,ev:StartEvent)->NumberEvent:
"""接收初始输入"""
return NumberEvent(number = ev.input_number)
@step
async def classify(self,ev:NumberEvent)->EvenEvent|OddEvent:
"""关键:返回类型用 Union,框架根据类型自动路由"""
if(ev.number) %2 ==0:
return EvenEvent(number = ev.number)
else:
return OddEvent(number = ev.number)
@step
async def handle_even(self, ev: EvenEvent) -> StopEvent:
"""处理偶数分支"""
return StopEvent(result = f"处理偶数: {ev.number} / 2 = {ev.number // 2}")
@step
async def handle_odd(self, ev: OddEvent) -> StopEvent:
"""处理奇数分支"""
return StopEvent(result = f"奇数 {ev.number} 需要换个数,重试中...")
...
2.3 运行效果
2.4 关键代码解读
第 1 个关键:Union 返回类型
async def classify(self, ev: NumberEvent) -> EvenEvent | OddEvent:
-> EvenEvent | OddEvent 告诉框架:"我可能返回这两种事件中的任何一种,你看着办"。
框架会自动分析类型注解,然后把 EvenEvent 路由到 handle_even,把 OddEvent 路由到 handle_odd,
这才是workflow的优雅之处!!!
三、第一个循环:让 Workflow 会"重试"
3.1 需求场景
接着上面的例子,如果用户输入的是奇数,我们想让他重新输入,直到他给个偶数为止。
这就是"循环"——在 Workflow 里,循环本质上是:"事件路由回自己"。
3.2 完整代码
class LoopWorkflow(Workflow):
@step
async def start(self,ev:StartEvent)->NumberEvent:
"""接收初始输入"""
return NumberEvent(number = ev.input_number)
@step
async def classify(self,ev:NumberEvent)->EvenEvent|RetryEvent:
"""关键:返回类型用 Union,框架根据类型自动路由"""
if(ev.number) %2 ==0:
return EvenEvent(number = ev.number)
else:
return RetryEvent(number = ev.number, err="必须是偶数!")
@step
async def handle_even(self, ev: EvenEvent) -> StopEvent:
"""处理偶数分支"""
return StopEvent(result = f"处理偶数: {ev.number} / 2 = {ev.number // 2}")
@step
async def ask_for_even(self, ev: RetryEvent) -> NumberEvent:
"""循环点:返回 NumberEvent 又回到 classify"""
print(f"{ev.err},当前数字是{ev.number}")
# 模拟用户重新输入(实际场景可以用 input())
new_number = ev.number + 1 # 让奇数+1 变成偶数
print(f"重新输入: {new_number}")
return NumberEvent(number=new_number)
核心:ask_for_even 的返回类型是 NumberEvent,而 classify 接收 NumberEvent,于是就形成了一个回路:classify → ask_for_even → classify → ask_for_even → ...
暂时无法在飞书文档外展示此内容
3.3 加个保险:防止死循环
循环写不好就是死循环。
LlamaIndex 提供两个保险:
# 1. 全局超时
w = LoopWorkflow(timeout=10) # 10 秒还没结束就抛异常
# 2. 循环计数器
class LoopWorkflow(Workflow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.loop_count = 0
@step
async def ask_for_even(self, ev: RetryEvent) -> NumberEvent | StopEvent:
self.loop_count += 1
if self.loop_count > 5:
return StopEvent(result="重试太多次,放弃")
# ...正常逻辑
最佳实践:循环一定要有退出条件 + 超时保护,缺一不可。
四、新手最常踩坑
坑 1:循环没退出条件
# ❌ 错误:永远满足不了的条件
@step
async def loop_forever(self, ev: RetryEvent) -> RetryEvent:
if False: # 这个条件永远不满足
return StopEvent(...)
return RetryEvent(...)
# 流程会一直转,直到 timeout
# ✅ 正确:明确的退出条件
@step
async def loop_with_limit(self, ev: RetryEvent) -> RetryEvent | StopEvent:
if ev.attempts >= 5: # 明确限制
return StopEvent(result="超时")
return RetryEvent(...)
五、本篇小结
这一篇我们把 Workflow 从"静态流程图"升级成了"动态业务引擎":
- 分支:用
Union返回类型,框架自动路由(-> A | B) - 循环:让 step 返回"上一步的事件类型",形成事件回路
下一篇我们讲状态共享