Я — Мила Муромцева, системный аналитик в Альфа-Банке. Пишу эту статью, чтобы поделиться нашим опытом, а с кодом и техчастью мне помогает наш разработчик Миша Буторин. Расскажем, как мы научили 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 |
Автор: psygalaxy


