Привет, Хабр!
Я — Мила Муромцева, системный аналитик в Альфа-Банке. Пишу эту статью, чтобы поделиться нашим опытом, а с кодом и техчастью мне помогает наш разработчик Миша Буторин. Расскажем, как мы научили LLM проверять документацию для платформы Альфа-Онлайн: переписали стандарт, поборолись с токенами и немного с хаосом.
TL;DR (если коротко)
Мы разработали и внедрили AI-агента в процесс ревью документации на платформе Альфа-Онлайн (веб-приложение Альфа Банка). Этот инструмент автоматизирует проверку документации, освобождая аналитиков от рутинных задач.
Проверка документации — трудоемка задача, требующая концентрации и внимания ревьювера и отвлекающая его от других задач. Агент ускоряет процесс, обеспечивая стабильное качество проверки и минимизируя задержки.
AI-агент полезен платформенным аналитикам (ревьюверам) и аналитикам продуктовых команд (авторам документации). Агент помогает быстрее получать обратную связь, фокусируясь на ключевых задачах.
Контекст и боль
Контекст
Чтобы разобраться в проблеме, давайте взглянем на наши процессы. Прежде чем зарелизить фичу, команда готовит поставку. Например, разработчик команды проходит код-ревью, а аналитик — ревью документации. Весь процесс подготовки поставки проходит по определенному workflow в Jira, в котором есть этап Docs review.
У платформенных аналитиков есть так называемые «дежурства», во время которых они проверяют поставки. Одна из задач платформенных аналитиков — провести ревью документации. Они проверяют, что документация написана в соответствии со стандартом и все новые изменения по продуктам там отражены. В общем, стоят на страже качества документации Банка.

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

Почему они мешали жить?
Данный этап влияет на lead time и замедляет процесс подготовки к релизу. А релизиться часто нужно ASAP.
Несогласованность в логике ответов разных ревьюверов иногда может сбивать с толку аналитиков продуктовых команд. Не хотелось бы, чтобы аналитики задавались вопросом, а на что обратят внимание ревьюверы в этот раз?
Какие идеи были ранее и почему не подошли?
№1. Отказаться от ревью совсем. Радикально, экономит время всех. Но в таком случае мы очень сильно теряем качество. На документацию смотрят не только участники разных команд, но и саппорты. Отпустив контроль полностью мы теряем важные детали работы функционала, а что еще важнее — место, где лежит документация. Сейчас все располагается в едином пространстве Confluence по всей платформе, что проверяется в том числе.
Иначе говоря, возвращаемся к тому, от чего ушли раньше. По кругу ходить как-то не хочется :)
№2. Использовать алгоритмическую автоматизацию (не AI based) для проверки. Можно конечно, но будет куча If’ов. Как ни крути, у документации нет синтаксиса, как в разработке. Все пишут по-разному. Сопровождать алгоритм выйдет сильно дороже ручной проверки.
Зачем здесь AI-агент?
Работа с текстом. AI-агент умеет хорошо работать с текстом и понимать суть, работать с разными разметками (HTML, Markdown и т.д.) и представлениями данных (сплошной текст, таблицы).
Гибкость в изменениях. В большинстве случаев для изменения или корректировки логики достаточно обновить промпт. Это может сделать аналитик, не привлекая ресурсы разработчика.
Мгновенная обратная связь. У агента одна задача — провести ревью документации. К задачам он приступает сразу. Не нужно ждать, пока платформенный аналитик проверит документацию.
Встраивание в производственный процесс. Агент встраивается в существующие процессы разработки и ревью в Jira (в будущем это будет масштабируемая история). Это делает его невидимым помощником в экосистеме компании.
Прозрачность процесса ревью. У агента есть четкий набор инструкций (как проверять) и требований (что проверять). Он будет работать всегда единообразно с минимизацией человеческого фактора.
Его ценность
Снижение времени проверки: автоматическое выявление нарушений или несоответствий.
Стабилизация качества: единый стандарт проверки вне зависимости от человеческого фактора.
Как всё устроено под капотом
Desmond — task-oriented AI-агент для автоматизации ревью внутренней документации на платформе Альфа-Онлайн.
Его задача — снижать нагрузку на платформенных аналитиков, ускорять фидбек для продуктовых аналитиков и обеспечивать единый стандарт качества.
Особенности
Task-oriented cognitive assistant: выполняет одну интеллектуальную задачу — ревью документации.
Без диалогового интерфейса: запускается по событию (webhook из Jira), работает реактивно и без состояния.
Встроенная персона — Desmond, без взаимодействия с пользователем.
LLM-as-a-Service: использует open-source LLM (Qwen-72B) через API, без fine-tuning, RAG и обучения (внутренний продукт Банка “AlfaGen”).
Few-shot prompting: логика работы задаётся через промпты с примерами.
Архитектура
Точка входа — задача в Jira, переходящая в нужный статус, триггерит webhook.
-
Основные шаги:
Определение, нужна ли вообще проверка.
Поиск релевантной документации по фиче.
Проверка соответствия стандартам.
Формирование и отправка комментария с результатами в Jira.
Технологии
Инфраструктура: Docker + Kubernetes во внутреннем AI-кластере банка.
Язык: Java 21 + Spring AI.
Модель: Qwen-72B, локально развёрнутая Банке.
Интеграции: Jira (webhook + REST API), Redis (кеш), jsoup (HTML-форматирование).
Будущие планы: интеграция с Confluence для аннотирования документации напрямую.
Красивая картинка с изображением архитектуры и логика работы:

