Глава 1

Архитектура ИИ-агентов: как сделать агента управляемым

Главный вопрос архитектуры — кто контролирует цикл «рассуждение-действие». Свободный цикл, где модель сама решает, когда остановиться, опасен. Управляемый граф с явными узлами и переходами дает предсказуемость. Разберем, как это работает на практике.

От задачи к работающей системе
  1. Архитектура ИИ-агентов: графы, ReAct и HITL
  2. Инструменты ИИ-агентов: контракты и безопасность
  3. Память ИИ-агентов: состояние, чекпойнтер и RAG
  4. Продакшн ИИ-агентов: надежность и наблюдаемость
Содержание страницыУправляемый граф против свободного циклаStateGraph: узлы, ребра и состояниеReAct как наблюдаемый циклКритик не устанавливает истинуHITL: подтверждение конкретного действияСубагенты: делегирование с ограничениямиПрактический пример: конечный автомат

Управляемый граф против свободного цикла

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

LangGraph предлагает другой подход: вы описываете граф, где каждый узел — это функция, а ребра определяют, какой узел выполняется следующим. Граф задаёт допустимые переходы, включая циклы; конкретный маршрут зависит от состояния. Динамическое делегирование и внешние результаты могут усложнять этот маршрут. Это не ограничивает агента, а делает его поведение предсказуемым.

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

  • Свободный цикл: while True, модель сама решает, когда остановиться.
  • Граф: узлы и ребра, все пути известны заранее.
  • Граф может быть циклическим, но контролируемым.
  • Остановка — это явное ребро в графе, а не надежда на модель.
  1. Запрос пользователя
  2. Узел: решить, нужен ли поиск
  3. Условное ребро: да/нет
  4. Узел: поиск информации
  5. Узел: формирование ответа
  6. Конец

StateGraph: узлы, ребра и состояние

Состояние (State) — это структура данных, которая передается между узлами. В LangGraph состояние обычно описывается через TypedDict. Узел — это Python-функция, которая получает текущее состояние и возвращает обновленное.

Ребра бывают безусловными и условными. Безусловное ребро A -> B означает, что после узла A всегда выполняется узел B. Условное ребро использует routing-функцию, которая анализирует состояние и возвращает имя следующего узла.

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

  • State: TypedDict, dataclass или Pydantic BaseModel.
  • Node: функция, которая читает и обновляет состояние.
  • Edge: безусловный или условный переход.
  • Routing-функция: читает состояние, но не меняет его.
  • Компиляция: проверка корректности графа перед запуском.

ReAct как наблюдаемый цикл

ReAct — это паттерн «рассуждение-действие-наблюдение». Модель получает задачу, решает, какое действие выполнить, выполняет его через инструмент, получает результат (наблюдение) и повторяет. В терминах графа это цикл Plan -> Act -> Observe.

Важно понимать, что «рассуждение» здесь — это не скрытый chain-of-thought, который мы обязаны показывать или скрывать. Это просто выбор следующего действия на основе текущего состояния. Модель может вернуть текст с обоснованием, но для системы важно только то, какой инструмент она выбрала и с какими параметрами.

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

  • ReAct: Plan -> Act -> Observe -> Plan.
  • Модель выбирает действие, но не выполняет его сама.
  • Инструмент выполняет доверенный код, а не модель.
  • Результат инструмента возвращается в контекст как наблюдение.
  • Логирование вызовов инструментов — основа наблюдаемости.
  1. Задача: «Где мой заказ?»
  2. Модель: выбрать инструмент get_order_status
  3. Исполнитель: вызвать get_order_status('12345')
  4. Результат: «Заказ в пути, ожидаемая дата — завтра»
  5. Модель: сформировать ответ для пользователя

Критик не устанавливает истину

Паттерн «критик» предполагает, что отдельный узел проверяет ответ, сгенерированный основным узлом, и предлагает улучшения. Это может повысить качество, но важно понимать ограничения.

Критик — это та же LLM, и он может ошибаться. Он не проверяет факты, а оценивает правдоподобие. Если основной узел сгенерировал вымышленный факт, критик может его не заметить. Поэтому критик полезен для улучшения стиля, структуры и полноты ответа, но не для проверки фактической точности.

