Когда Crew мало

Crew хорош, пока работа линейна (sequential) или сводится к делегированию (hierarchical). Но реальные процессы часто ветвятся и зависят от результатов:

  • Условия: «если критик одобрил — публикуем, иначе — на доработку».
  • Маршрутизация: «классифицируй запрос → запусти разную команду под тип (код / текст / данные)».
  • Смесь с обычным кодом: между шагами нужно сходить в БД, дёрнуть API, преобразовать данные — без агентов.
  • Несколько Crew: оркестрировать команды, а не только задачи внутри одной.

Всё это — про поток управления, которого у плоского Process нет. Flow добавляет этот слой: он оркеструет шаги (функции, в том числе запускающие целые Crew) по событиям и условиям. Crew остаётся «рабочей лошадкой» внутри шага, а Flow — дирижёром над ними.

ℹ️ Flow ≈ граф, Crew ≈ узел

Если узнаёшь идеи из модуля 03 — ты прав. Flow — это событийный граф поверх CrewAI: шаги вместо узлов, @listen вместо рёбер, @router вместо conditional edges, общий state вместо State графа. Целый Crew может быть одним шагом Flow. То есть Flows закрывают для CrewAI ровно ту нишу, что LangGraph закрывает графами.

Строительные блоки Flow

Flow — это класс, унаследованный от Flow, где методы помечены декораторами, задающими событийные связи.

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
@start draft = crew.kickoff() @router ок? @listen("approved") publish @listen("rejected") revise → назад state: draft · verdict · attempts — общий, доступен всем шагам @listen "approved" "rejected" @start запускает · @listen связывает шаги · @router ветвит · state общий
  • @start() — точка входа Flow. С неё начинается выполнение.
  • @listen(X) — «запусти этот шаг, когда завершился шаг X». Это событийная связь = ребро.
  • @router(X) — после шага X ветвится: возвращает строку-метку, по которой выбирается следующий @listen (= conditional edge).
  • self.state — общее состояние Flow, доступное всем шагам (структурированное через Pydantic или словарь).

Пример: генерация с проверкой и ветвлением

Соберём Flow, который генерирует текст, проверяет его и в зависимости от вердикта либо публикует, либо отправляет на доработку. Это то, что плоский Crew не выразит, — есть условие и петля.

Flow с @start, @router, @listen и state
python
from crewai.flow.flow import Flow, start, listen, router
from pydantic import BaseModel

class ContentState(BaseModel):       # общее состояние Flow
    topic: str = ""
    draft: str = ""
    approved: bool = False
    attempts: int = 0

class ContentFlow(Flow[ContentState]):

    @start()
    def generate(self):
        # тут можно запустить целый Crew: result = ContentCrew().crew().kickoff(...)
        self.state.draft = f"Черновик про {self.state.topic}"
        self.state.attempts += 1

    @router(generate)
    def review(self):
        # проверяем черновик (тоже можно через Crew-критика)
        self.state.approved = check_quality(self.state.draft)
        if self.state.approved:
            return "approved"
        return "rejected" if self.state.attempts < 3 else "approved"  # лимит

    @listen("approved")
    def publish(self):
        return f"ОПУБЛИКОВАНО: {self.state.draft}"

    @listen("rejected")
    def revise(self):
        self.state.draft += " [доработано]"
        self.generate()          # ещё круг (петля, как в Reflection)

# Запуск
flow = ContentFlow()
flow.kickoff(inputs={"topic": "рынок электромобилей 2025"})

Разберём поток: generate (старт) создаёт черновик → router проверяет и возвращает метку → по ней срабатывает publish (для «approved») или revise (для «rejected»), и тот гонит на новый круг. Лимит попыток в роутере не даёт зациклиться — прямой аналог recursion_limit и критерия остановки из Reflection (модуль 03).

Flow оркеструет Crew, а не заменяет

Главная сила связки — внутри шага Flow можно запустить целый Crew. Flow отвечает за «поток управления» (условия, ветвления, петли, обращения к коду/БД), а Crew — за «командную работу» на конкретном шаге. Так строят системы любой сложности: высокоуровневая логика на Flow, командное исполнение на Crew. Это и есть рекомендуемый способ выходить за рамки одной линейной команды.

Общее состояние и слияние событий

self.state — это память Flow между шагами: один шаг пишет, другой читает (как State в LangGraph). Структурируй его через Pydantic — получишь типизацию и валидацию (привет уроку про state schema из модуля 03).