Разбор: как агент решает задачу
Посмотрим на работу агенты со стороны процесса и распишем детально каждый шаг.

Общий сценарий в коде выглядит как-то так:
/**
* Метод проверяет, подходит ли задача для запуска сценариев
*
* @param webHookEventDto событие на изменение в Jira
*/
public void checkScenarioEligibility(WebHookEventDto webHookEventDto) {
log.info("Check scenario for task {} in project {}", webHookEventDto.getIssueKey(), webHookEventDto.getProjectKey());
if (isBlank(webHookEventDto.getIssueKey()) || isBlank(webHookEventDto.getProjectKey())) {
throw new IllegalArgumentException("documentationLink or changeDescription is blank");
}
val promptCheckDescription = docsPrompts.checkChangeDescription();
val question = escapeCurlyBrackets(String.format(promptCheckDescription, webHookEventDto.getChangeDescription()));
val checkResult = isChangeDescriptionTechnical(question);
if (checkResult.answer() == YES) {
log.warn("Change description is technical for task {} in project {}",
webHookEventDto.getIssueKey(),
webHookEventDto.getProjectKey());
publishService.publishComment(webHookEventDto.getIssueKey(), COMMENT_TECHNICAL_CHANGE);
return;
}
// проверяем линк на документацию
checkDocumentationLink(webHookEventDto);
startScenarios(webHookEventDto);
}
Теперь по шагам.
№1. Получение сообщения о новой документации на ревью
Здесь не будем надолго останавливаться. Главное, что мы на входе имеем следующую информацию:
Описание изменений (фича).
Ссылка на документацию.
№2. Анализ необходимости запуска сценария
Опустим косметические проверки и перейдем к самому интересному. Бот ориентируется на описание изменений и определяет, техническая доработка это или нет. Как? Он просто смотрит на примеры, ничего больше.
Промпт
Ты аналитик, работающий в IT-компании. Твоя задача — определить, является ли доработка технической или нет, основываясь на её описании.
Определи, является ли описание доработки технической.
Технической считается доработка, если она затрагивает исключительно код и не оказывает влияния на бизнес-логику. Такие изменения не требуют отражения в документации.
Примеры технических доработок:
Обновление версий библиотек и фреймворков.
Исправление ошибок линтера.
Написание или рефакторинг тестов.
Оптимизация кода без изменения функционала.
Изменение конфигураций для среды разработки или деплоя.
Все мелкие правки по дизайну (например, отступы, шрифты, цвета), которые не влияют на отображение графических элементов и UX клиента.
Логирование
...
#часть примеров под NDA ??♀️
Исключения: если в описании доработки указано, что приложение перемещено в новый репозиторий, это изменение должно быть отражено в документации и не считается техническим.
...
#да, исключения бывают. Некоторые технические нюансы мы сохраняем в документации.
ОПИСАНИЕ Доработки (тебе нужно проверить техническое ли оно по пунктам выше):
- %s; #сюда мы подставляем описание доработки
Здесь мы столкнулись с одной из проблем — нестабильные ответы LLM. Описали подробнее решение этой проблемы в главе «Нестабильность, хаос, баги, токен-хелл».
Если проверять нечего, так и пишем в ответе:

Если это тот самый случай с бизнес-фичей, идем дальше.
Насколько это точно? Не рискованно ли завязываться на «мнение» LLM? Рискованно. Однако LLM, также как и люди, совершают ошибки. Процент этих ошибок может быть допустимым, а может быть и нет.
Мы протестировали на реальных задачах: 27 из 30 обработалось успешно (89,7%). В целом, нас устраивает для старта. Понятно, что всего разнообразия не учесть. Будем тюнить со временем :)
№3. Проверка наличия документации по фиче
На этом этапе всё достаточно просто: необходимо найти фрагменты документации, которые описывают интересующую нас фичу, и ответить на два вопроса:
Найдено ли описание?
Если да — где оно находится и какие фрагменты текста были найдены?
Схема выглядит так:

