Привет, Хабр! 

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

Архитектура

  1. Точка входа — задача в Jira, переходящая в нужный статус, триггерит webhook.

  2. Основные шаги:

    • Определение, нужна ли вообще проверка.

    • Поиск релевантной документации по фиче.

    • Проверка соответствия стандартам.

    • Формирование и отправка комментария с результатами в 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. Если да — где оно находится и какие фрагменты текста были найдены?

Схема выглядит так:

Для поиска релевантных фрагментов мы используем следующий промпт:

Промпт

Ты — эксперт по анализу документации и выявлению соответствий. Твоя задача — анализировать предоставленный документ и находить в нём все релевантные фрагменты, соответствующие описанию доработки.

Правила работы:

1. Точно следуй формату ответа, указанному пользователем.

2. Находи релевантные фрагменты:

  • Определи ключевые слова, синонимы и близкие по смыслу выражения из описания доработки.

  • Используй расширенный контекст для поиска релевантных описаний, включая синонимы, описания функций, интерфейса, визуальных и функциональных изменений.

  • Выделяй разделы, где явно или косвенно упомянута данная доработка.

  • Перечисли эти разделы и тексты, описывающие доработку.

3. Проверяй раздел «7. История изменений»:

  • Упоминаются ли в этом разделе соответствующие изменения или найденные релевантные фрагменты?

  • Если информации недостаточно, запрашивай уточнение.

Будь точен и краток. Не добавляй лишнюю информацию.

# Ознакомься с документом: %s

# Найди все релевантные фрагменты текста, которые содержат ключевые слова, синонимы или совпадают с описанием доработки по смыслу. Укажи все найденные релевантные фрагменты, где упоминается данная доработка.

Описание доработки: %s

# Проверь, указаны ли соответствующие изменения или описание найденных релевантных фрагментов в разделе "7. История изменений".

# Ответь строго в одном из следующих форматов:

Форматы ответа:

1. Если описание доработки найдено в документации:

Нашел описание доработки в разделе(-ах):

"Название раздела 1*" — "Текст описания 1";

"Название раздела 2*" — "Текст описания 2"

...

Раздел "7. История изменений": [указано / не указано]

2. Если описание НЕ найдено в документации:

Информация о доработке не найдена в предоставленной документации.

3. Если информации недостаточно для проверки:

Для поиска описания доработки в документации необходимо уточнить описание изменений.

Единственная проблема — лимит токенов. Тогда ситуация становится чуть сложнее. Что с этим делать? Разделить документацию на чанки и анализировать «по кусочкам». Так мы и поступили. 

Как правильно разбивать документацию на чанки?

Возникает логичный вопрос: какую стратегию разбиения (chunking strategy) выбрать?

На первый взгляд может показаться, что нужно логически делить текст на смысловые блоки — заголовки, параграфы, разделы. Однако в нашей задаче это не критично. Мы ищем одни и те же фрагменты информации вне зависимости от их положения в структуре документа. Поэтому оптимальной стала стратегия равномерного разбиения по количеству символов или токенов — она простая, быстрая и легко масштабируется.

Как не упереться снова в лимит?

Допустим, у нас уже есть текст и мы знаем лимит токенов. Как убедиться, что при разбиении документа на чанки мы не превысим лимит и при этом не потеряем контекст?

Ключ — в правильном расчёте размера чанков и перекрытии (overlap) между ними. Мы использовали простую формулу:

C_{chunk} = (T_{max} - T_{prompt})* r - C_{dup} - C_{header}

где:
T_{max} — максимальное количество токенов (например, 32 000)
T_{prompt} — количество токенов, зарезервированных под промпт (например, 1000)
r — среднее количество символов на токен (например, 5)
C_{dup} — количество дублируемых символов (например, 500)
C_{header} — длина заголовка в символа (например, 67)
C_{chunk} — максимальное количество символов в чанке

Смысл её в том, чтобы заранее рассчитать максимально допустимое количество символов на чанк (C_{chunk}), включая резерв под перекрытие (C_{dup}) и заголовок. Коэффициент rбыл подобран эмпирически — с запасом, чтобы учесть возможные колебания в длине токенов.

Визуализация алгоритма чанкования
Визуализация алгоритма чанкования

При нарезке текста на чанки мы:

  • делим его на куски длиной C_{chunk} символов;

  • добавляем к каждому i-му чанку C_{dup} символов из конца предыдущего, чтобы сохранить связный контекст;

  • подтягиваем заголовок из предыдущего раздела, чтобы агент мог точно определить, к какой части документа относится данный чанк.

Эта стратегия позволяет не только нарезать документы эффективно, но и минимизировать потерю смысла на границах чанков.

Финальный этап — анализ и саммари

После того как все чанки обработаны и даны ответы по каждому из них, остаётся самое важное — собрать результаты и сделать сводное резюме.

Промпт для саммари:

Ты — эксперт по анализу текста. Задача состоит в том, чтобы агрегировать и проанализировать результаты, полученные от 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 или других нотациях.

  • Детальное описание алгоритма — табличное описание шагов: действия клиента, поведение системы, логика интеграций и макеты интерфейсов.

  • История изменений: список доработок и авторы изменений.

Здесь нам очень повезло в том, что в нашем случае мы имеем регламентированную структуру, на которую можем смело опираться. 

Что такое стандарт?

Стандарт — это набор требований к структуре и содержанию документации.

Он определяет, какие разделы должны быть в документе, какие данные обязательны в каждом из них. Например, в «шапке» должны быть указаны: команда, описание фичи, диплинки, репозитории, фича-тогглы и т.п.

Стандарт требует согласованности между разделами: если в одном разделе упоминается микросервис, то в другом должна быть ссылка на его документацию.