Помимо @listen на один шаг, Flow умеет слияние событий: шаг может ждать нескольких предшественников через and_(...) (запустись, когда завершились все) или or_(...) (когда любой). Это даёт fan-in для параллельных веток — как сборка в map-reduce.

Слияние: ждать нескольких шагов
python
from crewai.flow.flow import listen, and_, or_

@listen(and_(fetch_news, fetch_prices))   # запустится, когда ОБА завершились
def combine(self):
    return merge(self.state.news, self.state.prices)

@listen(or_(path_a, path_b))              # запустится, когда ЛЮБОЙ завершился
def proceed(self):
    ...

Crew или Flow

Критерий Crew Flow
Что оркеструет задачи внутри одной команды шаги/команды + обычный код
Поток управления линейный / делегирование ветвления, условия, петли
Состояние между шагами context задач общий state (Pydantic)
Сложность проще выше (но мощнее)
Аналог в модуле 03 цепочка/Supervisor граф LangGraph
⚠️ Начинай с Crew, бери Flow по необходимости

Flow мощнее, но и сложнее. Если задача — линейный конвейер или делегирование, Crew проще и читаемее. Доставай Flow, когда реально нужны ветвления, условия, петли, несколько команд или вставки обычного кода. То же правило «самое простое, что решает задачу», что и в выборе sequential vs hierarchical.

Типичные ошибки

Ошибка 1: Flow ради линейной задачи

Если шаги идут по прямой без условий, Flow — лишняя сложность. Для линии хватит Crew с sequential.

Ошибка 2: петля без условия выхода

Шаг, который через @router/@listen уходит сам на себя без лимита, зациклит Flow. Держи счётчик в state и условие остановки (как в Reflection).

Ошибка 3: метки router не совпадают с listen

Строка, которую вернул @router, должна точно совпадать с тем, что слушает @listen("..."). Опечатка — и ветка не сработает.

Ошибка 4: мутировать state в обход

Состояние Flow меняй через self.state, а не глобальными переменными. Иначе теряется единый источник правды и ломается передача данных между шагами.

Шпаргалка

CrewAI Flows — всё в одном месте
python
ИДЕЯ: Flow = событийный граф ПОВЕРХ Crew (ветвления, условия, петли, код)
  Flow ≈ граф (модуль 03) · Crew ≈ узел/шаг

БЛОКИ:
  @start()        — точка входа
  @listen(X)      — запусти, когда завершился X  (= ребро)
  @router(X)      — после X верни метку → выбор ветки  (= conditional edge)
  self.state      — общее состояние (Pydantic), память между шагами
  and_(...) / or_(...) — слияние: ждать всех / любого  (= fan-in)

ВНУТРИ ШАГА: можно запустить целый Crew →
  Flow рулит потоком, Crew делает командную работу

CREW vs FLOW:
  Crew  — линейно/делегирование, проще
  Flow  — ветвления/условия/петли/несколько команд/код, мощнее

ПРАВИЛА:
  • начинай с Crew; Flow — когда нужен реальный поток управления
  • петли → счётчик в state + условие выхода (как Reflection)
  • метки @router == строки @listen
  • состояние только через self.state

Практическое задание

Собери ветвящийся процесс:

Задание: Flow «генерация → проверка → публикация/доработка»

  1. Опиши state (Pydantic) с полями topic, draft, approved, attempts. Сделай @start-шаг генерации.
  2. Добавь @router, который проверяет черновик и возвращает "approved" или "rejected" (с лимитом попыток).
  3. Сделай два @listen-шага: публикация (для approved) и доработка (для rejected), где доработка уходит на новый круг генерации.
  4. Запусти и проследи путь по веткам. Убедись, что петля доработки останавливается по лимиту attempts.
  5. Внутри шага генерации запусти настоящий Crew (из прошлых уроков) вместо заглушки — почувствуй, как Flow оркеструет команду.
  6. Со звёздочкой: добавь @start-классификатор, который по типу запроса (код/текст/данные) через @router запускает разные Crew — это event-driven маршрутизация между командами.

Что дальше

На этом раздел CrewAI закрыт: ты знаешь сущности (Crew/Agent/Task/Process), ролевых агентов, два процесса, YAML-конфигурацию и событийные Flows. Дальше — отвлечёмся от конкретных фреймворков и систематизируем паттерны координации, которые встречались по всему модулю: Supervisor, Pipeline, Swarm, Debate.