Для поиска релевантных фрагментов мы используем следующий промпт:
Промпт
Ты — эксперт по анализу документации и выявлению соответствий. Твоя задача — анализировать предоставленный документ и находить в нём все релевантные фрагменты, соответствующие описанию доработки.
Правила работы:
1. Точно следуй формату ответа, указанному пользователем.
2. Находи релевантные фрагменты:
Определи ключевые слова, синонимы и близкие по смыслу выражения из описания доработки.
Используй расширенный контекст для поиска релевантных описаний, включая синонимы, описания функций, интерфейса, визуальных и функциональных изменений.
Выделяй разделы, где явно или косвенно упомянута данная доработка.
Перечисли эти разделы и тексты, описывающие доработку.
3. Проверяй раздел «7. История изменений»:
Упоминаются ли в этом разделе соответствующие изменения или найденные релевантные фрагменты?
Если информации недостаточно, запрашивай уточнение.
Будь точен и краток. Не добавляй лишнюю информацию.
# Ознакомься с документом: %s
# Найди все релевантные фрагменты текста, которые содержат ключевые слова, синонимы или совпадают с описанием доработки по смыслу. Укажи все найденные релевантные фрагменты, где упоминается данная доработка.
Описание доработки: %s
# Проверь, указаны ли соответствующие изменения или описание найденных релевантных фрагментов в разделе "7. История изменений".
# Ответь строго в одном из следующих форматов:
Форматы ответа:
1. Если описание доработки найдено в документации:
Нашел описание доработки в разделе(-ах):
"Название раздела 1*" — "Текст описания 1";
"Название раздела 2*" — "Текст описания 2"
...
Раздел "7. История изменений": [указано / не указано]
2. Если описание НЕ найдено в документации:
Информация о доработке не найдена в предоставленной документации.
3. Если информации недостаточно для проверки:
Для поиска описания доработки в документации необходимо уточнить описание изменений.
Единственная проблема — лимит токенов. Тогда ситуация становится чуть сложнее. Что с этим делать? Разделить документацию на чанки и анализировать «по кусочкам». Так мы и поступили.
Как правильно разбивать документацию на чанки?
Возникает логичный вопрос: какую стратегию разбиения (chunking strategy) выбрать?
На первый взгляд может показаться, что нужно логически делить текст на смысловые блоки — заголовки, параграфы, разделы. Однако в нашей задаче это не критично. Мы ищем одни и те же фрагменты информации вне зависимости от их положения в структуре документа. Поэтому оптимальной стала стратегия равномерного разбиения по количеству символов или токенов — она простая, быстрая и легко масштабируется.
Как не упереться снова в лимит?
Допустим, у нас уже есть текст и мы знаем лимит токенов. Как убедиться, что при разбиении документа на чанки мы не превысим лимит и при этом не потеряем контекст?
Ключ — в правильном расчёте размера чанков и перекрытии (overlap) между ними. Мы использовали простую формулу:
где: — максимальное количество токенов (например,
)
— количество токенов, зарезервированных под промпт (например,
)
— среднее количество символов на токен (например,
)
— количество дублируемых символов (например,
)
— длина заголовка в символа (например,
)
— максимальное количество символов в чанке
Смысл её в том, чтобы заранее рассчитать максимально допустимое количество символов на чанк (), включая резерв под перекрытие (
) и заголовок. Коэффициент
был подобран эмпирически — с запасом, чтобы учесть возможные колебания в длине токенов.

При нарезке текста на чанки мы:
делим его на куски длиной
символов;
добавляем к каждому i-му чанку
символов из конца предыдущего, чтобы сохранить связный контекст;
подтягиваем заголовок из предыдущего раздела, чтобы агент мог точно определить, к какой части документа относится данный чанк.
Эта стратегия позволяет не только нарезать документы эффективно, но и минимизировать потерю смысла на границах чанков.
Финальный этап — анализ и саммари
После того как все чанки обработаны и даны ответы по каждому из них, остаётся самое важное — собрать результаты и сделать сводное резюме.
Промпт для саммари:
Ты — эксперт по анализу текста. Задача состоит в том, чтобы агрегировать и проанализировать результаты, полученные от LLM, после того как текст документации был разделён на чанки и каждый чанк был проанализирован отдельно.
## Контекст задачи:
1. На предыдущем шаге LLM проводила поиск описания фичи в документации. Текст документации был разделён на чанки, и LLM уже обработала каждый чанк, выполнив поиск релевантных фрагментов.
2. Ответы, полученные от LLM, содержат результаты анализа каждого чанка документации.
3. Теперь необходимо агрегировать все эти ответы и составить итоговое саммари.
## Что нужно сделать:
1. Проанализируй все ответы LLM, полученные по чанкам.
2. Найди, в каких чанках были найдены релевантные фрагменты, соответствующие описанию фичи (это описание фичи передано в виде переменной <changeDescription>
. Содержимое changeDescription: %s ).
3. В ответах, где такие фрагменты найдены, укажи название разделов, в которых они содержатся, и приведите текст этих фрагментов.
4. Также проверь, указаны ли соответствующие изменения или описание найденных фрагментов в разделе "7. История изменений" в каждом из этих чанков.
- Ответь строго "указано" или "не указано". Если информации недостаточно, запроси уточнение.
Содержимое всех ответов LLM, полученных по чанкам: %s
## Форматы итогового саммари:
1. Если описание фичи найдено в ответах LLM:
Нашел описание доработки в разделе(-ах):
"Название раздела 1*" — "Текст описания 1";
"Название раздела 2*" — "Текст описания 2"
...
Раздел "7. История изменений": [указано / не указано]
2. Если описание фичи НЕ найдено в ответах LLM:
* Информация о доработке не найдена.
3. Если информации недостаточно для проверки:
* Для поиска описания фичи в ответах LLM необходимо уточнить описание изменений.
ВАЖНО! Верни только Итоговое саммари четко в одном из указанных форматов.
Пример ответа, когда описани фичи найдено:

Пример ответа, описание фичи не найдено:

