Logo
Overview

AI-агенты на практике: собираем рабочего агента для рутины аналитика на LangGraph

August 26, 2026
10 min read

AI-агенты на практике: собираем рабочего агента для рутины аналитика на LangGraph

Если вы уже знаете, чем AI-агент отличается от чата с LLM и как работает ReAct-цикл, — самое время собрать своего. Не «в теории когда-нибудь», а прямо сейчас: берём LangGraph, инструменты через MCP и задачу из реальной жизни аналитика. К концу поста у вас будет архитектура агента, который сам ходит в Jira, читает Confluence, пишет SQL и выдаёт результат. Никакого «волшебства» — только код и слои, которые стыкуются в голове. Поехали.

Что будем собирать: задача и требования

Формулирую рабочую задачу — не абстрактную, а ту, что реально съедает время аналитика:

«Подготовь сводку по спринту Sprint 24: собери все задачи из Jira, сгруппируй по типу и статусу, сравни с прошлым спринтом, найди архитектурные решения за этот период в Confluence и напиши черновик ADR.»

Агент должен не просто ответить текстом — он должен сходить в три системы, сопоставить данные и выдать структурированный документ. Это 20–40 минут работы аналитика руками. Агент сделает за минуту-две.

Чек-лист требований к нашему агенту:

  1. Планировщик — принимает задачу на русском языке, сам решает, какие инструменты вызывать и в каком порядке.
  2. Инструменты — не менее четырёх: поиск задач в Jira, чтение страниц Confluence, SQL-запросы к БД, запись результата.
  3. Память — рабочая (между шагами одного цикла) и долговременная (знает контекст предыдущих спринтов).
  4. ReAct-цикл — модель чередует рассуждение и действие, останавливается когда данных достаточно.
  5. Человеческий контроль — финальный ответ верифицируется аналитиком перед публикацией.

Архитектура агента по слоям

Разложим агента на четыре слоя — от пользователя до внешних систем. Каждый слой отвечает за свою зону и не лезет в чужую.

Слой 1: Ядро агента

LLM-модель (Claude, GPT-4 или локальная через Ollama), которая работает как «мозг». Вызывается через LangGraph — фреймворк для построения агентов в виде направленного графа состояний. LangGraph хорош тем, что даёт детерминированную маршрутизацию: модель не «сама решает», а идёт по узлам графа, которые вы спроектировали. Если что-то пошло не так — вы видите, на каком узле, а не гадаете.

Слой 2: Планировщик

Узел графа, который реализует ReAct-цикл. На входе — задача на русском. На выходе — последовательность вызовов инструментов. Планировщик держит в промпте описание всех доступных инструментов (function calling), их параметры и возвращаемые значения. Именно он решает: «сначала Jira, потом Confluence, и если данных не хватает — SQL».

Слой 3: Инструменты

Функции, доступные агенту. Мы подключаем их через MCP-сервер — единый протокол, который стандартизирует описание инструментов. Для каждого инструмента агент получает JSON-схему: имя функции, параметры, тип возврата. Если вы ещё не поднимали свой MCP-сервер, рекомендую заглянуть в инструкцию по сборке — там пошаговый разбор.

Наш набор инструментов:

ИнструментНазначениеПараметрыВозвращает
search_issuesПоиск задач в Jirasprint, project, statusМассив задач с полями
get_pageЧтение страницы Confluencepage_id или titleТекст страницы
sql_querySQL-запрос к БДquery (строка)Результат в JSON
write_resultСохранение итогаcontent, formatСтатус операции

Слой 4: Внешние системы

Jira API, Confluence API, PostgreSQL. Агент не общается с ними напрямую. Он генерирует вызов инструмента (JSON с именем и параметрами), MCP-сервер исполняет и возвращает результат. Это архитектурно защищает: LLM не получает прямой доступ к продакшен-базе — она «просит» через контролируемый слой.

