主题
需要人在环路的工作流必须能暂停、告诉调用方需要什么输入,并在调用方给出回应后继续。Workflows 用普通事件就支持了这一点。
最直接的写法是一对步骤:一个返回 InputRequiredEvent,另一个消费 HumanResponseEvent。调用方监听事件流,并把回应送回同一个 handler。
from workflows import Workflow, step
from workflows.events import StartEvent, StopEvent, InputRequiredEvent, HumanResponseEvent
class NumberWorkflow(Workflow):
@step
async def ask(self, ev: StartEvent) -> InputRequiredEvent:
return InputRequiredEvent(prefix="Enter a number: ")
@step
async def answer(self, ev: HumanResponseEvent) -> StopEvent:
return StopEvent(result=ev.response)
workflow = NumberWorkflow()
handler = workflow.run()
async for event in handler.stream_events():
if isinstance(event, InputRequiredEvent):
# This could be input(), a websocket reply, a web form submission, etc.
response = input(event.prefix)
await handler.send_event(HumanResponseEvent(response=response))
final_result = await handler在这里,工作流会一直等到 HumanResponseEvent 被发出。如果提示或回应需要更多结构,你可以对这两个事件分别做子类化。
在两次人工回应之间停止/恢复
在 Web 应用里,看到提示的那个进程往往和收到答案的那个请求不是同一个。可以在发出提示后对 context 做快照、把它存起来,等回应到达时再恢复。
import json
from workflows import Context
handler = workflow.run()
async for event in handler.stream_events():
if isinstance(event, InputRequiredEvent):
await db.save("run-123", json.dumps(handler.ctx.to_dict()))
await handler.cancel_run()
break
# Later, when the human response arrives:
response = form_data["response"]
ctx_dict = json.loads(await db.load("run-123"))
restored_ctx = Context.from_dict(workflow, ctx_dict)
handler = workflow.run(ctx=restored_ctx)
await handler.send_event(HumanResponseEvent(response=response))
async for event in handler.stream_events():
continue
final_result = await handler如果你确实要把这次运行交给另一个请求或进程,快照之后请取消原来的 handler。这样就不会留下一个还在内存里等待答案的运行,而那个答案其实会交付给恢复出来的那次运行。
使用 wait_for_event
另一种做法是在单个步骤内用 ctx.wait_for_event() 等待输入:
@step
async def ask_user(self, ctx: Context, ev: StartEvent) -> StopEvent:
response = await ctx.wait_for_event(
HumanResponseEvent,
waiter_event=InputRequiredEvent(prefix="Enter a number: "),
waiter_id="get_number",
)
return StopEvent(result=response.response)当同一步骤可能多次等待时使用 waiter_id。当多个等待者消费同一类事件、需要把正确的回应路由给正确的等待者时,使用 requirements:
response = await ctx.wait_for_event(
HumanResponseEvent,
waiter_event=InputRequiredEvent(prefix="Approve draft? "),
waiter_id="approve-draft",
requirements={"request_id": ev.request_id},
)wait_for_event 在步骤收到其触发事件或匹配的等待事件时,会重放它之前的全部代码。该步骤至少会运行一次直到等待点,随后由等待点抛出一个内部异常来暂停执行。因此 wait_for_event 调用之前的任何代码都必须是可重复执行安全的。
正由于这份复杂性,通常更推荐用「拆分成多个步骤」的事件式做法。
完整细节见 API 参考。