4. Проверка документации на соответствие стандарту
Это, пожалуй, самая непростая задача в нашем сервисе — со звёздочкой. Объёмная документация, не менее объёмный стандарт — и всё это нужно сверить. Задача: пройтись по каждому разделу документа и определить, насколько он соответствует требованиям стандарта.
Что из себя представляет документация?
В документации описывается работа фичи. Она структурирована по разделам:
Таблица (без названия) — «шапка» документации.
Основная информация: таблица с глоссарием, ограничениями, ссылками (в том числе на документации микросервисов).
Предусловия: что должно быть выполнено до запуска фичи.
Постусловия: что происходит после выполнения фичи.
Сценарии использования — UML Use Case диаграмма.
Алгоритм: описание алгоритма и интеграций в UML Sequence или других нотациях.
Детальное описание алгоритма — табличное описание шагов: действия клиента, поведение системы, логика интеграций и макеты интерфейсов.
История изменений: список доработок и авторы изменений.
Здесь нам очень повезло в том, что в нашем случае мы имеем регламентированную структуру, на которую можем смело опираться.
Что такое стандарт?
Стандарт — это набор требований к структуре и содержанию документации.
Он определяет, какие разделы должны быть в документе, какие данные обязательны в каждом из них. Например, в «шапке» должны быть указаны: команда, описание фичи, диплинки, репозитории, фича-тогглы и т.п.
Стандарт требует согласованности между разделами: если в одном разделе упоминается микросервис, то в другом должна быть ссылка на его документацию.
Как мы решали эту задачу? Вот так:

Сначала мы попробовали запустить проверку на небольшой документации.
На входе: ссылка на стандарт и ссылка на сам документ. Задача — сравнить и понять, насколько документ соответствует требованиям.Проблема проявилась сразу: LLM интерпретировала требования по-разному. Нам они казались очевидными, но только потому, что мы в контексте.
Быстро стало понятно, что текущий стандарт не подходит. Он слишком «человеческий»: много пояснений, добавленных для аналитиков, которые не имеют отношения к технической проверке.
Решение — переписали стандарт в «LLM-читаемом формате». Подробнее описано ниже.
Запустили тест снова — стало лучше. Но часть требований всё равно игнорировалась, результаты каждый раз отличались, и для обычной (не огромной!) документации быстро возникал лимит токенов — и от стандарта, и от текста.
Мы решили делить документ на чанки — но уже логически, по структуре. Подробнее об этом расскажем ниже.
Сейчас требования практически не теряются, а документы среднего размера обрабатываются целиком — ура!
Оставалась одна проблема — слишком объёмный итоговый отчёт (200-250 строк).
Чтобы с ним было удобно работать, мы сделали отдельный формат финального саммари, который сократил саммари до 50 строк без потери качества.
Как переписать стандарт так, чтобы LLM-понимала, что именно нужно проверять?
Возьмем кусочек стандарта, описывающий шапку страницы:

Нам, как аналитикам, которые понимают, про какие репозитории идет речь, как выглядит ссылка на Epic в Jira и т.д. все понятно. А LLM в этот момент:

Для LLM этого недостаточно. Она не может «догадаться», если не задать ей рамки явно.
Примеры вопросов, которые остаются без ответа:
Что считается ссылкой на команду — Confluence? Notion? Jira?
Jira — это макрос или обычный URL?
Как проверить, что ссылка на репозиторий корректная? О каком репозитории идет речь?
Какие требования к неймингу? Доп. Страницы для LLM - это только ссылка. Доступа к контенту, доступному по этой ссылке, нет.
Всегда ли именно эта таблица обязательна, или бывают исключения?
На все эти вопросы нужно ответить при составлении LLM-читаемого стандарта. Вот пример того, что у нас получилось:


Для составления мы использовали few-shot prompting, так как возможность обучения на момент разработки и написания статьи отсутствуют. Для каждого пункта, для каждого поля мы привели примеры правильного и неправильного заполнения.
И так для всех разделов. В итоге LLM-читаемый стандарт получился внушительных размеров. Зато все расписано до мелочей.
А как быть с кросс-раздельными проверками?
Некоторые требования касаются согласованности между разделами.
Например: «Все ссылки на документацию сервисов, описанных в разделе 'Алгоритм', должны быть указаны в разделе 'Основная информация'.»
Для таких случаев мы ввели отдельный тип требований — кросс-проверки. Они оформлены как отдельные правила, в которых явно указано, какие разделы участвуют и как должна выглядеть связь между ними. Один из примеров кросс-проверок:

Подробнее о реализации — в следующем разделе, посвящённом стратегии чанкования
4.1. Chunking strategy
В этой задаче нам важно обеспечить максимально точную проверку по всем требованиям, поэтому мы стараемся снизить «шум» в данных и структурировать информацию так, чтобы LLM было легче анализировать:
чёткие требования,
разбитые по разделам,
никаких «всё сразу в одной куче».
А это значит, что мы будем здесь использовать стратегию логического разделения по разделам. Выглядит это как-то так:

У нас есть LLM-читаемый формат, разбитый по разделам и документация, также структурированная по разделам.
Решение:
Разделяем на чанки документацию: 1 чанк — 1 раздел.
Разделяем на чанки стандарт: 1 чанк — 1 требование.
Сопоставляем чанки стандарта и документации между собой. Проверка оформления: 1 чанк (раздел) документации ~ 1 чанку стандарта (требование к разделу). Кросс-проверки: 1 чанк кросс-проверки (отдельный раздел с кросс-проверками) ~ n чанкам (разделам) документации.
Отправляем в LLM запросы на проверку по каждому соответствию.
Как это работает покажу на примерах.
Проверка оформления
1 чанк стандарта

