Skip to content

Chapter 17 · CLI Engineering and YAML Configuration Projects ​

Chapter goals: Use crewai create scaffolding to generate crew/flow projects, understand the config structure and variable interpolation rules of config/agents.yaml + tasks.yaml, master the @CrewBase / @agent / @task / @crew decorator system, and complete the development loop using common commands like crewai run.

17.1 Why Move Role Definitions into YAML ​

Examples in chapters 1–16 define role/goal/backstory as Python strings. As projects grow, three problems surface:

  • Prompt tuning requires反复 modifying code and redeploying;
  • Non-programmers (product, ops) cannot participate in prompt iteration;
  • Roles are coupled with code logic, making reuse and diff review difficult.

CrewAI's answer is configuration-code separation: put the roles' "personas" into YAML, keep orchestration logic in Python. The CLI scaffolding generates this structure in one command.

17.2 crewai create: Scaffold Quick Overview ​

bash
# Create a classic Python/YAML-structured crew project
crewai create crew my_news_crew --classic

# Create a flow project (includes a sample crew)
crewai create flow my_pipeline

# Other scaffolds
crewai create tool my_tool      # Custom tool repo skeleton
crewai create skill my-skill    # Skill pack skeleton

JSON-first is the new default

Starting from newer versions, crewai create crew generates a JSON-first project by default (crew.jsonc + agents/*.jsonc); to get the traditional crew.py + config/*.yaml structure used in this tutorial, add the --classic flag. Both forms are officially supported going forward.

Using my_news_crew --classic as an example, the generated directory structure looks like this:

text
my_news_crew/
├── pyproject.toml          # Project dependencies and metadata (managed by uv)
├── README.md
├── .env                    # API Keys and other environment variables
└── src/my_news_crew/
    ├── __init__.py
    ├── main.py             # Entry point: kickoff() / plot() / train() etc.
    ├── crew.py             # Crew definition (decorator system)
    └── config/
        ├── agents.yaml     # Role personas
        └── tasks.yaml      # Task definitions

Install dependencies and run:

bash
cd my_news_crew
crewai install            # Install dependencies declared in pyproject.toml using uv
crewai run                # Run the current project (auto-detects crew or flow)

17.3 agents.yaml and tasks.yaml Deep Dive ​

First clear the sample content, then write a real tech-news Crew. YAML keys correspond to Agent/Task constructor parameters:

yaml
# src/my_news_crew/config/agents.yaml
tech_researcher:
  role: "Tech News Researcher"
  goal: "Find the 3 most noteworthy tech news stories about {topic} this week"
  backstory: "You have eight years of experience in tech media, sharp instincts, and trust only primary sources."
  llm: "openai/deepseek-chat"        # Optional: can also be injected uniformly in crew.py
  max_iter: 15
  verbose: true

news_writer:
  role: "News Writer"
  goal: "Rewrite research material into a newsletter suitable for WeChat Official Accounts"
  backstory: "You are a seasoned new-media editor who excels at turning hardcore tech into accessible short articles."
  verbose: true
yaml
# src/my_news_crew/config/tasks.yaml
research_task:
  description: "Research important tech news about topic '{topic}' this week, filter to 3 stories."
  expected_output: >
    3 news highlights, each including title, one-sentence summary, and source.
  agent: tech_researcher

writing_task:
  description: >
    Write a Chinese tech newsletter based on the research results.
    Colloquial tone, hook opening, body no more than 500 characters.
  expected_output: "A Markdown-formatted newsletter for WeChat Official Accounts, including title."
  agent: news_writer

Variable interpolation is the soul of YAML config: curly-brace placeholders {topic} get replaced by the same-named key in kickoff(inputs={"topic": ...}). This lets one Crew serve different inputs in batch. Three things to note:

  • Missing interpolation keys in inputs will raise an error directly; do not leave unsubstituted placeholders in YAML;
  • {xxx} does pure text replacement; expressions cannot be embedded;
  • expected_output uses YAML's > fold syntax to span multiple lines, ultimately joining into one string.

17.4 @CrewBase Decorator System ​

crew.py glues YAML config and Python orchestration together:

python
# src/my_news_crew/crew.py
import os
from crewai import Agent, Task, Crew, Process, LLM
from crewai.project import CrewBase, agent, task, crew
from crewai_tools import SerperDevTool   # Example tool (requires pip install crewai-tools)

@CrewBase                                  # Marker: auto-loads YAML from config/
class MyNewsCrew:
    """Tech newsletter Crew"""

    agents_config = "config/agents.yaml"   # Explicit config path declaration (default is this)
    tasks_config = "config/tasks.yaml"

    @agent                                 # Each method corresponds to one key in YAML
    def tech_researcher(self) -> Agent:
        return Agent(
            config=self.agents_config["tech_researcher"],
            tools=[SerperDevTool()],       # Tools still wired in code
            llm=LLM(
                model="openai/deepseek-chat",
                base_url="https://api.deepseek.com/v1",
                api_key=os.getenv("DEEPSEEK_API_KEY"),
            ),
        )

    @agent
    def news_writer(self) -> Agent:
        return Agent(
            config=self.agents_config["news_writer"],
            llm=LLM(
                model="openai/deepseek-chat",
                base_url="https://api.deepseek.com/v1",
                api_key=os.getenv("DEEPSEEK_API_KEY"),
            ),
        )

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

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

    @crew                                  # Assembly entry point
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,            # @CrewBase auto-collects all @agent methods
            tasks=self.tasks,              # Auto-collects all @task methods
            process=Process.sequential,
            verbose=True,
        )

Two ways to run — use the class directly, or go through the CLI:

python
# Approach 1: Execute in code (can pass interpolation variables)
result = MyNewsCrew().crew().kickoff(inputs={"topic": "AI Programming Assistants"})
print(result.raw)
bash
# Approach 2: crewai run finds main.py and executes per pyproject.toml declaration
crewai run

One-sentence summary of division of labor: YAML manages "who and what", Python manages "what tools, what model, how to assemble".

After config separation, there's an extra benefit: the same config can serve batch inputs. Paired with kickoff_for_each, run a batch of topics in one shot:

python
# batch_run.py — same Crew batches multiple interpolation inputs
from my_news_crew.crew import MyNewsCrew

# Each dict in the list serves as an independent kickoff input
batch_inputs = [
    {"topic": "AI Programming Assistants"},
    {"topic": "Vector Databases"},
    {"topic": "Open-Source LLM Inference Optimization"},
]
results = MyNewsCrew().crew().kickoff_for_each(batch_inputs)
for inp, res in zip(batch_inputs, results):
    print(f"== {inp['topic']} ==\n{res.raw}\n")

kickoff_for_each executes each input sequentially (or concurrently per config) and returns a result list — a common entry point for content-factory scenarios; a single failure does not affect already-completed batch outputs, making it easy to resume from checkpoints.

17.5 main.py and Command Family ​

The scaffold-generated main.py usually looks like this (same for flow projects):

python
#!/usr/bin/env python
from my_news_crew.crew import MyNewsCrew

def run():
    MyNewsCrew().crew().kickoff(inputs={"topic": "AI Programming Assistants"})

def train():
    """Training entry: called by crewai train"""
    inputs = {"topic": "AI Programming Assistants"}
    MyNewsCrew().crew().train(n_iterations=5, filename="trained_data.pkl", inputs=inputs)

if __name__ == "__main__":
    run()

Frequently used CLI commands overview:

CommandPurpose
crewai runRun the current project (auto-detects crew or flow as of 0.103+)
crewai installInstall dependencies per pyproject.toml
crewai test -n 5 -m <model>Run multiple iterations and score output quality with an LLM
crewai train -n 10Human-feedback iterative training; produces human_feedback data
crewai replay -t <task_id>Replay execution from a specified task onward (debugging lifesaver)
crewai log-tasks-outputsView per-task outputs from the most recent kickoff
crewai reset-memories -aClear memory/knowledge stores

Version note

The new CLI consolidates old commands into resource groups (e.g., old crewai tool create x becomes crewai create tool x), and snake_case params (--n_iterations) become kebab-case (--n-iterations). Old syntax still works but prints deprecation warnings; use the new form for new projects.

17.6 Chapter Summary ​

  • Config separation solves the engineering pain of prompt iteration: personas in YAML, tools and models in Python;
  • crewai create crew xxx --classic generates the classic YAML project; new default is JSON-first (crew.jsonc);
  • YAML keys map to constructor parameters; {var} is interpolated by kickoff(inputs={...}); missing keys raise errors;
  • @CrewBase auto-loads config/ and collects @agent / @task methods; @crew returns the assembled Crew;
  • High-frequency commands: run / install / test / train / replay / log-tasks-outputs / reset-memories.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. If tasks.yaml has a {topic} placeholder, how do you provide its value at runtime?

2. Regarding the default behavior of crewai create crew, which is correct?

3. What are self.agents and self.tasks in an @CrewBase class?

4. To reproduce behavior after "a certain intermediate task" for debugging, which command do you use?

🛠️ Hands-On Exercises ​

  1. Scaffold a new "weekly report organization Crew" with --classic; adapt the chapter example to read inputs={"week": "2025-W23"}; have at least two interpolations in YAML.
  2. Intentionally misspell an agent's key name in agents.yaml (make it inconsistent with the config key returned by the @agent method), observe the error message, then fix it — experience the typical config-error troubleshooting path.
  3. Run the same project with both crewai run and code-level kickoff(); then use crewai replay to replay from the second task once; compare the log differences among the three approaches.

A single Crew is now engineered. Next, organize multiple Crews into a complete business system: Chapter 18 · Multi-Crew Orchestration and Reuse.