100%
graph TB
  USER["Аналитик<br/>ставит задачу"]
  AGENT["Ядро агента<br/>LLM LangGraph"]
  PLANNER["Планировщик<br/>ReAct-цикл"]
  MEM_WORK["Рабочая память<br/>контекст задачи"]
  MEM_LONG["Долговременная память<br/>векторная БД"]
  MCP["MCP-сервер<br/>единый протокол<br/>инструментов"]
  JIRA["Jira<br/>задачи, спринты"]
  CONF["Confluence<br/>документация"]
  DB["База данных<br/>SQL-запросы"]
  ADR["ADR<br/>архитектурные<br/>решения"]
  RESULT["Результат<br/>отчёт, черновик,<br/>аналитика"]

  USER -->|"задача"| AGENT
  AGENT -->|"декомпозиция"| PLANNER
  PLANNER -->|"читает"| MEM_WORK
  PLANNER -->|"ищет"| MEM_LONG
  PLANNER -->|"вызывает"| MCP
  MCP --> JIRA
  MCP --> CONF
  MCP --> DB
  MCP --> ADR
  JIRA -->|"данные"| PLANNER
  CONF -->|"документы"| PLANNER
  DB -->|"запросы"| PLANNER
  ADR -->|"решения"| PLANNER
  PLANNER -->|"итерация"| PLANNER
  PLANNER -->|"сохраняет"| MEM_WORK
  PLANNER -->|"финал"| RESULT
  MEM_WORK -.->|"контекст"| RESULT

  style USER fill:#e0e0e0,stroke:#999,color:#333
  style AGENT fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style PLANNER fill:#f0a500,stroke:#c88400,color:#fff
  style MEM_WORK fill:#7b68ee,stroke:#5a4db2,color:#fff
  style MEM_LONG fill:#7b68ee,stroke:#5a4db2,color:#fff
  style MCP fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style JIRA fill:#50c878,stroke:#3a9a5c,color:#fff
  style CONF fill:#50c878,stroke:#3a9a5c,color:#fff
  style DB fill:#50c878,stroke:#3a9a5c,color:#fff
  style ADR fill:#50c878,stroke:#3a9a5c,color:#fff
  style RESULT fill:#50c878,stroke:#3a9a5c,color:#fff

На схеме четыре слоя. Аналитик (серый) ставит задачу и получает результат — не участвует в цикле. Ядро агента (синий) принимает запрос и передаёт планировщику. Планировщик (жёлтый) — главный узел: он читает память (фиолетовый), вызывает инструменты через 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 для нашего агента — это четыре узла:

  1. planner — рассуждает и выбирает инструмент.
  2. executor — вызывает инструмент через MCP и получает результат.
  3. evaluator — оценивает, достаточно ли данных. Если нет — возвращает в planner.
  4. responder — формирует финальный ответ, когда данных хватает.

Важно: evaluator проверяет не качество данных («хороший ли отчёт?» — это делает человек), а полноту — все ли затребованные источники опрошены. Для этого в системном промпте планировщика жёстко прописано: «Ты должен обратиться к Jira, Confluence и БД минимум по одному разу, прежде чем формировать ответ».

100%
sequenceDiagram
  participant A as Аналитик
  participant Agent as AI-агент
  participant P as Планировщик ReAct
  participant M as Память
  participant T as Инструменты
  participant Jira as Jira API
  participant Conf as Confluence API

  A->>Agent: "Подготовь отчёт по спринту"
  Agent->>P: Декомпозиция задачи
  P->>M: Сохранить цель и контекст
  loop ReAct-цикл пока задача не решена
      P->>P: Рассуждение: что делать дальше?
      P->>T: Вызов: search_issues(sprint="Sprint 24")
      T->>Jira: JQL-запрос
      Jira-->>T: Список задач с приоритетами
      T-->>P: Результат поиска
      P->>P: Анализ: достаточно ли данных?
      P->>M: Сохранить промежуточный итог
      P->>T: Вызов: get_page("ADR шаблон")
      T->>Conf: Поиск страницы
      Conf-->>T: Содержимое
      T-->>P: Результат
      P->>P: Оценка: ещё нужен SQL?
      P->>T: Вызов: sql_query("SELECT ...")
      T-->>P: Данные из БД
      P->>M: Сохранить третий результат
  end
  P->>M: Запросить полный контекст
  M-->>P: Все собранные данные
  P->>Agent: Формирование финального ответа
  Agent-->>A: Отчёт + черновик ADR

  Note over P: Агент сам решает<br/>какой инструмент вызвать<br/>каждую итерацию

На диаграмме три итерации 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. Когда соберёте своего первого агента и он с пятой итерации наконец вызовет правильный инструмент в правильном порядке — вы испытаете странное чувство. Это смесь «оно работает!» и «а не слишком ли быстро я автоматизировал пол-своей работы?». Второе — временное. Новые задачи приходят быстрее, чем агент успевает их обрабатывать.