1 чанк документации
Представлено графически для наглядности, в реальности данные передаются в LLM в HTML-формате.

Кросс-проверка
1 чанк стандарта

2 чанка документации


Теперь нужно сложить воедино все ответы LLM.
Итоговый промпт:
Ты -- опытный системный аналитик в IT-компании. Твоя задача -- провести точную проверку соответствия документации стандарту.
# Формат задачи:
1. У тебя есть название требования стандарта, его содержимое и документ для анализа.
2. Если в названии стандарта содержится фраза "Кросс проверка", это означает, что тебе нужно провести кросс-проверку нескольких разделов документации.
3. В остальных случаях -- это обычная проверка соответствия одному стандарту.
# ВАЖНО:
- Ты обязан начинать ответ с текста:
- Кросс проверка: <наименование требования стандарта>; -- если это кросс-проверка
- Требование: <наименование требования стандарта> -- если это обычная проверка
- В тексте должен быть анализ каждого пункта стандарта
- Каждый пункт комментируется отдельно, даже если он полностью соответствует стандарту
- При несоответствии: объясни, что не так и как исправить
---
# Наименование требования стандарта:
%s //здесь используется название раздела
# Содержимое требования стандарта:
%s //здесь используется чанк стандарта
# Содержимое документа (или документов):
%s //здесь используется чанк (или чанки в случае кросс-проверки) документации
Проведи детальный анализ по каждому пункту требования, строго соблюдай формат ответа.
После прохождения всех проверок собираем итоговое саммари, чтобы зафиксировать результат анализа по всему документу.
Как выглядит код:
public List<String> compareChunkStandardAndDocumentation(ChunkDocResult chunkDocResult) {
val requestPrompt = docsPrompts.getCompareStandardAndDocumentationPrompt();
val standardMap = chunkDocResult.getStandard();
val answerList = new ArrayList<String>();
List<Future<String>> futures = new LinkedList<>();
// проверяем параллельно каждый блок стандарта и документацию
standardMap.forEach((standardName, standardValue) -> {
var feature = virtualThreadExecutorService.getVirtualExecutor().submit(() -> {
val documentationNames = standardName.split("; ");
val documentationValues = Arrays.stream(documentationNames)
.map(docsName -> docsName + ": " + chunkDocResult.getDocumentation().get(docsName))
.toList();
// маркируем - это кросс проверка или проверка оформления
val standardNameGeneral = documentationValues.size() > 1 ? CROSS_CHECK + standardName : standardName;
val documentationValuesString = String.join(NEWLINE, documentationValues);
val request = String.format(requestPrompt, standardNameGeneral, standardValue, documentationValuesString);
log.info("Start compare standard {} with documentation", standardNameGeneral);
return chatModel.call(request);
});
futures.add(feature);
});
// собираем результаты после параллельной обработки
futures.forEach(future -> virtualThreadExecutorService.fillListOfVirtualThreadResult(future, answerList));
return answerList;
}
Мы видим, что при использовании чанков LLM перестала пропускать требования, проверки стали стабильнее, ответы — содержательнее (будут ниже).
Пример ответа LLM до чанкования:
Документация по стандарту:
*Структура документа*: Документ имеет правильную структуру, включающую все необходимые разделы: Table, Table of Contents, Основная информация, Предусловия, Постусловия, Сценарии использования, Алгоритм, Детальное описание алгоритма и История изменений.
*Таблица*: Таблица имеет правильную структуру и содержит все необходимые поля: Команда, Бизнес-ценность, Epic, Макеты, Git Front, Git Middle, Feature-Toggle и Deeplink.
*Основная информация*: Раздел содержит необходимые таблицы: Словарь, Ограничения и Полезные ссылки.
*Предусловия*: Раздел содержит необходимые шаги, которые пользователь должен выполнить, чтобы доступ к функциональности страницы.
*Постусловия*: Раздел содержит результаты, которые пользователь получает после выполнения функциональности.
*Сценарии использования*: Раздел содержит диаграмму, которая иллюстрирует взаимодействие между клиентом и системой.
*Алгоритм*: Раздел содержит диаграмму, которая иллюстрирует взаимодействие между клиентом и системой.
*Детальное описание алгоритма*: Раздел содержит необходимые шаги, которые система выполняет в ответ на действия клиента.
*История изменений*: Раздел содержит информацию об изменениях, внесенных в документацию.
//вывод на уровне “вроде норм”. Сложно понять, что LLM реально проанализировала, а что - нет.
В целом, документация соответствует стандарту, но необходимо проверить следующее:
//никакой конкретики ?
* Правильность ссылок в таблице и разделе "Полезные ссылки".
* Правильность дизайна в разделе "Детальное описание алгоритма".
* Правильность информации в разделе "История изменений".
Рекомендации:
* Проверить правильность ссылок в таблице и разделе "Полезные ссылки".
//ну спасибо, а тебя мы зачем попросили все это проверить? ?
* Проверить правильность дизайна в разделе "Детальное описание алгоритма".
* Проверить правильность информации в разделе "История изменений".
* Убедиться, что документация соответствует стандарту и содержит все необходимые разделы и информацию.
Пример ответа LLM после чанкования.
Примечание. Привела пример анализа только одного чанка (первая таблица), так как в сумме по чанкам получается тот самый объёмный текст длиной в 200-250 символов.
Ответ после чанкования
Проверяю документ на соответствие каждому пункту требований.
**1. Таблица должна состоять из 8 строк и 2 непустых столбцов.**
Требование выполнено. Таблица имеет 8 строк и 2 непустых столбца.
**2. Пример правильной структуры первого столбца:**
Требование выполнено. Первый столбец имеет правильную структуру, соответствующую примеру в стандарте.
**3. Второй столбец должен содержать:**
**3.1. Команда - ссылка на страницу команды.**
Требование выполнено. Второй столбец содержит ссылку на страницу команды "X".
**3.2. Бизнес-ценность - краткое описание бизнес-функции.**
Требование выполнено. Второй столбец содержит краткое описание бизнес-функции "Реализация в Альфа-Онлайн XXXX"
**3.3. Epic или задача в JIRA**
Требование не выполнено. Второй столбец содержит текст "см в jira", что не является ссылкой на задачу в JIRA. Исправление: необходимо добавить ссылку на задачу в JIRA.
**3.4. Макеты - ссылка на макеты в Figma.**
Требование выполнено. Второй столбец содержит ссылку на макеты в Figma.
**3.5. Git Front - ссылка на репозиторий приложения в проекте XXXXXX.**
Требование выполнено. Второй столбец содержит ссылку на репозиторий приложения в проекте XXXXX.
**3.6. Git Middle - ссылка на репозиторий сервиса в сети банка (XXXX.XXXX)**
Требование не выполнено. Второй столбец содержит ссылку на репозиторий на GitHub, что не соответствует требованиям. Исправление: необходимо добавить ссылку на репозиторий сервиса в сети банка (XXXX.XXXX).
**3.7. Feature-Toggle - название и краткое описание FT (Feature-Toggle).**
Требование частично выполнено. Второй столбец содержит название FT "XXXXX", но отсутствует краткое описание. Исправление: необходимо добавить краткое описание FT.
**3.8. Deeplink - описание диплинков функционала.**
Требование выполнено. Второй столбец содержит описание диплинков функционала "XXXX".
**Требования к оглавлению**
Требование выполнено. Документ содержит макрос оглавления.
В целом, документ частично соответствует требованиям. Необходимо исправить пункты 3.3, 3.6 и 3.7.
Ну совсем другое дело!
Формирование итогового отчета (саммари)

