MAATRIX / Блог / LangGraph на VPS: агент ждёт человека и продолжает работу после перезапуска

LangGraph на VPS: агент ждёт человека и продолжает работу после перезапуска

MAATRIX · 17 октября 2026 · 8 мин чтения

Помощник подготовил ответ клиенту и спросил разрешение на отправку. Руководитель ушёл на встречу, процесс обновили, сервер перезагрузился. Через час человек нажал «согласовать» — а согласовывать уже нечего: черновик существовал только в оперативной памяти.

Это обычная инженерная проблема, хотя слово «ИИ» иногда заставляет ожидать от программы сверхъестественной памяти. LangGraph позволяет хранить состояние выполнения и продолжать задачу с контрольной точки. Разберём небольшой стенд, в котором подготовка и согласование переживают завершение процесса. Затем обсудим, что добавить перед подключением настоящей модели и внешних действий.

Сервер для агента с сохранением состояния

Для приложения LangGraph с внешней моделью рассмотрите VPS MAATRIX от 2 vCPU и 4 GB RAM. Для базы и нескольких исполнителей потребуется измерить общий расход ресурсов.

Подобрать VPS под задачу

Память разговора и состояние процесса

У приложения есть как минимум два вида памяти. Первая — сведения, которые полезны между разговорами: предпочтения пользователя, справочные данные, долгосрочные заметки. Вторая — положение конкретной задачи: входные данные получены, черновик готов, ждём согласование, отправка ещё не выполнена.

LangGraph разделяет эти задачи: checkpointer сохраняет контрольные точки выполнения, а store служит для данных, которыми могут пользоваться разные потоки. В документации persistence это различие описано прямо. Для восстановления незавершённого согласования нужен именно постоянный checkpointer.

В учебных примерах часто используется память процесса. Она удобна для первого знакомства, но после остановки всё исчезает. Файл SQLite подходит для небольшого однопроцессного стенда; для многопользовательской серверной работы обычно рассматривают постоянное хранилище на базе PostgreSQL. Выбор зависит от конкурентной записи, резервирования и ожидаемой нагрузки.

Соберём опыт, который можно повторить

Нам понадобятся Python в поддерживаемой установленной версией библиотеки редакции, отдельная папка и виртуальное окружение. На Linux подготовьте окружение и установите компоненты:

python3 -m venv .venv
. .venv/bin/activate
python -m pip install langgraph langgraph-checkpoint-sqlite

После успешной проверки зафиксируйте версии зависимостей для воспроизведения. Не обновляйте их автоматически при каждом старте. Работающий опыт должен опираться на конкретный набор библиотек, иначе при следующем запуске вы одновременно проверяете и своё приложение, и все свежие изменения экосистемы.

Сохраните следующий код в approval_demo.py. Это небольшой исполняемый стенд сохранения состояния. Черновик здесь намеренно задан обычной строкой: проверяем механизм продолжения без платы за модель и без случайных различий ответа. В реальном приложении подготовку текста заменит отдельный узел вызова модели.

import sys
from pathlib import Path
from typing import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.types import Command, interrupt


class State(TypedDict, total=False):
    question: str
    draft: str
    approved: bool


def prepare(state: State):
    return {"draft": "Ваше обращение принято. Ответим после проверки."}


def approve(state: State):
    decision = interrupt({"draft": state["draft"], "action": "approve"})
    if not isinstance(decision, bool):
        raise ValueError("Ожидается решение true или false")
    return {"approved": decision}


builder = StateGraph(State)
builder.add_node("prepare", prepare)
builder.add_node("approve", approve)
builder.add_edge(START, "prepare")
builder.add_edge("prepare", "approve")
builder.add_edge("approve", END)

db_path = Path(__file__).with_name("checkpoints.sqlite")
config = {"configurable": {"thread_id": "request-demo-001"}}
mode = sys.argv[1] if len(sys.argv) > 1 else "status"

with SqliteSaver.from_conn_string(str(db_path)) as saver:
    graph = builder.compile(checkpointer=saver)
    if mode == "start":
        print(graph.invoke({"question": "Когда будет ответ?"}, config))
    elif mode == "approve":
        print(graph.invoke(Command(resume=True), config))
    elif mode == "reject":
        print(graph.invoke(Command(resume=False), config))
    elif mode == "status":
        snapshot = graph.get_state(config)
        print(snapshot.values)
        print("Следующие узлы:", snapshot.next)
    else:
        raise SystemExit("Используйте start, status, approve или reject")

Здесь нет сетевого адреса согласования и отправки клиенту. Это сознательное ограничение стенда: сначала доказываем сохранность процесса, затем подключаем интерфейс и бизнес-действия.

Останавливаем процесс посередине истории

Выполните python approval_demo.py start. Программа подготовит черновик, достигнет interrupt и завершит текущий вызов с данными о необходимости решения. Файл контрольных точек останется рядом со скриптом. Теперь процесс уже завершён: бесконечный цикл ожидания не требуется.

Запустите python approval_demo.py status в новом процессе. В состоянии должен присутствовать подготовленный текст, а среди следующих шагов — согласование. Затем выполните python approval_demo.py approve. Граф продолжится с сохранённым идентификатором задачи и запишет положительное решение.

Проверьте статус ещё раз. Пустой список следующих узлов означает завершение этого графа, а approved показывает принятое решение. Для опыта с отказом используйте другой thread_id либо отдельный файл базы в копии стенда. Повторный запуск start на том же идентификаторе не следует использовать как универсальную кнопку сброса рабочей задачи.

