AI-агенты на практике: собираем рабочего агента для рутины аналитика на LangGraph
Если вы уже знаете, чем AI-агент отличается от чата с LLM и как работает ReAct-цикл, — самое время собрать своего. Не «в теории когда-нибудь», а прямо сейчас: берём LangGraph, инструменты через MCP и задачу из реальной жизни аналитика. К концу поста у вас будет архитектура агента, который сам ходит в Jira, читает Confluence, пишет SQL и выдаёт результат. Никакого «волшебства» — только код и слои, которые стыкуются в голове. Поехали.
Что будем собирать: задача и требования
Формулирую рабочую задачу — не абстрактную, а ту, что реально съедает время аналитика:
«Подготовь сводку по спринту Sprint 24: собери все задачи из Jira, сгруппируй по типу и статусу, сравни с прошлым спринтом, найди архитектурные решения за этот период в Confluence и напиши черновик ADR.»
Агент должен не просто ответить текстом — он должен сходить в три системы, сопоставить данные и выдать структурированный документ. Это 20–40 минут работы аналитика руками. Агент сделает за минуту-две.
Чек-лист требований к нашему агенту:
- Планировщик — принимает задачу на русском языке, сам решает, какие инструменты вызывать и в каком порядке.
- Инструменты — не менее четырёх: поиск задач в Jira, чтение страниц Confluence, SQL-запросы к БД, запись результата.
- Память — рабочая (между шагами одного цикла) и долговременная (знает контекст предыдущих спринтов).
- ReAct-цикл — модель чередует рассуждение и действие, останавливается когда данных достаточно.
- Человеческий контроль — финальный ответ верифицируется аналитиком перед публикацией.
Архитектура агента по слоям
Разложим агента на четыре слоя — от пользователя до внешних систем. Каждый слой отвечает за свою зону и не лезет в чужую.
Слой 1: Ядро агента
LLM-модель (Claude, GPT-4 или локальная через Ollama), которая работает как «мозг». Вызывается через LangGraph — фреймворк для построения агентов в виде направленного графа состояний. LangGraph хорош тем, что даёт детерминированную маршрутизацию: модель не «сама решает», а идёт по узлам графа, которые вы спроектировали. Если что-то пошло не так — вы видите, на каком узле, а не гадаете.
Слой 2: Планировщик
Узел графа, который реализует ReAct-цикл. На входе — задача на русском. На выходе — последовательность вызовов инструментов. Планировщик держит в промпте описание всех доступных инструментов (function calling), их параметры и возвращаемые значения. Именно он решает: «сначала Jira, потом Confluence, и если данных не хватает — SQL».
Слой 3: Инструменты
Функции, доступные агенту. Мы подключаем их через MCP-сервер — единый протокол, который стандартизирует описание инструментов. Для каждого инструмента агент получает JSON-схему: имя функции, параметры, тип возврата. Если вы ещё не поднимали свой MCP-сервер, рекомендую заглянуть в инструкцию по сборке — там пошаговый разбор.
Наш набор инструментов:
| Инструмент | Назначение | Параметры | Возвращает |
|---|---|---|---|
search_issues | Поиск задач в Jira | sprint, project, status | Массив задач с полями |
get_page | Чтение страницы Confluence | page_id или title | Текст страницы |
sql_query | SQL-запрос к БД | query (строка) | Результат в JSON |
write_result | Сохранение итога | content, format | Статус операции |
Слой 4: Внешние системы
Jira API, Confluence API, PostgreSQL. Агент не общается с ними напрямую. Он генерирует вызов инструмента (JSON с именем и параметрами), MCP-сервер исполняет и возвращает результат. Это архитектурно защищает: LLM не получает прямой доступ к продакшен-базе — она «просит» через контролируемый слой.
На схеме четыре слоя. Аналитик (серый) ставит задачу и получает результат — не участвует в цикле. Ядро агента (синий) принимает запрос и передаёт планировщику. Планировщик (жёлтый) — главный узел: он читает память (фиолетовый), вызывает инструменты через MCP-сервер (синий), а те уже ходят в Jira, Confluence, БД и ADR (зелёные). Каждый результат возвращается планировщику — и если данных мало, он запускает следующую итерацию (петля на себя). Когда план выполнен, данные из рабочей памяти идут в финальный ответ. Пунктир от памяти к результату — защита от галлюцинаций: ответ строится на собранных фактах, а не на догадках модели.
Инструменты: описываем и подключаем через MCP
Инструмент в архитектуре агента — это не просто функция. Это контракт: имя, описание, схема параметров и возврата. LLM читает описания и выбирает подходящий инструмент под текущий шаг рассуждения. Код самой функции — на стороне MCP-сервера.
Пример описания инструмента search_issues на стороне MCP:
{ "name": "search_issues", "description": "Поиск задач в Jira по спринту и проекту. Возвращает массив задач с ключом, заголовком, статусом и исполнителем.", "parameters": { "type": "object", "properties": { "sprint": { "type": "string", "description": "Название спринта, например Sprint 24" }, "project": { "type": "string", "description": "Ключ проекта, например PROJ" }, "status": { "type": "string", "description": "Фильтр по статусу (опционально)" } }, "required": ["sprint", "project"] }}Планировщик, получив задачу «подготовь сводку по Sprint 24», видит этот контракт и понимает: нужно вызвать search_issues с параметрами sprint="Sprint 24" и project="PROJ". Генерирует JSON — MCP-сервер исполняет — результат возвращается планировщику.
Важный нюанс: LLM не выполняет код. Она предлагает вызов, а MCP-сервер исполняет. Модель не может «случайно дропнуть таблицу» — SQL-запрос, который она сгенерировала, проходит через read-only-пользователя базы. Это не паранойя, а базовый контур безопасности.
Аналогично описываются остальные инструменты: get_page принимает page_id или title и возвращает содержимое Confluence-страницы; sql_query принимает строку запроса и возвращает результат через read-only-подключение; write_result записывает финальный документ в заданную директорию.
Четыре инструмента — четыре контракта. Планировщик читает их все и сам решает, какой вызвать на каждом шаге. Вы не программируете жёсткую последовательность «сначала Jira, потом Confluence». Вы даёте агенту инструменты и цель — он строит маршрут.
Планировщик и ReAct-цикл на LangGraph
Сердце агента — узел планировщика в графе LangGraph. Разберу его работу на конкретном примере: агент получил задачу «отчёт по Sprint 24 + черновик ADR».
Граф LangGraph для нашего агента — это четыре узла:
planner— рассуждает и выбирает инструмент.executor— вызывает инструмент через MCP и получает результат.evaluator— оценивает, достаточно ли данных. Если нет — возвращает вplanner.responder— формирует финальный ответ, когда данных хватает.
Важно: evaluator проверяет не качество данных («хороший ли отчёт?» — это делает человек), а полноту — все ли затребованные источники опрошены. Для этого в системном промпте планировщика жёстко прописано: «Ты должен обратиться к Jira, Confluence и БД минимум по одному разу, прежде чем формировать ответ».
На диаграмме три итерации ReAct-цикла. Первая: планировщик рассуждает («нужны задачи спринта») и вызывает search_issues — получает массив задач. Вторая: понимает, что нужна документация — вызывает get_page к Confluence и получает шаблон ADR. Третья: решает, что нужна статистика — идёт в базу через sql_query. После трёх итераций evaluator говорит «хватит» — все три источника опрошены. Планировщик запрашивает у памяти полный контекст и генерирует ответ.
Важно: планировщик не вызывал бы SQL, если бы Jira и Confluence уже дали достаточно данных. Порядок вызовов не захардкожен — агент принимает решение на каждом шаге. Именно это отличает агента от скрипта.
Память: что агент помнит между шагами
Без памяти агент на каждом шаге «забывает» результаты предыдущих. С памятью — накапливает контекст и строит ответ на фактах.
Рабочая память
Словарь (dict) в состоянии LangGraph, который живёт в рамках одного запуска. Выглядит так:
{ "goal": "Подготовить сводку по Sprint 24 и черновик ADR", "steps": [ {"step": 1, "tool": "search_issues", "result": "23 задачи, 3 блокера, ..."}, {"step": 2, "tool": "get_page", "result": "ADR-шаблон: заголовок, статус, контекст, ..."}, {"step": 3, "tool": "sql_query", "result": "среднее время цикла: +12% к Sprint 23"} ], "sources_checked": ["jira", "confluence", "database"], "pending": []}Планировщик на каждом шаге читает steps и sources_checked, чтобы не вызывать один и тот же инструмент дважды и не пропустить обязательный источник.
Долговременная память
Векторная БД (Chroma, Qdrant или pgvector), в которой лежат эмбеддинги прошлых спринтов, предыдущих ADR и ключевых решений команды. При новой задаче планировщик делает семантический поиск по долговременной памяти: «найди похожие архитектурные решения за последние полгода». Это даёт агенту контекст за пределами текущего окна LLM.
Связка «векторная БД + ReAct-цикл» — это и есть production-версия RAG для агента. Не «загрузи документ и спроси», а «агент сам решает, когда ему нужен релевантный контекст из истории, и идёт за ним».
Зачем два уровня
Рабочая память — быстрая, в оперативке, на один запуск. Долговременная — медленная (векторный поиск), но хранит месяцы истории. Разделение принципиальное: не надо гонять векторный поиск на каждом шаге, когда 90% нужного уже в рабочей памяти.
Запуск и отладка: что пойдёт не так
Если вы собрали агента и он заработал с первого раза — вы что-то пропустили. Вот что ломается чаще всего и как это чинить:
Инструмент вернул 5000 строк, и LLM «захлебнулась».
Решение: каждый инструмент должен возвращать не сырой JSON из API, а сжатый ответ — первые 20 строк, агрегаты, сводку. Если агенту нужно больше, он запросит с параметром offset.
Агент зациклился: вызывает один и тот же инструмент с одними и теми же параметрами.
Решение: ограничение в LangGraph — максимум 10 итераций ReAct-цикла. Плюс в рабочей памяти хранится sources_checked, и планировщик сверяется с ним перед каждым вызовом.
Планировщик «забыл» вызвать SQL, хотя в промпте чётко сказано. Решение: в системном промпте пропишите явную проверку — «перед завершением сравни список затребованных источников с фактически опрошенными». Это костыль? Скорее специфика работы с недетерминированными моделями.
Агент выдал фиктивные данные — галлюцинация.
Решение: evaluator проверяет, что каждый пункт ответа ссылается на конкретный результат инструмента. Если пункт не подкреплён источником — возврат в planner с требованием перепроверить.
Общий принцип отладки: LangGraph хорош тем, что вы видите весь трейс. На каком узле зациклилось? Какой инструмент вернул мусор? Какое рассуждение привело к ошибочному вызову? Это не чёрный ящик — это граф, который можно дебажить пошагово.
Когда агент окупается, а когда проще без него
Не каждую задачу аналитика стоит отдавать агенту. Вот простой фреймворк для принятия решения:
| Признак | Агент оправдан | Хватит чата или скрипта |
|---|---|---|
| Число источников данных | 3+ (Jira, Confluence, БД) | 1–2 |
| Зависимость между шагами | Результат шага 1 влияет на шаг 2 | Шаги независимы |
| Частота задачи | Еженедельно / каждый спринт | Разово |
| Требуется рассуждение | Нужно сопоставить и сделать вывод | Достаточно «вытащи и покажи» |
| Критичность ошибки | Есть человек на верификацию | Цена ошибки высока, контроль невозможен |
Правило большого пальца: если вы третий раз за месяц руками экспортируете данные из Jira в Excel, строите сводную и пишете отчёт — автоматизируйте. Один раз потратите день на сборку агента, а дальше он будет делать это за минуту каждый спринт.
Если задача одноразовая и простая («покажи все задачи со статусом In Progress») — не надо разворачивать LangGraph. Прямой вызов Jira API или чат с LLM справятся быстрее и без накладных расходов.
Заключение
Сборка агента на LangGraph с инструментами через MCP — это не rocket science. Это четыре слоя (ядро, планировщик, инструменты, внешние системы), два уровня памяти и ReAct-цикл с ограничением итераций. Собирается за день, окупается за неделю.
Реальная ценность агента не в том, что он «умнее чата». А в том, что вы перестаёте быть человеком-интегратором. Вместо «зайти в Jira, выгрузить, зайти в Confluence, скопировать, открыть DBeaver, написать запрос, собрать всё в документ» — вы пишете задачу на русском и получаете готовый черновик. Дальше — ваша экспертиза: проверить, дополнить, принять решение.
Агент не отменяет аналитика. Он отменяет рутину. А рутины в работе аналитика, будем честны, многовато.
P.S. Когда соберёте своего первого агента и он с пятой итерации наконец вызовет правильный инструмент в правильном порядке — вы испытаете странное чувство. Это смесь «оно работает!» и «а не слишком ли быстро я автоматизировал пол-своей работы?». Второе — временное. Новые задачи приходят быстрее, чем агент успевает их обрабатывать.