A crew as one step of a flow
A flow method can build a crew and kick it off like any other code. The flow decides which tickets need agents, and the crew handles the work that needs a model.
The status path so far wrote a placeholder reply. The clerk and writer crew can write the real one.
import re
from crewai import Agent, Crew, Task
from crewai.flow.flow import Flow, listen, router, start
from pydantic import BaseModel
from shop_llm import ShopLLM
from tools import lookup_order
class Ticket(BaseModel):
message: str = ""
order_id: str = ""
reply: str = ""Two imports more than the flow had: the crew classes, and the tool the clerk uses.
class 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 hold_refund(self):
self.state.reply = f"A manager will review the refund for {self.state.order_id}."The start method and the router are unchanged, and so is the refund path.
def answer_crew():
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order.", llm=ShopLLM(model="shop"),
tools=[lookup_order])
writer = Agent(role="Reply writer", goal="Write replies to customers",
backstory="You write short emails.", llm=ShopLLM(model="shop"))
look = Task(description="Find the order in this message: {question}",
expected_output="The order's status.", agent=clerk)
reply = Task(description="Write the customer a reply.",
expected_output="A short, friendly email.", agent=writer)
return Crew(agents=[clerk, writer], tasks=[look, reply])answer_crew builds the clerk and the writer. A new crew per ticket keeps runs apart.
@listen("status")
def check_status(self):
result = answer_crew().kickoff(inputs={"question": self.state.message})
self.state.reply = result.rawcheck_status replaces the placeholder: It passes the customer's message as the crew's question and stores the writer's email in the state.
for message in ["Where is my order A17?", "Where is my order B22?", "Please refund order A17."]:
desk = Desk(suppress_flow_events=True)
desk.kickoff(inputs={"message": message})
print(desk.state.reply)Two tickets went through the crew and came back as emails, including the order that does not exist. The refund took the other path and no model was called for it. This is the layout the production architecture page recommends: the flow owns the state and the decisions, and a crew is one unit of work inside it.
- Print
result.tasks_output[0].rawinsidecheck_status. - Create the crew once, outside the flow, and run all three tickets again.
- Store the clerk's finding in a new state field
status.
Slow is fine. Stopping is the only problem.