- BrainTools - https://www.braintools.ru -
Меня зовут Макунина Арина, я аналитик и инженер данных в Just AI. Наша команда аналитики обожает, чтобы рутина в данных была максимально предсказуемой. Если что-то ломается, то должно быть понятно что, где, почему и что делать дальше. Когда мы поработали с Data Quality в продакшене, выяснилось, что правила качества сами по себе есть базовый минимум, но время утекает в две черные дыры.
Первая — это эффект белого листа. Приехала новая таблица или витрина и нужно понять, какие проверки на неё вообще разумно повесить. Мы профилируем данные, смотрим примеры строк, строим гипотезы, пишем черновые тесты, встраиваем в пайплайн, и весь этот процесс повторяется от источника к источнику.
Вторая — это проблема интерпретации инцидента. Проверки у нас уже есть, как вдруг внезапно одна падает, прилетает алерт и начинается расследование, которое почти всегда одинаковое: это массовая проблема или одноразовый выброс? Сломался источник данных или что-то прошло не так в процессе трансформации? Проблема появилась сегодня или деградировало всю рабочую неделю? Кто вообще владелец этих данных? Расследование первопричины проблемы каждый раз съедает достаточно много времени.
На фоне того, как индустрия двигается к тенденции фиксации ожиданий (data contracts) и видения поведения [1] данных во времени (data observability), возникает естественный вопрос: можно ли использовать LLM там, где команда тратит часы на повторяющиеся задачи качества данных?
Мы попробовали пойти в эту сторону, но с важной оговоркой: LLM у нас не умный аналитик с доступом ко всему, а строго ограниченный помощник. Он не ходит в продовую базу, не принимает решений и не меняет пайплайны. Его задача — генерировать проверяемые артефакты, а именно стартовые DQ-правила для новых структур, и объяснять уже случившиеся инциденты.
В статье покажу, как устроен этот контур, где он действительно помогает, где ошибается и почему без жёстких ограничений такой подход быстро становится опасным.
Концептуально архитектура состоит из трех логических уровней, каждый из которых решает свою задачу.
Первый уровень — платформа данных. Это существующая инфраструктура, где живут сами данные и связанные с ними метаданные: DDL-схемы, история запусков проверок, агрегаты профилирования и примеры данных. Именно отсюда формируется контекст, который используется для генерации правил качества и анализа инцидентов.
Второй уровень — прикладной слой качества данных. Здесь находятся сервисы, которые понимают предметную область качества данных: генерация правил, интерпретация инцидентов, работа с результатами проверок и хранение артефактов. Именно они формируют структурированный контекст, который может быть передан модели.
Третий уровень — платформенный слой LLM. Это единый шлюз для работы с моделями и AI-сервисами. В нашем случае эту роль выполняет Caila. Платформа отвечает за управление моделями, маршрутизацию запросов, аудит вызовов, контроль доступа и защиту данных.
Однако даже при такой архитектуре напрямую соединять прикладной слой и LLM было бы рискованно. Поэтому между уровнями мы ввели набор правил взаимодействия, которые определяют, какой контекст может видеть LLM, какие результаты она может генерировать и в каком формате эти результаты должны возвращаться.
Правило первое: не упоминать о LLM не имеет прямого доступа к продовой базе. Никаких просьб выполнить запрос из модели. Модель видит только заранее подготовленный контекст, а именно DDL/метаданные, агрегаты/профили, примеры данных (но уже замаскированные), историю запусков. Это снижает класс рисков: от утечек до случайных нежелательных рекомендаций.
Правило второе: не упоминать нигде о LLM пишет артефакты, а не выносит приговор. Артефакты в данном случае это правила в нашей библиотеке проверки качества данных или проверки, которые потом детерминировано исполняются человеком и дают измеримый результат. Это паттерн «tests as code», где тест возвращает строки‑нарушители, а если строк нет, то тест прошел.
Правило третье: любой автогенерируемый результат должен быть проверяемым. Поэтому мы требуем строго структурированный формат ответа, например JSON с валидируемой схемой.