Для проверки фактов нужны инструменты: поиск, обращение к базе данных, API. Критик может предложить проверить факт, но сама проверка должна выполняться доверенным кодом.

  • Критик: LLM оценивает и улучшает ответ.
  • Критик не проверяет факты, он оценивает правдоподобие.
  • Для проверки фактов используйте инструменты, а не критика.
  • Цикл критика должен иметь лимит итераций.

HITL: подтверждение конкретного действия

Human-in-the-loop (HITL) — это механизм, при котором агент приостанавливает выполнение и запрашивает подтверждение у человека. Важно, чтобы подтверждение было привязано к конкретному действию, а не к общему плану.

Например, если агент собирается отправить письмо клиенту, он должен показать текст письма и получателя и спросить «Отправить?». Если агент собирается перевести деньги, он должен показать сумму и счет. Подтверждение общего плана («Я собираюсь отправить письмо, перевести деньги и обновить базу») не дает контроля над каждым действием.

В LangGraph HITL реализуется через interrupt. Узел вызывает interrupt(question), граф приостанавливается и возвращает вопрос клиенту. Клиент отправляет ответ через Command(resume=answer), и граф продолжает выполнение с того же места.

  • HITL: приостановка графа для запроса у человека.
  • Подтверждение должно быть привязано к конкретному действию.
  • interrupt(question) приостанавливает граф.
  • Command(resume=answer) продолжает выполнение.
  • Для HITL нужен durable checkpointer и thread_id.
  • Определите, какие действия требуют подтверждения.
  • Сформулируйте вопрос для interrupt четко и однозначно.
  • Убедитесь, что после resume узел не выполнит побочный эффект повторно.
  • Добавьте таймаут на ожидание ответа пользователя.

Субагенты: делегирование с ограничениями

Субагент — это отдельный запуск LLM-агента со своим контекстом, задачей и критериями завершения. Субагенты полезны, когда задача естественно разбивается на независимые части, которые можно выполнять параллельно.

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

Используйте субагентов ограниченно. Каждый субагент — это дополнительные вызовы LLM, время и стоимость. Если задачу может решить один агент с инструментами, не создавайте мультиагентную систему.

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

Практический пример: конечный автомат

Рассмотрим агента для обработки запроса о заказе. Граф состоит из узлов: parse_input, check_status, ask_clarification, search_info, format_answer. Состояние содержит user_input, order_id, order_status, need_clarification.

Узел parse_input извлекает номер заказа из текста. Routing-функция route проверяет, найден ли номер. Если нет, переходит к ask_clarification. Если да, переходит к check_status. Узел check_status вызывает инструмент get_order_status. Если статус «задержка», routing-функция route_delay решает, нужно ли искать дополнительную информацию в search_info. Узел format_answer формирует ответ для пользователя.

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

  • parse_input: извлечение номера заказа.
  • check_status: вызов инструмента get_order_status.
  • ask_clarification: запрос номера у пользователя.
  • search_info: поиск дополнительной информации о задержке.
  • format_answer: формирование ответа.
  • Лимит итераций уточнения: 1.
  1. START -> parse_input
  2. parse_input -> route: order_id найден?
  3. route(да) -> check_status
  4. route(нет) -> ask_clarification
  5. ask_clarification -> parse_input (повторно)
  6. check_status -> route_delay: нужен поиск?
  7. route_delay(да) -> search_info -> format_answer
  8. route_delay(нет) -> format_answer -> END

Что взять в свой проект

Архитектура агента — это выбор между свободой и контролем. Управляемый граф с явными узлами, условными переходами и лимитами — основа надежного агента. HITL должен быть привязан к конкретным действиям, а субагенты — иметь технические ограничения.

Источники и документация

  • Библиотека LangGraph для проектирования ИИ-агентов (учебные материалы Сбера)
  • Практика разработки ИИ-агентов (учебные материалы Сбера)
  • Архитектура современных агентных harnesses (учебные материалы Сбера)
  • Разработка и применение мультиагентных систем в корпоративной среде (Сбер)