LLM пайплайны укрощают сложность баз данных, или как мы подружили ИИ с БД без ИБД

Большие языковые модели (Large Language Model, LLM) используют в разных областях: с их помощью генерируют программный код, ищут информацию, озвучивают реплики чат-ботов. А вот при работе с реляционными данными языковые модели часто ошибаются.

d15e36afd408f28d8dcd82b03d985f18.jpg

Чтобы справиться с этими ошибками, в мы разработали три пайплайна для работы с базами данных. Эти пайплайны представляют собой цепочку связанных между собой языковых моделей: каждая из них генерирует свой ответ, и следующая модель работает с ответом предыдущей. Таким образом мы получаем дополнительный контекст, и запрос к базе данных становится точнее. 

Решаем проблему генерации SQL-запросов 

Задача. У нас есть вопрос пользователя и описание схемы базы данных (DDL). На основании вопроса мы хотим сгенерировать запрос, чтобы узнать некую информацию из базы данных. Например, получить список клиентов компании, которые находятся в определенном городе. 

Языковые модели хорошо работают с декларативными языками. Поэтому кажется, что чтобы сгенерировать SQL-запрос, нужно просто написать промпт. Но на практике выясняется, что результаты такой генерации приводят к ошибкам: логическим, синтаксическим, ошибкам исполнения и другим. 

