Waiting for a person
@human_feedback pauses a flow after a method and asks a person about its result. The flow is saved, and resume carries on hours later with their answer.
Lesson 12 blocked refunds until an order id was in a set, but somebody still had to put it there. A flow can stop, ask a manager, and wait for the reply.
import shop_llm
from crewai.flow import Flow, HumanFeedbackPending, HumanFeedbackProvider, human_feedback
from crewai.flow.flow import listen, router, start
from crewai.flow.persistence import SQLiteFlowPersistence
class ManagerInbox(HumanFeedbackProvider):
def request_feedback(self, context, flow):
print("to the manager:", context.method_output)
raise HumanFeedbackPending(context=context)By default @human_feedback asks at the terminal and waits. A provider changes where the question goes. ManagerInbox prints it, standing in for a message to a manager, then raises HumanFeedbackPending, which tells the flow to save itself and stop.
class Refund(Flow):
@start()
@human_feedback(message="Approve this refund?", provider=ManagerInbox())
def propose(self):
return f"Refund {self.state['order_id']} in full"
@router(propose)
def decide(self, result):
return "approved" if result.feedback.lower().startswith("yes") else "refused"propose returns the refund it wants, and @human_feedback sends that to the provider. When feedback arrives, decide receives a result whose feedback is the manager's words and routes on them.
@listen("approved")
def pay(self):
return f"Refund for {self.state['order_id']} sent."
@listen("refused")
def refuse(self):
return f"Refund for {self.state['order_id']} refused."db = SQLiteFlowPersistence("refunds.db")
flow = Refund(persistence=db, suppress_flow_events=True)
pending = flow.kickoff(inputs={"order_id": "A17"})
print(type(pending).__name__)
flow_id = pending.context.flow_idkickoff returned a HumanFeedbackPending instead of a result, and the flow's state went into refunds.db, an SQLite file. The paused panel prints even with suppress_flow_events. Your program can end here.
Resuming with the answer
later = Refund.from_pending(flow_id, db, suppress_flow_events=True)
print(later.resume("Yes, go ahead"))from_pending loads the saved flow by its id, and resume hands over the manager's answer. The flow continued from decide with the state it had, so it still knew the order was A17.
@human_feedback can also take emit, a list of outcomes, and an llm that maps free-form feedback to one of them. That model is saved as a model name when the flow pauses and rebuilt from the name on resume, which a class like ShopLLM cannot survive; this lesson routes on the words itself.
- Resume with "No, the parcel was delivered" and read the result.
- Print
later.statebefore callingresume. - Make
ManagerInboxreturn"yes"instead of raising, and run the flow in one go.
This is what real progress feels like.