На этом этапе у нас уже есть детализированные ответы от LLM по каждому требованию стандарта.
Однако при сборке итогового саммари возникла новая проблема:
❗️ В одном большом ответе LLM не всегда может корректно агрегировать замечания, даже если они явно присутствуют в предыдущих ответах.
Причина очевидна — слишком большой объём текста, высокий уровень детализации, а значит, большой риск потери важных фрагментов при свёртке. Чтобы избежать этого, мы решили ввести строгую структуру и формализованный формат отчета:
Промпт
Вы работаете с результатами проверки документации на соответствие стандарту. Вам нужно объединить данные о проверке структуры, проверке разделов и ошибок в один отчет.
Ваша задача:
1. Объедините все результаты в единый отчет.
2. Отчет должен быть максимально точным и подробным. Укажите все обнаруженные ошибки, чтобы пользователь не пропустил ни одну деталь.
3. Отсортируйте ответы по номеру требования и в конце добавьте вывод об общем статусе проверки. Не забудьте про раздел 0. Таблица с нее нужно начать.
4. Проверьте корректность ссылок и форматов данных.
5. Убедитесь, что формулировки соответствуют установленным стандартам.
Результаты: %s //здесь подставляем совокупность ответов по всем чанкам
Формат отчета:
НАЧАЛО ФОРМАТА
Возможные значения поля Статус:
- "✅ Выполнено"
- "❌ Не выполнено"
- "⚠️ Частично выполнено"
- "❌ Нет данных, тк раздел пустой или случилась ошибка"
Обязательно:
В поле статус в скобках "()" ВСЕГДА указывать:
- детали проведенной проверки
h1. Итоговый отчет по проверке документа
- Общий статус проверки: [вставить краткое резюме - например: "Частично выполнено", "Выполнено", "Много критичных недочетов" и т.д.]
- Оценка по результатам проверки: [X из 100]
h2. Структура
| Критерий || Статус |
| Структура документа || |
h2. 0. Таблица
| Критерий || Статус |
| Структура таблицы | |
| Команда (ссылка) | |
| Бизнес-ценность | |
| Epic (ссылка) | |
| Макеты (ссылка) | |
| Git Front (ссылка) ||
| Git Middle (ссылка) | |
| Feature-Toggle | |
| Deeplink ||
h2 1. Основная информация
| Критерий || Статус |
| Ссылки на документацию для каждого сервиса | |
| Глоссарий | |
| Ограничения | |
| Полезные ссылки | |
//.. и так далее для каждого раздела
h2 Кросс-проверки
| Критерий || Статус |
| Все сервисы из "Git Middle" имеют ссылки на документацию в "1. Основная информация" | |
| Названия сервисов из "Git Middle" отражены в "5. Алгоритм" | |
//.. и так далее для всех кросс-проверок
h2 Вывод
ОКОНЧАНИЕ ФОРМАТА
Что делает LLM на этом этапе:
Объединяет все результаты проверок (по разделам и кросс-проверкам).
Формирует таблицы по каждому разделу с указанием всех критериев.
Отмечает статус каждого пункта: выполнен/не выполнен/частично выполнен.
Добавляет детали ошибок в скобках рядом со статусом — чтобы пользователь ничего не упустил.
В финале — генерирует краткий вывод и общую оценку*.
*Оценка является субъективной и основывается на “мнении” LLM (по крайней мере, пока что)
public String checkDocsCompareWithStandardLlm(WebHookEventDto webHookEventDto) {
val answerList = new ArrayList<String>();
val chunkDocResult = createChunkDocResult(webHookEventDto);
// Добавляем ответ по проверке на соответствие структуре стандарта
answerList.add(checkStandardStructure(chunkDocResult));
// Добавляем ответ по проверке на соответствие разделов стандарта и документации
answerList.addAll(compareChunkStandardAndDocumentation(chunkDocResult));
return summarizeAnswer(answerList);
}
// метод объединения всех результатов проверки
private String summarizeAnswer(List<String> answerList) {
val requestPrompt = docsPrompts.getSummaryAllDocsComparingPrompt();
val answerString = String.join(NEWLINE, answerList);
val request = String.format(requestPrompt, answerString);
return chatModel.call(request);
}
Так выглядит отчёт о проверке на соответствие стандарту в комментарии к задаче в Jira:

