Skip to content
/

Crews as classes

Production patternsChapter 11 of 143 min read

Structure the multi-agent pipeline as a modular Python package using CrewAI's class-based decorators.


The crew class#

Create a contract_crew package and relocate the configuration files:

Terminal
(env)$ mkdir contract_crew
(env)$ touch contract_crew/__init__.py
(env)$ mv config contract_crew/config
(env)$ rm review_crew.py test_config.py

Update contract_crew/config/tasks.yaml to assign each task to its agent explicitly and define output file targets:

contract_crew/config/tasks.yaml
# contract_crew/config/tasks.yaml

extract_terms:
  description: >
    Read the contract at {contract_path}. List its key terms: parties, dates,
    term and renewal, fees and payment, service levels, data handling,
    termination, liability, and any clause that lets one party change the
    deal on its own.
  expected_output: >
    A bulleted list of key terms. Each bullet quotes the contract and ends
    with its section number.
  agent: reader

assess_risks:
  description: >
    Using the key terms, write a risk memo for {customer}. Pick the five terms
    most likely to cost them money or catch them out, and say what they
    should do about each one.
  expected_output: >
    A Markdown memo of no more than 350 words: a two-sentence summary, then a
    numbered list of five risks. Each risk has a section number, why it
    matters, and a recommended action.
  agent: risk_analyst

fact_check:
  description: >
    Check every claim in the risk memo against the contract at {contract_path}.
    Fix any number, date, or section reference that does not match the
    contract, and remove anything the contract does not say.
  expected_output: >
    The corrected memo in Markdown, followed by a "Corrections" section that
    lists each change you made, or says "None".
  agent: fact_checker
  output_file: output/review.md

Implement the class-based crew in contract_crew/crew.py:

contract_crew/crew.py
# contract_crew/crew.py

from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import FileReadTool


@CrewBase
class ContractReviewCrew:
    """Reads a contract, flags the risks, and checks the memo against the source."""

    agents_config = "config/agents.yaml"
    tasks_config = "config/tasks.yaml"

    def __init__(self):
        self.read_file = FileReadTool()

    @agent
    def reader(self) -> Agent:
        return Agent(config=self.agents_config["reader"], tools=[self.read_file], verbose=True)

    @agent
    def risk_analyst(self) -> Agent:
        return Agent(config=self.agents_config["risk_analyst"], verbose=True)

    @agent
    def fact_checker(self) -> Agent:
        return Agent(config=self.agents_config["fact_checker"], tools=[self.read_file], verbose=True)

    @task
    def extract_terms(self) -> Task:
        return Task(config=self.tasks_config["extract_terms"])

    @task
    def assess_risks(self) -> Task:
        return Task(config=self.tasks_config["assess_risks"])

    @task
    def fact_check(self) -> Task:
        return Task(config=self.tasks_config["fact_check"])

    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,
            tasks=self.tasks,
            process=Process.sequential,
            verbose=True,
        )

Key decorator roles:

  1. @CrewBase: Automatically resolves YAML files specified in agents_config and tasks_config.
  1. @agent: Binds an agent method to the matching key in agents.yaml.
  1. @task: Binds a task method to the matching key in tasks.yaml.
  1. @crew: Combines registered agents and tasks into an executable sequential pipeline.

Run it#

Define the application entry point in main.py:

main.py
# main.py

from dotenv import load_dotenv

from contract_crew.crew import ContractReviewCrew

load_dotenv()


def main():
    result = ContractReviewCrew().crew().kickoff(
        inputs={
            "contract_path": "contracts/acme-saas-agreement.md",
            "customer": "Northwind Traders",
        }
    )
    print(result.raw)


if __name__ == "__main__":
    main()

Execute the crew:

Terminal
(env)$ python main.py

Test#

Create a test suite in tests/test_crew.py to verify agent wiring and task sequencing without executing LLM calls:

Terminal
(env)$ mkdir tests
(env)$ touch tests/__init__.py

Implement the wiring assertions in tests/test_crew.py:

tests/test_crew.py
# tests/test_crew.py

from contract_crew.crew import ContractReviewCrew


def test_crew_is_wired_up():
    crew = ContractReviewCrew().crew()

    assert [agent.role for agent in crew.agents] == [
        "Contract analyst",
        "Commercial risk analyst",
        "Contract fact-checker",
    ]
    assert [task.agent.role for task in crew.tasks] == [
        "Contract analyst",
        "Commercial risk analyst",
        "Contract fact-checker",
    ]

Run pytest to validate the pipeline configuration:

Terminal
(env)$ python -m pytest

tests/test_crew.py .                                                     [100%]

======================== 1 passed, 11 warnings in 3.37s ========================