Продвижение > Пайплайны LLM для работы с данными в БД > unnamed (2).png» /><p>Чтобы решить эти проблемы, мы разработали пайплайн и добавили к языковой модели дополнительные элементы, которые уменьшают количество возможных ошибок. Вот как он работает.</p><ol><li><p><strong>История чата</strong> с пользователем дает языковой модели дополнительный контекст. </p></li><li><p>Промпт трансформируется в формат <strong>chain of thought</strong>, или цепочка мыслей. Это техника, при которой LLM поэтапно описывает свой путь действий перед тем, как начать генерацию. Так процесс принятия решений становится более прозрачным, и мы можем выявлять ошибки в рассуждениях модели.</p></li><li><p>Сформулированная цепочка мыслей поступает в <strong>LLM-генератор.</strong> На выходе он выдает SQL-запрос — он может быть как чистым, так и скрытым в цепочке мыслей. </p></li><li><p><strong>LLM-экстрактор</strong> извлекает из цепочки мыслей SQL-запрос и приводит его к виду JSON модели.</p></li><li><p>Получившийся запрос поступает в <strong>LLM-критик</strong> — он выявляет ошибки и оставляет комментарий. Этот комментарий переходит в шаблон изначального промпта, и затем измененный промпт снова поступает в модель-генератор.</p></li><li><p>В результате получается цикл. Цикл может выполняться достаточно долго, а если задача не имеет решения — бесконечно. Чтобы этого не происходило, нужно поставить <strong>ограничение на количество итераций</strong>. Тогда мы выйдем из пайплайна, даже если LLM-критика не устроит результат генерации. </p></li><li><p>Полученный запрос отправляется в <strong>SQL Executor</strong> — так мы проверяем, исполнится он или нет. </p></li><li><p>Если запрос не исполняется, значит, произошла ошибка. Эту ошибку в виде текста мы отправляем в шаблон <strong>fixerror prompt</strong> и формулируем новый, более точный промпт. </p></li></ol><p>Как показывает практика, такой пайплайн генерации SQL неплохо справляется. Многие ошибки получается отловить на этапе LLM-критика, а те, которые пробиваются через него, находит SQL Executor. Тем не менее, у этого пайплайна есть «точки роста» — возможности для дальнейшего совершенствования. </p><p>Одна из ключевых проблем возникает, когда мы работаем с действительно большими и сложными базами данных с тысячами таблиц. DDL такой базы может быть огромным. А у языковых моделей есть ограничение на объем входных данных — «окно контекста». Попытка «скормить» модели DDL для тысяч таблиц обречена на провал: информация просто не поместится в этот лимит. В результате модель либо не сможет обработать запрос, либо получит лишь часть схемы, что приведет к ошибкам при генерации SQL.</p><p>Как решить эту проблему? Перспективный подход — не передавать всю схему целиком. Вместо этого можно внедрить дополнительный компонент: специализированную модель-экстрактор. Задача этой модели — выступить в роли «умного фильтра». Получив вопрос пользователя, она анализирует его и всю доступную схему данных (те самые тысячи таблиц), но выбирает из них только те, которые нужны для ответа на вопрос пользователя. </p><h4>Для каких сценариев подходит пайплайн</h4><p>Большие языковые модели, генерирующие SQL, помогают преодолеть барьер между человеком и сложным языком запросов. Даже если пользователь не знает SQL, он может напрямую обратиться к данным на естественном языке. Модель выступит «переводчиком»: на основе вопроса пользователя и схемы БД (DDL) она сформулирует корректный SQL-запрос. Это ключевой шаг к демократизации доступа к информации, хранящейся в базах.</p><p>Другая важная задача, которую помогают решить LLM, — это создание качественных тестовых данных. Заполнение баз для тестов часто требует много времени, а простые генераторы случайных наборов данных дают нереалистичные результаты. LLM же могут генерировать INSERT-запросы, создавая осмысленные и правдоподобные данные (например, имена и адреса, соответствующие региону). Это значительно ускоряет процессы разработки и прототипирования, предоставляя разработчикам готовые и реалистичные данные для работы.</p><h4>Возможные проблемы с пайплайном</h4><p><strong>Проблема 1: Семантические ошибки — LLM не понимает логику бизнеса</strong></p><p>Наиболее коварный тип ошибок — семантические. LLM может сгенерировать синтаксически абсолютно правильный SQL, который, тем не менее, неверно отражает бизнес-логику. Происходит это потому, что модель по умолчанию не разбирается специфике вашего бизнеса.</p><p><strong>Пример.</strong> Пользователь просит рассчитать выручку за прошлый месяц. По определению выручка — это та сумма, которую бизнес получил от реализации своих товаров. LLM составляет запрос, который суммирует все продажи (SUM (sale_amount)), но результат получается неверным. Дело в том, что некоторые покупатели возвращали товар, и чтобы правильно рассчитать выручку, из суммы полученных за продажи денег нужно вычесть эти возвраты.</p><p><strong>Причина.</strong> У LLM нет доступа к определениям ключевых терминов, метрик и правил расчета, принятых в компании.</p><p><strong>Решение.</strong> Решить проблему можно при помощи подхода RAG (Retrieval-Augmented Generation — генерация, дополненная поиском). Тогда LLM сначала найдет в базе знаний релевантную информацию, а затем использует эти сведения для того, чтобы сформулировать ответ. В данном случае модель «изучит» бизнес-глоссарий,   «выяснит», что такое выручка и как ее считать, и только после этого сгенерирует SQL-запрос.</p><p><strong>Проблема 2. Неоптимальный запрос — LLM плохо знает SQL</strong></p><p><strong>Пример. </strong>Пользователь просит модель посчитать, сколько сотрудников устроилось в компанию за каждый год. LLM сначала обращается к таблице с персоналом отдела маркетинга, фильтрует сотрудников по датам трудоустройства, потом делает то же самое для других отделов и присоединяет с помощью JOIN. А оптимальное решение — сформулировать иерархический запрос (CTE), объединить всех сотрудников в одну временную таблицу и отсортировать ее по датам. </p><p><strong>Причина. </strong>Языковая модель не знает best practices работы с SQL. </p><p><strong>Решение.</strong> Эту проблему тоже можно решить с помощью RAG: «скормить» модели учебник по SQL, чтобы она сверялась с ним, когда генерирует ответ. </p><h3>Работаем с графовыми базами</h3><p>При работе с данными иногда фокус смещается с отдельных записей на сложные связи и отношения между ними. В таких сценариях возможностей традиционных реляционных баз данных, с которыми работают через SQL, может быть недостаточно. Для решения таких задач были разработаны графовые базы данных. Они представляют информацию в виде графа — интуитивно понятной структуры, состоящей из узлов (объектов) и ребер (связей между этими объектами).</p><p>Для работы с графовыми базами используются специализированные языки, например, Cypher. Синтаксис этого языка оптимизирован для описания и поиска паттернов связей между узлами. Cypher встречается реже, чем SQL — это связано именно с его специализацией на графовых моделях данных, которые применяются для определенного круга задач.</p><p>Где же графовый подход становится нашим «другом»? Классический пример — социальные сети, когда нужно анализировать связи между пользователями, чтобы увидеть, в какие кластеры они входят. Также с помощью графовой базы можно отслеживать цепочки банковских транзакций и распознавать мошеннические схемы. </p><p>Большие языковые модели могут генерировать запросы не только на SQL, но и на других языках, например, на Cypher. Однако на практике генерация корректного Cypher-кода с помощью LLM оказывается значительно сложнее, чем генерация SQL. Основная причина этого — меньшая популярность Cypher. Модели обучались на гораздо меньшем объеме текстов и кода, содержащих Cypher, и в результате их «знание» синтаксиса, специфических конструкций и распространенных паттернов Cypher существенно ограничено, что часто приводит к ошибкам.</p><p>Чтобы решить эту проблему, приходится дополнительно «учить» языковую модель писать на Cypher: давать ей подробные инструкции и примеры запросов.</p><p><strong>Задача. </strong>Проанализировать кодовую базу PostgreSQL. Она довольно большая: около двух миллионов строк чистого кода без учета комментариев. Обычному человеку сложно справиться с такой задачей без помощи средств автоматизации.</p><img src=© Habrahabr.ru