И ниже такие же таблички для каждого раздела. А после — краткая выжимка:

Что пошло не так (нестабильность, хаос, баги, токен-хелли) и как мы это починили
Спрятанные элементы

Многие элементы документации в Confluence «прячутся» внутри макросов, тегов и других визуальных обёрток. При попытке анализа через обычный текст LLM просто не видит, например, ссылки — они выглядят как обычный текст.
? Решение: обрабатываем HTML напрямую. Так мы можем вытащить реальный контент, включая ссылки, форматирование и скрытые блоки. Это обязательный шаг, если хотим точный анализ.
Лимит токенов
Классика. Самая неудобная проблема на текущий момент.
? Решение:
«Чанкование». Разделение на чанки, последовательная проверка документации по кусочкам и суммаризация результатов.
Очистка текста. Определили whitelist тегов, которые действительно несут смысл (заголовки, ссылки, таблицы и т.д.), остальные — в мусор. Это уменьшает шум и экономит токены.
Примечание. Даже с логическим делением мы всё ещё можем упереться в лимиты — это остаётся известным ограничением. Пока просто учитываем это как риски: сверхбольшие документы встречаются нечасто.
Многобукв → LLM не справляется
Когда в промпте слишком много требований и данных, модель начинает путаться: формулировки становятся размытыми, какие-то пункты игнорируются. Здесь нас выручают:
«Чанкование». При возможности лучше разбивать текст на логические блоки и обрабатывать отдельно. Чем меньше контекст, тем выше точность.
Чёткие структура и формат для саммари. Вместо «ну вроде всё ок» — детальный разбор по каждому пункту стандарта. Формат саммари должен быть строгим и однообразным, тогда ответы проще обрабатывать.
Нестабильные ответы

