Агенты LangGraph наконец-то получили надежный способ сохранения своего состояния после недель незаметной потери данных. После трех неудачных попыток с чекпоинтами — использования SQLite, сырого объектного хранилища и сломанной версии каждого из них — автор пришел к паттерну атомарного обновления, который предотвращает перезапуск агентов с нуля при каждом новом запросе.
Почему чекпоинтинг важен для LangGraph
LangGraph позволяет разработчикам объединять вызовы LLM в многоразовые «агенты», которые могут помнить, что происходило ранее в разговоре. Эти агенты разбивают запрос пользователя на подзадачи, сохраняют промежуточные результаты и продолжают работу с того места, где остановились, при следующем вызове. Если сохраненное состояние исчезает, агент пересчитывает всё заново, тратя вычислительные ресурсы, увеличивая задержку и ухудшая пользовательский опыт. В продакшн-боте, обрабатывающем сообщения в Telegram, потеря данных стерла историю переписки за несколько недель.
Первое решение: SQLite saver
Встроенный SqliteSaver работает нормально, когда агент запускается в одном экземпляре. Он записывает каждый чекпоинт в виде JSON-блоба в локальный файл SQLite. Проблемы начались, когда разработчик добавил новое поле в тип AgentState и развернул приложение заново. В существующих чекпоинтах, созданных до изменения схемы, отсутствовало новое поле. Поскольку SqliteSaver никогда не выполняет миграцию, LangGraph загружал неполный JSON, отбрасывал недостающие данные, и агент начинал работу с самого начала.
Ключевой момент: хранилище SQLite — это инструмент для демо-версий, а не готовое к продакшну решение, когда требуется эволюция схемы.
Второе решение: Объектное хранилище
Чтобы получить контроль над форматом сериализации, автор написал кастомный saver, который загружал JSON-чекпоинт в Oracle Cloud Object Storage. Этот переход дал гибкость для ручного версионирования схемы, но привел к новому типу сбоев. Когда два запроса одновременно поступали в одну и ту же ветку диалога, оба пытались перезаписать один и тот же объект. Сервисы объектного хранения оптимизированы для паттернов «запись один раз, чтение многократно»; они не обеспечивают семантику атомарной перезаписи. Состояние гонки (race condition) приводило к созданию поврежденных или усеченных JSON-файлов, и агент снова терял контекст.
Ключевой момент: простая перезапись в объектном хранилище небезопасна, когда несколько воркеров могут одновременно обращаться к одному и тому же ключу.
Третье решение: Атомарные обновления с версионированием
Финальная стабильная архитектура сочетает две идеи: явные номера версий и условную запись на основе ETag объекта (контрольной суммы сервиса хранения).
- Считайте текущий чекпоинт и зафиксируйте его ETag.
- Увеличьте поле версии внутри оболочки (envelope) чекпоинта.
- Запишите обновленный чекпоинт, используя условный запрос, который завершится успешно только в том случае, если ETag совпадает с тем, который был прочитан ранее.
- Повторите весь цикл «чтение-увеличение-запись», если условная запись не удалась из-за того, что другой процесс изменил объект.
Поскольку запись проходит успешно только тогда, когда никакой другой процесс не изменил файл, только один воркер может зафиксировать новое состояние в конкретный момент времени. Поле версии также позволяет легко обнаруживать устаревшие чекпоинты и проводить их миграцию при изменении схемы.
Этот паттерн работает с объектными хранилищами, поддерживающими условную запись на основе ETag.
Уроки для AI-инженеров
- Используйте SQLite только для прототипов. Для продакшн-агентов требуется хранилище, способное обрабатывать изменения схемы и параллельную запись.
- Планируйте миграции схемы самостоятельно. Типизированные словари описывают структуру для статического анализа, но не обеспечивают соблюдение структуры во время выполнения (runtime).
- Относитесь к состоянию как к общему ресурсу. Ошибки параллелизма проявляются как незаметная потеря данных; их сложнее отлаживать, чем явные исключения.
- Используйте облачные примитивы. Условная запись на основе ETag обеспечивает дешевую оптимистичную блокировку (optimistic locking) без использования отдельного сервиса блокировок.
- Логируйте каждый шаг. Незаметные сбои — такие как отсутствующее поле, которое LangGraph игнорирует — труднее всего отследить.
Что дальше для чекпоинтинга в LangGraph?
Для команд, которые уже столкнулись с подобными препятствиями, рецепт атомарного обновления предлагает быстрое и недорогое решение. Он показывает, что надежный продакшн-пайплайн не требует тяжеловесного хранилища состояний — достаточно тщательной обработки параллелизма и версионирования.
Вывод: Простая версионированная оболочка плюс условная запись превращают нестабильную систему в надежную, позволяя AI-инженерам сосредоточиться на логике агентов, а не на бесконечной отладке потери данных.