Как мы решали эту задачу? Вот так:

  1. Сначала мы попробовали запустить проверку на небольшой документации.
    На входе: ссылка на стандарт и ссылка на сам документ. Задача — сравнить и понять, насколько документ соответствует требованиям.

  2. Проблема проявилась сразу: LLM интерпретировала требования по-разному. Нам они казались очевидными, но только потому, что мы в контексте.

  3. Быстро стало понятно, что текущий стандарт не подходит. Он слишком «человеческий»: много пояснений, добавленных для аналитиков, которые не имеют отношения к технической проверке.

  4. Решение — переписали стандарт в «LLM-читаемом формате». Подробнее описано ниже.

  5. Запустили тест снова — стало лучше. Но часть требований всё равно игнорировалась, результаты каждый раз отличались, и для обычной (не огромной!) документации быстро возникал лимит токенов  — и от стандарта, и от текста.

  6. Мы решили делить документ на чанки — но уже логически, по структуре. Подробнее об этом расскажем ниже.

  7. Сейчас требования практически не теряются, а документы среднего размера обрабатываются целиком — ура!

  8. Оставалась одна проблема — слишком объёмный итоговый отчёт (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 раздел. 

  2. Разделяем на чанки стандарт: 1 чанк — 1 требование.

  3. Сопоставляем чанки стандарта и документации между собой. Проверка оформления: 1 чанк (раздел) документации ~ 1 чанку стандарта (требование к разделу). Кросс-проверки: 1 чанк кросс-проверки (отдельный раздел с кросс-проверками) ~ n чанкам (разделам) документации. 

  4. Отправляем в 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 на этом этапе:

  1. Объединяет все результаты проверок (по разделам и кросс-проверкам).

  2. Формирует таблицы по каждому разделу с указанием всех критериев.

  3. Отмечает статус каждого пункта: выполнен/не выполнен/частично выполнен.

  4. Добавляет детали ошибок в скобках рядом со статусом — чтобы пользователь ничего не упустил.

  5. В финале — генерирует краткий вывод и общую оценку*.

*Оценка является субъективной и основывается на “мнении” 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 раза?», Дезмонд не увидит в этом ошибки. Во всяком случае, пока что :)

    Пример №2: «Проверка наличия фичи»

    • Аналитик №1 писал: «Не поняла, где описан функционал. Куда смотреть?»

    • Аналитик №2 писал: «Не вижу описания в документации, добавьте описание доработки».

    • Агент пишет: «Информация о доработке не найдена в предоставленной документации».

    Теперь результат сравнения всегда единообразен. Просто теперь данный поиск автоматизирован.

    Что упростилось/ускорилось?

    • Проверка стала менее рутинной. Теперь не нужно вручную вычитывать корректность ссылок и формат — агент справляется с этим.

    • У аналитиков появилась «шпаргалка» в виде отчёта с результатами проверок. Это сокращает время на уточнение формулировок и требований.

    • Этап ревью ускорился. Аналитики продуктовых команд стали быстрее вносить правки сразу после прогонки документации через агента. Даже несмотря на то, что мы предупреждали — агент всё еще в разработке.

    • Нет необходимости ждать или проводить повторное ревью изменений, можно перезапустить агента и получить результат уже с учетом правок.

    А что по цифрам?

    В нашем процессе раскатки фич мы замеряем время каждого этапа. В случае с проверкой документации нас интересует время нахождения задачи в статусе «Docs review», т.е. продолжительность ревью документации. Сравнили медианное время ревью месяцем ранее и медианное время спустя месяц после внедрения инструмента.

    Получилось так: 1 час 55 минут уменьшились до 1 часа 19 минут. Что составляет 31,3%

    Хорошо это или нет — решать вам.

    Ограничения

    • Большие документы — пока упираемся в лимит токенов при проверке на соответствие стандарту.

    • Макеты — проверяется только наличие, не контент.

    • Нестандартные кейсы — если структура документации сильно отличается от шаблонной, автоматически проверить её корректность не получится.

    Что ещё осталось нерешённым?

    • Любимый лимит токенов. Пока что в проверке на соответствие стандарту мы на него натыкаемся. 

    • Обработка нескольких документов сразу в рамках одной поставки. И такое бывает :) Сейчас такие кейсы требуют ручной обработки.

    Чего хочется дальше?

    • Проверка документации до этапа ревью — в идеале прямо в процессе написания.

    • Уйти от огромного отчета к короткой выжимке, в которой описано только то, что нужно исправить.

    • Массовая проверка всей документации в пространстве, составление общего отчёта и трекинг качества по командам/проектам.

    • Автоматический аппрув документации в случае, когда проверки по документации были выполнены успешно.

    О чём было бы круто услышать мнение читателей

    Мы не специализируется на разработке агентов и не посвящаем этому все наше рабочее время. Данный агент - side проект группы энтузиастов в рамках нашего технического развития по одному из направлений :) 

    • Возможно, мы где-то «переизобрели велосипед».

    • Могли упустить best practices.

    • Или есть более простые подходы, о которых мы не знали.

    Если ты дочитал(а) до этого места — будем очень благодарны за обратную связь ?

    Благодарности

    Семен Корольков и Влад Курчин (@simple_analytics) — спасибо за идеи, внимательность и то, что помогали довести всё до ума. За обсуждения, уточнения и споры, без которых финальная версия точно была бы слабее. 

    Мише Буторину (@MoshkaBortmanXXL) — за код, терпение и то, что поддержал идею, когда она была ещё просто гипотезой. 

    Артёму Гринько — за поддержку нашего проекта и наставления на верный путь. 

    P.S. Полезные ссылки

Комментарии (9)