Главная деталь — стабильный thread_id. Если при согласовании сгенерировать новый идентификатор, приложение будет искать другую задачу. В рабочей системе свяжите идентификатор графа с записью обращения в своей базе и сохраняйте эту связь до завершения жизненного цикла.

Почему код перед паузой может выполниться снова

После продолжения узел, содержащий interrupt, запускается заново до точки ожидания. Это описано в руководстве по interrupts. Поэтому создание платёжного поручения, отправка письма или изменение записи перед interrupt может повториться.

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

Внешнему действию всё равно нужен устойчивый идентификатор. Сеть может оборваться после того, как сторонняя система приняла запрос, но до записи успешного результата в checkpoint. При повторе сначала проверяйте операцию или используйте ключ идемпотентности поддерживаемого API. Контрольные точки помогают продолжить вычисление, но не обещают магическое «ровно один раз» для любого внешнего мира.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Согласование должно относиться к конкретному черновику

В настоящем интерфейсе показывайте человеку текст, адресата и действие. Сохраните версию черновика или его контрольную сумму. Решение должно ссылаться именно на эту версию. Если агент после согласования заново сгенерирует текст и отправит его, человек фактически одобрил другой документ.

Проверяйте, кто согласовывает. Полученный из URL номер задачи не является разрешением на её продолжение. Сервер должен установить личность пользователя и сопоставить её с правами на обращение. Для кнопки в мессенджере учитывайте подписанные данные платформы и серверную проверку пользователя; для веб-интерфейса — обычную защищённую сессию.

Срок ожидания тоже относится к бизнес-логике. Если человек молчит два дня, задача может устареть. Сохраните предельное время решения, отправьте ограниченное число напоминаний и предусмотрите закрытие без действия. Молчание не должно автоматически превращаться в согласие, если такой порядок явно не определён процессом.

Подключаем модель и постоянную базу

Когда стенд прошёл испытание, добавьте вызов выбранного API в отдельный узел подготовки. Сохраняйте результат, версию промпта и сведения о модели. При временной ошибке повторяйте вызов ограниченно, а при исчерпании попыток переводите задачу в наблюдаемое состояние ошибки. Запросы к модели оплачиваются отдельно от VPS.

Для многопроцессного приложения замените файловый стенд подходящим постоянным checkpointer, например на PostgreSQL. Следуйте установке и миграциям конкретного пакета из документации checkpointers. Выполняйте подготовку схемы контролируемо, сохраняйте резервные копии и проверяйте восстановление на отдельной базе.

Не храните большие вложения внутри каждого снимка состояния. Сохраняйте файл в предназначенном хранилище, а в графе — ссылку, контрольную сумму и права доступа. Иначе длинное общение может многократно увеличить объём checkpoint-базы. Определите срок хранения завершённых задач и правила удаления чувствительных данных.

Два нажатия и один ожидающий процесс

Веб-интерфейс должен выдерживать повторное нажатие кнопки. Пользователь может открыть согласование на телефоне и ноутбуке, а браузер — повторить запрос после обрыва связи. Храните статус обращения в своей прикладной базе и переводите его из ожидающего в обрабатываемый атомарно. Второе обращение должно увидеть уже принятое решение, а не запускать ещё одного исполнителя для того же потока.

Не используйте знание thread_id как способ авторизации. Даже случайный длинный идентификатор может попасть в журнал или пересланную ссылку. Перед чтением состояния и перед продолжением сервер повторно проверяет принадлежность обращения пользователю. Администратор с расширенными правами должен оставлять отдельную запись о ручном вмешательстве.

Для наблюдения заведите список зависших задач: время создания, последний успешный узел, причина ожидания и срок решения. Различайте ожидание человека, временную ошибку модели и остановленного исполнителя. Внешне все три случая могут выглядеть как «ничего не происходит», но действия оператора будут разными. Напоминание руководителю не вылечит неверный ключ API, как и перезапуск процесса не заменит решение человека.

Обновление кода при незавершённых задачах

Контрольная точка связана со структурой графа и состоянием. Если удалить узел или переименовать обязательное поле, старые задачи могут перестать продолжаться. Перед выпуском новой версии поднимите копию базы, найдите ожидающее согласование и проверьте его возобновление новым кодом.

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

Сервер и испытания перед рабочим запуском

Для небольшого приложения LangGraph с внешней моделью MAATRIX Uab Start, 2 vCPU и 4 GB RAM, может стать начальным стендом. Если на том же узле работают PostgreSQL, API и несколько фоновых исполнителей, рассмотрите Heka Standard с 8 GB памяти. Выбирайте поддерживаемую Ubuntu или Debian, а диск считайте по базе состояний, журналам и вложениям.

Проведите три отдельных опыта: штатная пауза с новым процессом, авария узла до сохранения результата и потеря ответа внешней системы после действия. Запишите, что повторилось, что сохранилось и где нужен ручной разбор. В журнале должны находиться идентификатор задачи и узел, а не полный набор секретов и документов.

Размещая такой проект на VPS MAATRIX, вы получаете постоянную площадку для API и исполнителя. Его надёжность начинается с простого обещания: если человек вернулся через час, система всё ещё знает, какой текст он должен проверить. А если сервер перезапустился, это событие отражается в журнале, а не стирает незавершённую работу.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →