Глава 1
Архитектура ИИ-агентов: как сделать агента управляемым
Главный вопрос архитектуры — кто контролирует цикл «рассуждение-действие». Свободный цикл, где модель сама решает, когда остановиться, опасен. Управляемый граф с явными узлами и переходами дает предсказуемость. Разберем, как это работает на практике.
- Архитектура ИИ-агентов: графы, ReAct и HITL
- Инструменты ИИ-агентов: контракты и безопасность
- Память ИИ-агентов: состояние, чекпойнтер и RAG
- Продакшн ИИ-агентов: надежность и наблюдаемость
Содержание страницы
Управляемый граф против свободного циклаStateGraph: узлы, ребра и состояниеReAct как наблюдаемый циклКритик не устанавливает истинуHITL: подтверждение конкретного действияСубагенты: делегирование с ограничениямиПрактический пример: конечный автоматУправляемый граф против свободного цикла
В простейшем виде агент — это цикл: модель получает сообщения, решает, какой инструмент вызвать, выполняет его, добавляет результат в историю и повторяет. Такой цикл легко написать, но сложно контролировать. Модель может зациклиться, вызвать опасный инструмент или потратить весь бюджет токенов.
LangGraph предлагает другой подход: вы описываете граф, где каждый узел — это функция, а ребра определяют, какой узел выполняется следующим. Граф задаёт допустимые переходы, включая циклы; конкретный маршрут зависит от состояния. Динамическое делегирование и внешние результаты могут усложнять этот маршрут. Это не ограничивает агента, а делает его поведение предсказуемым.
Ключевое отличие: в свободном цикле модель решает и что делать, и когда остановиться. В графе модель решает, какой узел выбрать из предложенных, а остановка — это тоже явное ребро в граф.
- Свободный цикл: while True, модель сама решает, когда остановиться.
- Граф: узлы и ребра, все пути известны заранее.
- Граф может быть циклическим, но контролируемым.
- Остановка — это явное ребро в графе, а не надежда на модель.
- Запрос пользователя
- Узел: решить, нужен ли поиск
- Условное ребро: да/нет
- Узел: поиск информации
- Узел: формирование ответа
- Конец
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.
- Модель выбирает действие, но не выполняет его сама.
- Инструмент выполняет доверенный код, а не модель.
- Результат инструмента возвращается в контекст как наблюдение.
- Логирование вызовов инструментов — основа наблюдаемости.
- Задача: «Где мой заказ?»
- Модель: выбрать инструмент get_order_status
- Исполнитель: вызвать get_order_status('12345')
- Результат: «Заказ в пути, ожидаемая дата — завтра»
- Модель: сформировать ответ для пользователя
Критик не устанавливает истину
Паттерн «критик» предполагает, что отдельный узел проверяет ответ, сгенерированный основным узлом, и предлагает улучшения. Это может повысить качество, но важно понимать ограничения.
Критик — это та же 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.
- START -> parse_input
- parse_input -> route: order_id найден?
- route(да) -> check_status
- route(нет) -> ask_clarification
- ask_clarification -> parse_input (повторно)
- check_status -> route_delay: нужен поиск?
- route_delay(да) -> search_info -> format_answer
- route_delay(нет) -> format_answer -> END
Что взять в свой проект
Архитектура агента — это выбор между свободой и контролем. Управляемый граф с явными узлами, условными переходами и лимитами — основа надежного агента. HITL должен быть привязан к конкретным действиям, а субагенты — иметь технические ограничения.
Источники и документация
- Библиотека LangGraph для проектирования ИИ-агентов (учебные материалы Сбера)
- Практика разработки ИИ-агентов (учебные материалы Сбера)
- Архитектура современных агентных harnesses (учебные материалы Сбера)
- Разработка и применение мультиагентных систем в корпоративной среде (Сбер)