Архитектура получилась намеренно скучной, и в этом ее плюс. Мы сознательно жертвуем магией, чтобы сохранить контроль, воспроизводимость и проверяемость. LLM не знает бизнес-правил вашей системы. Она не понимает, что какой-то идентификатор может дублироваться до пяти раз просто потому, что так устроен процесс. Значит, ее нужно ставить в рамки.
У нас проверки данных работают по одному и тому же шаблону:
Выбираем данные;
Применяем правило;
Собираем нарушения;
Возвращаем результат в едином формате.
Нам было важно не просто писать отдельные проверки под каждую таблицу, а иметь единый, воспроизводимый и расширяемый механизм. Поэтому мы приняли решение сделать собственную Python-библиотеку для проверок качества данных. В ней мы зафиксировали свой формат описания правил, свой формат результата и единый способ исполнения проверок.
Внутри библиотеки у нас есть словарь стандартных проверок, который мы переиспользуем между разными проектами и датасетами. Идея здесь примерно такая же, как в популярных библиотеках для проверки качества данных, но у нас реализован собственный формат описания правил и свой кастомный формат возврата результатов.
Часть первая: генерация стартовых проверок для новой таблицы
Когда появляется новая таблица, мы делаем три вещи и все они обычно доступны даже без глубокого знания домена. Вытаскиваем DDL/метаданные, что дает LLM понимание структуры данных, делаем профиль данных и агрегаты, что существенно улучшает качество предложений, а также даем LLM наш словарь проверок, т.е. перечень доступных типов правил и их параметры. С этим джентльменским набором уверенно просим LLM сгенерировать комплект стартовых правил для конкретной таблицы. Чтобы это работало стабильно и даже автоматизировано, мы используем жесткий формат вывода.
Пример промпта:
Роль: ведущий инженер по качеству данных.
Задача:
На основе структуры таблицы, профиля колонок и словаря доступных правил
сгенерируй стартовый набор проверок качества данных.
Работай по правилам:
1. Используй только типы проверок из переданного словаря.
2. Не придумывай новые колонки, поля и бизнес-ограничения.
3. Если для правила не хватает доменной информации, не угадывай — пометь его как needs_domain_info.
4. Для каждого правила укажи, на каких фактах оно основано:
- имя колонки
- тип данных
- null_ratio
- distinct_ratio
- top_values
- min/max
- примеры значений
5. Предлагай только те правила, которые выглядят разумными как стартовый baseline.
6. Не предлагай взаимоисключающие или явно избыточные проверки.
Формат ответа:
Верни только JSON-массив объектов следующего вида:
{
"column": "string",
"rule_type": "string",
"params": {},
"status": "auto_ok | needs_review | needs_domain_info",
"reason": "string",
"evidence": ["string"],
"questions_for_owner": ["string"]
}
Критерии качества ответа:
- JSON должен быть валидным.
- Все rule_type должны быть из разрешенного списка.
- Если уверенность низкая, это должно быть явно отражено в status и questions_for_owner.
- Не добавляй текст вне JSON.
Контекст:
[DDL и метаданные таблицы]
[Профиль данных]
[Словарь доступных правил и их параметров]
Смысл “needs_domain_info” простой. Мы не пытаемся заставить LLM угадывать бизнес-семантику, например, что NULL в date_closed допустим, потому что заявка в процессе. Она дает разумный дефолт и список вопросов, которые реально нужно задать владельцу.
Часть вторая: интерпретация инцидента
Когда правило нарушено, мы пытаемся превратить уведомление об ошибке [2] в actionable alert. Мы собираем контекст ровно из тех фактов, которые у нас уже есть:
что за правило;
результаты выполнения;
примеры проблемных строк;
запросы для быстрого просмотра проблемных записей;
история изменений.
LLM таким образом объясняет проблему человеческим языком, оценивает масштаб, формирует гипотезы с пометкой уверенности, предлагает конкретный чек‑лист следующих шагов.
Удобно воспользоваться сгенерированным запросом под конкретную таблицу и вывести все проблемные записи. Результаты проверок храним так, чтобы можно было построить тренды, взглянуть на пару примеров проблемных строк, сопоставить падение с последними изменениями и владельцами.
Промпт‑шаблон:
Роль: ведущий инженер по качеству данных.
Задача:
Объясни нарушение правила качества данных, опираясь только на факты из контекста.
Критически важно:
- Не выдавай гипотезу за установленную причину.
- Если причина не доказана, пометь её как hypothesis.
- Не придумывай изменения в данных, коде или пайплайне, если их нет в контексте.
- Все предлагаемые действия должны быть проверяемыми.
Верни результат в структурированном markdown со следующими разделами:
1. Summary
Кратко опиши, что именно сломалось и насколько это серьёзно.
2. Facts
Только подтверждённые факты из входных данных:
- правило
- таблица/колонка
- expected / actual
- масштаб
- примеры проблемных строк
- динамика было/стало, если она есть
3. Impact
На что это может повлиять технически и бизнесово.
4. Top hypotheses
До 3 гипотез.
Для каждой гипотезы укажи:
- hypothesis
- confidence: high | medium | low
- why
- how_to_verify
5. Next steps
До 7 конкретных запросов к базе данных на чтение данных, которые помогут локализовать проблему.
Ключевые ограничения, которые мы держим в голове, это то, что LLM может написать красиво и убедительно, но это не значит, что она права.
Подключаем новую таблицу
Инженер добавляет таблицу в реестр DQ (таблица, владелец, критичность).
Система самостоятельно собирает DDL и строит профиль колонок (агрегаты + примеры).
LLM генерирует набор правил.
Мы рассматриваем предложения и правим спорные места.
Правила уходят в репозиторий.
templates:
id_column:
- unique
- not_null
email_column:
- not_null
- format_check: email
date_column:
- not_null
- date_range: min_date=2020-01-01, max_date=today()
tables:
- name: *****_intermediate.account
description: Таблица '*****_intermediate.account' из PostgreSQL
columns:
# recommended_tests:
# - not_null: condition="=" # Проверяет отсутствие NULL/NaN в столбце.
# - unique: condition="=" # Проверяет, что значения в столбце уникальны.
# - data_type: condition="=", fmt="numeric" # Проверяет тип данных столбца.
# - range_check: min_value=1, max_value=999999999, condition=">=" # Проверяет числовые значения на попадание в диапазон.
- name: id
description: Колонка 'id' таблицы '*****_intermediate.account'
tests: []
# recommended_tests:
# - data_type: condition="all", fmt="numeric" # Проверяет тип данных столбца.
# - range_check: min_value=46480545.0, max_value=1000172858.0, condition="not_null" # Проверяет числовые значения на попадание в диапазон.
# - unique: condition="not_null" # Проверяет, что значения в столбце уникальны.
- name: cc_account_id
description: Колонка 'cc_account_id' таблицы '*****_intermediate.account'
tests: []
# recommended_tests:
# - not_null: condition="==" # Проверяет отсутствие NULL/NaN в столбце.
# - data_type: condition="==", fmt="datetime" # Проверяет тип данных столбца.
# - date_range: min_date="2020-01-01", max_date="2030-12-31", date_format="%Y-%m-%d %H:%M:%S", condition="between" # Проверяет, что даты в столбце в заданном диапазоне.
- name: transaction_time_utc
description: Колонка 'transaction_time_utc' таблицы '*****_intermediate.account'
tests: []
# recommended_tests:
# - not_null: condition="all" # Проверяет отсутствие NULL/NaN в столбце.
# - data_type: condition="all", fmt="datetime" # Проверяет тип данных столбца.
- name: transaction_time_msk
description: Колонка 'transaction_time_msk' таблицы '*****_intermediate.account'
tests: []
Результат получился в режиме советчика в комментариях, но именно так и было задумано, о чем я писала выше.
Однако в выводе можно заметить и косяки LLM. В одном месте condition="=", в другом condition="==", потом condition="all", condition="not_null" и condition="between". По самому выводу видно, что модель, скорее всего, начала додумывать допустимые значения, а не выбирать их из жесткого словаря.
Последнее замечание по структуре — модель не использует шаблоны, хотя они здесь явно подходят. У нас уже есть id_column и date_column, но для id и transaction_time_* LLM не сослалась на template, который предоставляет интерфейс библиотеки, а развернула рекомендации вручную.
Логично [3] выглядят проверки для id, transaction_time_utc и transaction_time_msk. Это нормальный стартовый baseline. А вот range_check для id и cc_account_id обычно плохая идея, потому что текущие min/max это не бизнес-ограничение, а просто снимок сегодняшнего состояния. Через месяц диапазон вырастет и мы получим ложные алерты.
unique для cc_account_id может быть правильным, а может быть совсем неправильным – это как раз зависит от предметной области. Если это внешний account id и он должен быть 1 к 1, тогда все хорошо. Если это ссылка на аккаунт, к которой может относиться несколько строк, unique будет ложным правилом. То есть это хороший пример теста со статусом needs_domain_info, а не auto_ok.
date_range до 2030-12-31 тоже больше похоже на догадку модели, чем на правило системы. Это выглядит как признак отсутствия жесткого контракта генерации.
Отдельно видно, что модель пропустила связь между transaction_time_utc и transaction_time_msk. Вот здесь LLM как раз недодумала. Если у нас есть два поля: UTC и MSK-время одной и той же транзакции, напрашивается проверка согласованности между ними.
Для этого примера мы использовали модель Claude Sonnet 4. Поведение [4] LLM в таких задачах сильно зависит от конкретной модели, и те огрехи, которые видны в YAML-выводе, стоит интерпретировать именно в контексте выбранной модели и заданного промпта, а не как свойство подхода в целом.
Инженерная ценность здесь в том, что мы превращаем исследование новой таблицы в стандартизированный процесс и тем самым решаем проблему чистого листа.
Упало правило
Алерт формируется с приоритетом и базовой статистикой.
LLM‑интерпретатор получает контекст и пишет объяснение:
что именно сломалось,
насколько массово,
что могло изменить распределение,
какие запросы сделать дальше,
кому пинговать.
Пример уведомления:
⚠️ Обнаружена ошибка качества данных
🚩 Проверка format_check не прошла в таблице *****_intermediate.registration_utm, поле email.
Ожидалось: значение должно соответствовать принятому в системе правилу валидации email
Получено: ['bblkovo@gmail', 'sdsdsdsdsd@1212', 'proskurin231@45', 'solowwomon@gmail', 'belluchi99@bk']
Детали: 28 значений не прошли проверку формата (0.01% от 340785 строк)
Кратко
В таблице *****_intermediate.registration_utm обнаружены значения в колонке email, которые не соответствуют используемому у нас правилу валидации email. Нарушение точечное: 28 записей из 340785 (0.01%).
Факты
Таблица: *****_intermediate.registration_utm (PostgreSQL)
Колонка: email
Количество нарушений: 28 из 340785 строк (0.01%)
Примеры значений: bblkovo@gmail, sdsdsdsdsd@1212, proskurin231@45, solowwomon@gmail, belluchi99@bk
Тип нарушения: значение не прошло текущее regex-правило проверки email
Влияние
Масштаб нарушения низкий, проблема выглядит точечной, а не массовой
Некорректные значения в email могут приводить к ошибкам в уведомлениях, коммуникациях и downstream-процессах
Возможны искажения в сегментации, отчётности и аналитике по email-каналу
Если такие записи продолжают накапливаться, точечная проблема может перерасти в системную
Топ гипотез
На части сценариев записи email недостаточно строгая прикладная валидация
Уверенность: medium
Почему: среди примеров есть значения, похожие на типичные ошибки ручного ввода или неполные домены
Как проверить:
сверить текущую frontend- и backend-валидацию email;
проверить, все ли точки входа используют одинаковые правила.
Часть записей пришла из внешнего источника или batch-загрузки в обход основной валидации
Уверенность: medium
Почему: единичные аномалии такого типа часто приходят не из основного пользовательского сценария, а из импортов или технических интеграций
Как проверить:
сопоставить время появления некорректных записей с загрузками и ETL-джобами;
проверить source system / ingestion path для этих строк.
Текущее regex-правило строже, чем реально допустимый формат в вашем продукте
Уверенность: low
Почему: часть “некорректных” адресов может нарушать именно ваш проектный паттерн, а не общий синтаксис email
Как проверить:
зафиксировать целевое бизнес-правило валидности email;
сравнить его с текущей regex-проверкой и правилами в приложении.
Следующие шаги
Посмотреть все 28 записей и сгруппировать их по типу ошибки
Проверить, из каких каналов и в какой период они появились
Сравнить правила валидации на frontend, backend и в DQ-проверке
Проверить, не проходят ли такие значения через импортные пайплайны
После локализации причины решить, нужна ли корректировка валидации, очистка данных или исключение для отдельных сценариев
Рекомендации
📌 Сначала синхронизировать единое правило валидности email между приложением, ETL и DQ
📌 Не вводить жёсткие ограничения в БД, пока не подтверждено целевое бизнес-правило
📌 Добавить разметку источника записи, чтобы быстрее разделять ошибки ввода и импортные артефакты
📌 Оставить мониторинг на этот кейс и следить, растёт ли количество нарушений во времени
Диагностические запросы
SELECT email, COUNT(*) AS cnt
FROM *****_intermediate.registration_utm
WHERE email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$'
GROUP BY email
ORDER BY cnt DESC
LIMIT 50;
SELECT LENGTH(email) AS email_length, COUNT(*) AS cnt
FROM *****_intermediate.registration_utm
WHERE email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$'
GROUP BY LENGTH(email)
ORDER BY email_length;
⚠️ Анализ от LLM может быть неточным. Перед изменениями в пайплайнах проверьте гипотезы.
Пример с имейлом выше — игрушечный и приведён только для наглядности читателя. В реальном контуре, особенно при работе с облачными LLM, к таким данным уже нельзя относиться как к безобидному тексту. Если в контекст попадают персональные данные, это становится задачей не только удобства, но и защиты данных. Иными словами, ответственность за то, какие данные ушли в модель и какие решения были приняты по её ответу, остаётся на человеке и на организации, а не на LLM.
Если при чтении кажется, что модель местами повторяется, это нормально. Здесь сама проблема очень простая и наглядная, поэтому ключевые мысли неизбежно дублируются. Кроме того, LLM у нас работает по жёсткой инструкции и ей нужно заполнить все блоки ответа, даже если часть содержания между ними пересекается. Для такого инженерного сценария это скорее плюс, чем минус, так как формат получается чуть менее изящным, зато более стабильным, проверяемым и удобным для дальнейшей работы.
На практике такой разбор сильно сокращает время до первой полезной гипотезы.
Как не утонуть в стоимости
На практике деньги улетают на генерацию алертов, зачастую даже одинаковых. Поэтому у нас три техники:
кэшируем объяснения по набору (таблица, правило, сигнатура инцидента);
используем маршрутизацию моделей, где дешёвые модельки направляем на типовые тексты, а сильные на расследования, где нужен анализ контекста;
обязательно ограничиваем контекст, чтобы модель получала профиль и небольшой сэмпл данных, а не большие выгрузки.
Безопасность, контроль и мониторинг
Мы заранее приняли истину, что LLM потенциально небезопасный компонент и надо выстроить архитектуру защиты данных.
Первый слой — маскирование данных на пути в LLM. У нас это решается шлюзом защиты данных, который анализирует поля запроса и маскирует чувствительные сущности, с возможностью восстановить их в ответе.
Второй слой — управление ключами, правами и лимитами. На уровне платформы мы можем выдавать ключи с ограничениями по правам доступа, лимитами, временем жизни. Это важно, чтобы AI‑помощник был таким же субъектом доступа, как сервисный аккаунт.
Третий слой — защита от вредных советов. Проблема prompt injection возникает, когда в контекст может попасть текст вида «игнорируй инструкции». Поэтому мы разделяем доверительный контекст (DDL, метрики, результаты проверок, то есть то, что генерирует система) и недоверительный контекст (описания, текстовые поля, комментарии) и жестко фиксируем разграничение в инструкциях.
Что получилось в итоге
В цифрах эффект оказался вполне приземленным. Если смотреть на оба сценария вместе, то экономия времени больше 80%, относительно ручного расследования и заполнения конфигураций Data Quality.
На текущий момент у нас получилось главное: AI закрывает две самые дорогие части процесса обеспечения качества данных, а именно старт и разбор. Процесс становится спокойнее и предсказуемее.
Автор: just_ai
Источник [5]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/29195
URLs in this post:
[1] поведения: http://www.braintools.ru/article/9372
[2] ошибке: http://www.braintools.ru/article/4192
[3] Логично: http://www.braintools.ru/article/7640
[4] Поведение: http://www.braintools.ru/article/5593
[5] Источник: https://habr.com/ru/companies/just_ai/articles/1011428/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1011428
Нажмите здесь для печати.