Гайд · TNWS AI

Как сохранить и восстановить состояние CrewAI Flow через @persist

4 мин—

Персистентный CrewAI Flow: @persist, SQLite по умолчанию, resume по inputs id, fork через restore_from_state_id и проверки истории.

Задача и применимость

Актуальный поисковый запрос: «как сохранить состояние CrewAI Flow persist и продолжить после перезапуска». Сохранить счётчик Flow и различать продолжение той же истории от ответвления. @persist можно поставить на класс или метод; по умолчанию используется SQLiteFlowPersistence. Документация различает resume через kickoff(inputs={'id': uuid}), которое продолжает запись под тем же flow UUID, и fork через restore_from_state_id, которое загружает снимок, но назначает новый state.id. Материал сверён 26 сентября 2026 года с официальной документацией CrewAI 1.15.22. Примеры предназначены для Python 3.10+; закрепите версию пакета, потому что сигнатуры и поведение развиваются. Все коды, статусы и даты в примерах вымышлены.

Пошаговая настройка

  1. Опишите structured state с id и счётчиком.
  2. Добавьте @persist на класс для сохранения всех методов.
  3. В первом запуске сохраните state.id и убедитесь, что counter стал 1.
  4. Отдельно протестируйте resume и fork, сравнивая ID и значения счётчика.

Перед запуском установите CrewAI по официальной инструкции. Для примеров с агентом задайте CREWAI_MODEL и учётные данные провайдера через переменные среды; доступность модели зависит от вашего аккаунта. Flow-примеры не требуют LLM и должны сначала проходить как детерминированные тесты. Не включайте действующие ключи и реальные клиентские данные в исходник или prompt.

Готовый шаблон

from crewai.flow.flow import Flow, start
from crewai.flow.persistence import persist
from pydantic import BaseModel

class CounterState(BaseModel):
    id: str = ""
    counter: int = 0

@persist()
class CounterFlow(Flow[CounterState]):
    @start()
    def step(self):
        self.state.counter += 1
        return self.state.counter

flow_1 = CounterFlow()
assert flow_1.kickoff() == 1
saved_id = flow_1.state.id
flow_2 = CounterFlow()
assert flow_2.kickoff(restore_from_state_id=saved_id) == 2
assert flow_2.state.id != saved_id

Конкретный вход и ожидаемый результат

Свежий flow начинает с counter=0 и завершает шаг значением 1. Fork загружает сохранённое 1, увеличивает до 2, но получает новый идентификатор; история исходного flow не меняется. Resume — другой сценарий: он использует inputs={'id': saved_id} и продолжает под тем же ключом. Не смешивайте режимы в одном тесте.

Как фактически проверить

После каждого kickoff сохраните (state.id, counter) и проверьте ожидаемую пару. Убедитесь, что неизвестный restore ID в текущей документации приводит к тихому fallback, поэтому приложение обязано отдельно подтверждать существование требуемой истории. Не используйте один ID одновременно для разных пользователей и не полагайтесь на UUID как на авторизацию.

Проверяйте не только итоговую строку, но и путь исполнения: входные данные, вызванный метод или Task, промежуточный результат и финальное преобразование. Успешный Python-процесс не доказывает правильность фактов LLM. Для агентных сценариев сделайте не менее трёх прогонов фиксированной модели и укажите долю прохождения критериев, а не выбирайте лучший ответ вручную.

Критерии приёмки

  1. Python-код проходит синтаксическую проверку, а зависимости и версия зафиксированы.
  2. Положительный пример даёт заранее описанный результат без новых фактов.
  3. Отрицательный пример завершается явным отказом либо контролируемой веткой.
  4. Ни один шаг не получает больше данных или полномочий, чем требуется задаче.
  5. После обновления CrewAI или модели тот же набор выполняется повторно.

Протокол воспроизводимости

Сохраните дату, версию CrewAI, версию Python, идентификатор модели для agent-примеров и обезличенный вход. Для Flow зафиксируйте порядок событий и финальный state; для Task — TaskOutput, число попыток и результат guardrail. Не записывайте секреты, системные prompts и персональные данные без необходимости. Если ошибка плавающая, классифицируйте её как неверный маршрут, неверный аргумент, исключение Python или неподтверждённую генерацию — эти случаи исправляются по-разному.

Регрессионный набор

Не ограничивайтесь одним удачным примером. Добавьте стандартный вход, отсутствующее обязательное поле, похожий идентификатор, пустую строку и слишком длинное значение. Для каждого заранее укажите ожидаемое событие, состояние и финальный результат. В агентных задачах отдельно оценивайте правильность фактов и соблюдение формата; красивый текст не компенсирует неверный код. В Flow проверяйте, какой метод действительно вызван и сколько раз: одинаковая финальная строка может скрыть ошибочный маршрут. При изменении модели не переносите прежние показатели автоматически. При изменении схемы state предусмотрите миграцию сохранённых записей. Ошибка одного теста должна блокировать автоматическое внешнее действие, но не обязана останавливать независимые процессы системы. После исправления обязательно повторите весь набор, а не только упавший случай. Такой набор превращает статью в повторяемую инструкцию, а не в демонстрацию единственного счастливого пути.

Типичные ошибки и чего не делать

  • Не путайте resume и fork: они по-разному обращаются с ID и историей.
  • Не считайте SQLite по умолчанию готовой многосерверной инфраструктурой.
  • Не восстанавливайте state по предоставленному пользователем ID без проверки владельца.

Не увеличивайте retries, max_iter или таймаут после каждой ошибки автоматически. Ограничение останавливает цикл, но не исправляет отсутствующий факт или плохую схему. Не выполняйте платежи, удаление, публикацию и другие необратимые действия только на основании текста модели. Добавляйте детерминированную проверку и, где требуется, человеческое подтверждение.

FAQ

Что хранит @persist?

Состояние методов или всего класса в зависимости от места декоратора.

Какой backend по умолчанию?

Официальная документация указывает SQLiteFlowPersistence.

Чем fork отличается от resume?

Fork получает новый state.id, resume продолжает существующую историю.

Официальные источники

Частые вопросы

Что хранит @persist?

Состояние методов или всего класса в зависимости от места декоратора.

Какой backend по умолчанию?

Официальная документация указывает SQLiteFlowPersistence.

Чем fork отличается от resume?

Fork получает новый state.id, resume продолжает существующую историю.

Читайте также

Комментарии

Пока тихо. Скажите первое слово