Привет, Хабр! Меня зовут Макунина Арина, я аналитик и инженер данных в Just AI. Наша команда аналитики обожает, чтобы рутина в данных была максимально предсказуемой. Если что-то ломается, то должно быть понятно что, где, почему и что делать дальше. Когда мы поработали с Data Quality в продакшене, выяснилось, что правила качества сами по себе есть базовый минимум, но время утекает в две черные дыры.
Первая— это эффект белого листа.Приехала новая таблица или витрина и нужно понять, какие проверки на неё вообще разумно повесить. Мы профилируем данные, смотрим примеры строк, строим гипотезы, пишем черновые тесты, встраиваем в пайплайн, и весь этот процесс повторяется от источника к источнику.
Вторая — это проблема интерпретации инцидента.Проверки у нас уже есть, как вдруг внезапно одна падает, прилетает алерт и начинается расследование, которое почти всегда одинаковое: это массовая проблема или одноразовый выброс? Сломался источник данных или что-то прошло не так в процессе трансформации? Проблема появилась сегодня или деградировало всю рабочую неделю? Кто вообще владелец этих данных? Расследование первопричины проблемы каждый раз съедает достаточно много времени.
На фоне того, как индустрия двигается к тенденции фиксации ожиданий (data contracts) и видения поведения данных во времени (data observability), возникает естественный вопрос:можно ли использовать LLM там, где команда тратит часы на повторяющиеся задачи качества данных?
Мы попробовали пойти в эту сторону, но с важной оговоркой: LLM у нас не умный аналитик с доступом ко всему, а строго ограниченный помощник. Он не ходит в продовую базу, не принимает решений и не меняет пайплайны. Его задача — генерировать проверяемые артефакты, а именно стартовые DQ-правила для новых структур, и объяснять уже случившиеся инциденты.
В статье покажу, как устроен этот контур, где он действительно помогает, где ошибается и почему без жёстких ограничений такой подход быстро становится опасным.
Архитектура решения
Концептуально архитектура состоит из трех логических уровней, каждый из которых решает свою задачу.
Первый уровень — платформа данных.Это существующая инфраструктура, где живут сами данные и связанные с ними метаданные: DDL-схемы, история запусков проверок, агрегаты профилирования и примеры данных. Именно отсюда формируется контекст, который используется для генерации правил качества и анализа инцидентов.
Второй уровень — прикладной слой качества данных.Здесь находятся сервисы, которые понимают предметную область качества данных: генерация правил, интерпретация инцидентов, работа с результатами проверок и хранение артефактов. Именно они формируют структурированный контекст, который может быть передан модели.
Третий уровень — платформенный слой LLM.Это единый шлюз для работы с моделями и AI-сервисами. В нашем случае эту роль выполняетCaila.Платформа отвечает за управление моделями, маршрутизацию запросов, аудит вызовов, контроль доступа и защиту данных.
Однако даже при такой архитектуре напрямую соединять прикладной слой и LLM было бы рискованно. Поэтому между уровнями мы ввели набор правил взаимодействия, которые определяют, какой контекст может видеть LLM, какие результаты она может генерировать и в каком формате эти результаты должны возвращаться.
Правила использования LLM при работе с данными
Правило первое:не упоминать оLLM не имеет прямого доступа к продовой базе. Никаких просьб выполнить запрос из модели. Модель видит только заранее подготовленный контекст, а именно DDL/метаданные, агрегаты/профили, примеры данных (но уже замаскированные), историю запусков. Это снижает класс рисков: от утечек до случайных нежелательных рекомендаций.
Правило второе:не упоминать нигде оLLM пишет артефакты, а не выносит приговор. Артефакты в данном случае это правила в нашей библиотеке проверки качества данных или проверки, которые потом детерминировано исполняются человеком и дают измеримый результат. Это паттерн «tests as code», где тест возвращает строки‑нарушители, а если строк нет, то тест прошел.
Правило третье:любой автогенерируемый результат должен быть проверяемым. Поэтому мы требуем строго структурированный формат ответа, например JSON с валидируемой схемой.
Архитектура получилась намеренно скучной, и в этом ее плюс. Мы сознательно жертвуем магией, чтобы сохранить контроль, воспроизводимость и проверяемость. LLM не знает бизнес-правил вашей системы. Она не понимает, что какой-то идентификатор может дублироваться до пяти раз просто потому, что так устроен процесс. Значит, ее нужно ставить в рамки.
Как выглядят правила качества у нас
У нас проверки данных работают по одному и тому же шаблону:
- Выбираем данные;
- Применяем правило;
- Собираем нарушения;
- Возвращаем результат в едином формате.
Нам было важно не просто писать отдельные проверки под каждую таблицу, а иметь единый, воспроизводимый и расширяемый механизм. Поэтому мы приняли решение сделать собственную Python-библиотеку для проверок качества данных. В ней мы зафиксировали свой формат описания правил, свой формат результата и единый способ исполнения проверок.
Внутри библиотеки у нас есть словарь стандартных проверок, который мы переиспользуем между разными проектами и датасетами. Идея здесь примерно такая же, как в популярных библиотеках для проверки качества данных, но у нас реализован собственный формат описания правил и свой кастомный формат возврата результатов.
Контур AI‑помощника
Часть первая: генерация стартовых проверок для новой таблицы
Когда появляется новая таблица, мы делаем три вещи и все они обычно доступны даже без глубокого знания домена. Вытаскиваем DDL/метаданные, что дает LLM понимание структуры данных, делаем профиль данных и агрегаты, что существенно улучшает качество предложений, а также даем LLM наш словарь проверок, т.е. перечень доступных типов правил и их параметры. С этим джентльменским набором уверенно просим LLM сгенерировать комплект стартовых правил для конкретной таблицы. Чтобы это работало стабильно и даже автоматизировано, мы используемжесткий формат вывода.
Пример промпта:
Смысл “needs_domain_info” простой. Мы не пытаемся заставить LLM угадывать бизнес-семантику, например, чтоNULLвdate_closedдопустим, потому что заявка в процессе. Она дает разумный дефолт и список вопросов, которые реально нужно задать владельцу.
Часть вторая: интерпретация инцидента
Когда правило нарушено, мы пытаемся превратить уведомление об ошибке в actionable alert. Мы собираем контекст ровно из тех фактов, которые у нас уже есть:
- что за правило;
- результаты выполнения;
- примеры проблемных строк;
- запросы для быстрого просмотра проблемных записей;
- история изменений.
LLM таким образом объясняет проблему человеческим языком, оценивает масштаб, формирует гипотезы с пометкой уверенности, предлагает конкретный чек‑лист следующих шагов.
Удобно воспользоваться сгенерированным запросом под конкретную таблицу и вывести все проблемные записи. Результаты проверок храним так, чтобы можно было построить тренды, взглянуть на пару примеров проблемных строк, сопоставить падение с последними изменениями и владельцами.
Промпт‑шаблон:
Ключевые ограничения, которые мы держим в голове, это то, что LLM может написать красиво и убедительно, но этоне значит, что она права.
Как это выглядит в жизни
Подключаем новую таблицу
- Инженер добавляет таблицу в реестр DQ (таблица, владелец, критичность).
- Система самостоятельно собирает DDL и строит профиль колонок (агрегаты + примеры).
- LLM генерирует набор правил.
- Мы рассматриваем предложения и правим спорные места.
- Правила уходят в репозиторий.
Результат получился в режиме советчика в комментариях, но именно так и было задумано, о чем я писала выше.
Однако в выводе можно заметить и косяки LLM. В одном местеcondition="=",в другомcondition="==", потомcondition="all",condition="not_null"иcondition="between".По самому выводу видно, что модель, скорее всего, начала додумывать допустимые значения, а не выбирать их из жесткого словаря.
Последнее замечание по структуре — модель не использует шаблоны, хотя они здесь явно подходят. У нас уже естьid_columnиdate_column,но для id иtransaction_time_*LLM не сослалась на template, который предоставляет интерфейс библиотеки, а развернула рекомендации вручную.
Логично выглядят проверки для 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. Поведение LLM в таких задачах сильно зависит от конкретной модели, и те огрехи, которые видны в YAML-выводе, стоит интерпретировать именно в контексте выбранной модели и заданного промпта, а не как свойство подхода в целом.
Инженерная ценность здесь в том, что мы превращаем исследование новой таблицы в стандартизированный процесс и тем самым решаем проблему чистого листа.
Упало правило
- Алерт формируется с приоритетом и базовой статистикой.
- LLM‑интерпретатор получает контекст и пишет объяснение:
- что именно сломалось,
- насколько массово,
- что могло изменить распределение,
- какие запросы сделать дальше,
- кому пинговать.
Пример уведомления:
Пример с имейлом выше — игрушечный и приведён только для наглядности читателя. В реальном контуре, особенно при работе с облачными LLM, к таким данным уженельзяотноситься как к безобидному тексту. Если в контекст попадают персональные данные, это становится задачей не только удобства, но и защиты данных. Иными словами, ответственность за то, какие данные ушли в модель и какие решения были приняты по её ответу, остаётся на человеке и на организации, а не на LLM.
Если при чтении кажется, что модель местами повторяется, это нормально. Здесь сама проблема очень простая и наглядная, поэтому ключевые мысли неизбежно дублируются. Кроме того, LLM у нас работает по жёсткой инструкции и ей нужно заполнить все блоки ответа, даже если часть содержания между ними пересекается. Для такого инженерного сценария это скорее плюс, чем минус, так как формат получается чуть менее изящным, зато более стабильным, проверяемым и удобным для дальнейшей работы.
На практике такой разбор сильно сокращает время до первой полезной гипотезы.
Как не утонуть в стоимости
На практике деньги улетают на генерацию алертов, зачастую даже одинаковых. Поэтому у нас три техники:
- кэшируем объяснения по набору (таблица, правило, сигнатура инцидента);
- используем маршрутизацию моделей, где дешёвые модельки направляем на типовые тексты, а сильные на расследования, где нужен анализ контекста;
- обязательно ограничиваем контекст, чтобы модель получала профиль и небольшой сэмпл данных, а не большие выгрузки.
Безопасность, контроль и мониторинг
Мы заранее приняли истину, что LLM потенциально небезопасный компонент и надо выстроить архитектуру защиты данных.
Первый слой— маскирование данных на пути в LLM. У нас это решается шлюзом защиты данных, который анализирует поля запроса и маскирует чувствительные сущности, с возможностью восстановить их в ответе.
Второй слой— управление ключами, правами и лимитами. На уровне платформы мы можем выдавать ключи с ограничениями по правам доступа, лимитами, временем жизни. Это важно, чтобы AI‑помощник был таким же субъектом доступа, как сервисный аккаунт.
Третий слой— защита от вредных советов. Проблема prompt injection возникает, когда в контекст может попасть текст вида «игнорируй инструкции». Поэтому мы разделяем доверительный контекст (DDL, метрики, результаты проверок, то есть то, что генерирует система) и недоверительный контекст (описания, текстовые поля, комментарии) и жестко фиксируем разграничение в инструкциях.
Что получилось в итоге
В цифрах эффект оказался вполне приземленным. Если смотреть на оба сценария вместе, то экономия времени больше 80%, относительно ручного расследования и заполнения конфигураций Data Quality.
На текущий момент у нас получилось главное: AI закрывает две самые дорогие части процесса обеспечения качества данных, а именно старт и разбор. Процесс становится спокойнее и предсказуемее.