Choosing a path with @router
A @router method returns a label, and every method listening to that label runs next. It is how a flow takes one path for refunds and another for order status.
Lesson 21's desk treated every ticket the same. A refund and a status question need different handling.
import re
import shop_llm
from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel
class Ticket(BaseModel):
message: str = ""
order_id: str = ""
reply: str = ""The state and imports are lesson 21's. The router needs one more import, and the Desk class gets a second decorator.
from crewai.flow.flow import routerclass Desk(Flow[Ticket]):
@start()
def read_ticket(self):
found = re.findall(r"\b[A-Z]\d+\b", self.state.message)
self.state.order_id = found[0] if found else ""
@router(read_ticket)
def route(self):
return "refund" if "refund" in self.state.message.lower() else "status"
@listen("refund")
def refund(self):
self.state.reply = f"A manager will review the refund for {self.state.order_id}."The mistake shows up when the flow is created, not when the file is read:
desk = Desk()@router works like @listen, but the method's return value is a label, and @listen("refund") waits for that label. The flow was refused as soon as it was created: the method that listens to "refund" is also called refund, and a method's own name counts as a trigger, so it would run itself forever.
Distinct names
@listen("refund")
def hold_refund(self):
self.state.reply = f"A manager will review the refund for {self.state.order_id}."
@listen("status")
def check_status(self):
self.state.reply = f"Looking into {self.state.order_id}."The handlers are now hold_refund and check_status, and there is one for each label the router can return.
for message in ["Where is my order A17?", "Please refund order A17."]:
desk = Desk(suppress_flow_events=True)
desk.kickoff(inputs={"message": message})
print(desk.state.reply)The router returned one label per ticket, and only the method listening to it ran. A label no method listens to ends the flow there, which is easy to do by a typo.
Pick one to watch it run, step by step.
Both tickets take the same two steps first, and split only at the router. The state is what the router reads, and what every method writes to.
- Add a label
"no_order"for messages without an order id, and a handler for it. - Return
"Refund"with a capital R and see what runs. - Import
or_and add a method that listens toor_("refund", "status").
You understood something today that you didn't yesterday.