Когда от LLM требуется бинарное решение (например, «Да» или «Нет»), просто хорошо написанного промпта может быть недостаточно — модель может «фантазировать» или давать слишком расплывчатые ответы.
? Решение. Для таких случаев мы используем Structured Output — форматируем запросы и просим возвращать результат в строго заданной структуре (JSON, таблицы, булевы значения и т.д.). Это сильно повышает стабильность и предсказуемость результата.
// отправляем запрос в LLM и мапим его в нужную нам entity
private YesNoAnswerRecord isChangeDescriptionTechnical(String question) {
return ChatClient.create(openAiChatModel).prompt(new Prompt(question)).call().entity(YesNoAnswerRecord.class);
}
Долгое выполнение запросов
LLM — штука небыстрая. Пока «подумает», пока вернёт ответ, проходит ощутимое количество времени. А если у нас десятки чанков — привет очередь из запросов и вечность в ожидании.
Чтобы не наблюдать, как летят секунды, мы подключили Java Virtual Threads и распараллелили запросы. Вызов чанков и проверок происходит параллельно, далее результаты собираются в один список. Вместо конвейера асинхронных запросов выполняем все запросы сразу, а длительность будет зависеть от самого долгого запроса в LLM.
Результат на одном из примеров: вместо 1 мин 20 сек — 26 секунд. И никаких жертв — всё стабильно, аккуратно и параллельно.
Как это выглядит в коде:
List<Future<String>> futures = new LinkedList<>();
// Вызываем виртуал тред
docsLinkChunks.forEach((chunkIndex, chunk) -> {
Future<String> future = virtualThreadExecutorService.getVirtualExecutor().submit(() -> {
String request = String.format(requestPrompt, chunk, changeDescription);
return chatModel.call(request);
});
futures.add(future);
});
// Заполняем список ответов из многопоточности
List<String> answerList = new ArrayList<>();
futures.forEach(future -> virtualThreadExecutorService.fillListOfVirtualThreadResult(future, answerList));
Что получилось в итоге?
Есть ли польза от агента? Как теперь изменились результаты ревью? Посмотрим наглядно на примерах, требующих комментариев.
Пример №1: «Проверка на соответствие стандарту»
Если ранее Аналитик №1 писал «Добрый день, поправьте, пожалуйста, документацию в соответствии со стандартом» и оставлял ссылку, на стандарт, а Аналитик №2 отписывал комментарии вида...:
«Добавить диплинк в шапку».
«Указать сервис X в шапке».
«Так где точка входа? Нужно их все указать и приложить макеты».
«Все макеты привести к стандарту: десктоп 600px, а моб 350px».
...
«Откуда у диплинка пример ответа?»
«Здесь правильнее вставлять ссылкой, по аналогии как git middle сделано»
Все замечания имеют место быть, но поскольку мы все (платформенные аналитики) — разные люди, то и пишем мы замечания по-разному. Это может сбивать с толку продуктовых аналитиков.
На моем личном опыте: иногда из-за банальной усталости я, как платформенный аналитик, могу пропустить ошибки, связанные с ссылками и любыми скрытыми элементами. Также сложно отслеживаются ошибки согласованности между разделами. Например, в детальном описании алгоритма сервис описан, а в шапку репозиторий не добавлен — могу случайно пропустить. Поэтому такие моменты в списке замечаний отсутствуют. Агентом же они всегда проверяются.
При этом иногда вопросы касаются логики. Например, «Зачем здесь сервис вызывается 2 раза?», Дезмонд не увидит в этом ошибки. Во всяком случае, пока что :)
Аналитик №1 писал: «Не поняла, где описан функционал. Куда смотреть?»
Аналитик №2 писал: «Не вижу описания в документации, добавьте описание доработки».
Агент пишет: «Информация о доработке не найдена в предоставленной документации».
Проверка стала менее рутинной. Теперь не нужно вручную вычитывать корректность ссылок и формат — агент справляется с этим.
У аналитиков появилась «шпаргалка» в виде отчёта с результатами проверок. Это сокращает время на уточнение формулировок и требований.
Этап ревью ускорился. Аналитики продуктовых команд стали быстрее вносить правки сразу после прогонки документации через агента. Даже несмотря на то, что мы предупреждали — агент всё еще в разработке.
Нет необходимости ждать или проводить повторное ревью изменений, можно перезапустить агента и получить результат уже с учетом правок.
Большие документы — пока упираемся в лимит токенов при проверке на соответствие стандарту.
Макеты — проверяется только наличие, не контент.
Нестандартные кейсы — если структура документации сильно отличается от шаблонной, автоматически проверить её корректность не получится.
Любимый лимит токенов. Пока что в проверке на соответствие стандарту мы на него натыкаемся.
Обработка нескольких документов сразу в рамках одной поставки. И такое бывает :) Сейчас такие кейсы требуют ручной обработки.
Проверка документации до этапа ревью — в идеале прямо в процессе написания.
Уйти от огромного отчета к короткой выжимке, в которой описано только то, что нужно исправить.
Массовая проверка всей документации в пространстве, составление общего отчёта и трекинг качества по командам/проектам.
Автоматический аппрув документации в случае, когда проверки по документации были выполнены успешно.
Возможно, мы где-то «переизобрели велосипед».
Могли упустить best practices.
Или есть более простые подходы, о которых мы не знали.
...то сейчас от агента мы получаем подробную таблицу с каждым пунктом проверки, списком необходимых исправлений. При этом таблица всегда одинаковая.
Результат сравнения:
Пример №2: «Проверка наличия фичи»
Теперь результат сравнения всегда единообразен. Просто теперь данный поиск автоматизирован.
Что упростилось/ускорилось?
А что по цифрам?
В нашем процессе раскатки фич мы замеряем время каждого этапа. В случае с проверкой документации нас интересует время нахождения задачи в статусе «Docs review», т.е. продолжительность ревью документации. Сравнили медианное время ревью месяцем ранее и медианное время спустя месяц после внедрения инструмента.
Получилось так: 1 час 55 минут уменьшились до 1 часа 19 минут. Что составляет 31,3%
Хорошо это или нет — решать вам.
Ограничения
Что ещё осталось нерешённым?
Чего хочется дальше?
О чём было бы круто услышать мнение читателей
Мы не специализируется на разработке агентов и не посвящаем этому все наше рабочее время. Данный агент - side проект группы энтузиастов в рамках нашего технического развития по одному из направлений :)
Если ты дочитал(а) до этого места — будем очень благодарны за обратную связь ?
Благодарности
Семен Корольков и Влад Курчин (@simple_analytics) — спасибо за идеи, внимательность и то, что помогали довести всё до ума. За обсуждения, уточнения и споры, без которых финальная версия точно была бы слабее.
Мише Буторину (@MoshkaBortmanXXL) — за код, терпение и то, что поддержал идею, когда она была ещё просто гипотезой.
Артёму Гринько — за поддержку нашего проекта и наставления на верный путь.

P.S. Полезные ссылки
Категория |
Ссылка |
Основы LLM и AI-агентов |
|
Технологии в основе Desmond |
|
Решение проблем с LLM |
Руководство по Structured Output с LLM Exploring the Power of Virtual Threads in Java 21 | by coffee and tips | Medium |