Генератор SQL на базе LLM — понятный продукт с понятной ценностью. Он может быть отдельной платформой или инструментом для агента, решающего более общую задачу. Генерировать код модели с попеременным успехом, к счастью, умеют.
И что же? Берем API с моделью помощнее, даем ей доступ к БД, задаем вопрос, смотрим на результат, и всё — полноценная замена аналитику? Конечно, нет, ведь аналитик делает гораздо больше, чем просто пишет и исполняет SQL.
Однако давайте остановимся на SQL и посмотрим, почему это тоже не так просто:
Во внутренний контур никто API от OpenAI или Anthropic не встроит и уж тем более не будет туда отправлять чувствительные данные.
Если нет возможности обратиться к внешним API, то надо разворачивать модель локально и желательно без GPU, а это дорого и инфраструктура пока не готова.
Открытые LLM нередко ошибаются, особенно когда контекста много и он плохо сформулирован или когда речь заходит об определенном диалекте SQL, например PostgreSQL 17.
Процесс сбора данных и обучения LLM под конкретный диалект дорогостоящий во всех смыслах.
На локальных моделях можно использовать грамматики для guided decoding, однако их разработка и отладка — задачи непростые.
По итогу проблемы есть. Посмотрим, как их можно решить.
Что позволяет модели хорошо генерировать SQL?
В нашей области практика — критерий истины, поэтому рассмотрим несколько существующих подходов, которые дают отличные результаты в бенчмарках.
CHASE-SQL
Авторы CHASE-SQL предположили, что использование различных техник генерации SQL по-отдельности нецелесообразно: каждая из них имеет уникальные особенности, преимущества и недостатки.
Если нам важна именно точность на бенчмарке, а не доступность или скорость модели, почему бы не воспользоваться всем имеющимся арсеналом?
То есть решаем задачу по отдельности в нескольких обособленных пайплайнах, а затем результат каждого подхода отправляем в модель-селектор, выбирающую финальный ответ.
В сущности, каждый пайплайн из себя представляет хитрый промптинг крупной модели, типа Gemini или Claude, и этап саморефлексии в случае синтаксической ошибки.
SQLFuse
Веха в развитии систем для решения задачи Text-To-SQL, совмещающая в себе лучшие практики промпт-инжиниринга, эффективные многомодульные пайплайны, красивые архитектурные решения, технологии fine-tuning'а LLM и многое другое.
Модель состоит из 4 частей:
Schema Mining — все про промптинг и создание контекста для модели. Здесь достаются таблицы, столбцы и констреинты типа primary key и foreign key.
Schema Linking — модуль, позволяющий выделить значимое подмножество столбцов и таблиц для генерации SQL. Выполняется тоже при помощи языковой модели, обученной конкретно этой задаче при помощи SFT (Supervised Fine-Tuning).
SQL Generation — основная модель, в которую направляется весь накопленный ранее контекст вместе с вопросом пользователя. Тоже обучается отдельно при помощи SFT на TRAIN-датасете из Spider-бенчмарка.
SQL Critic — стандартный прием, когда результат генератора валидируется еще одним этапом. Только на этот раз у модели есть примеры хороших и плохих практик, пары схожих вопросов и ответов и другая априорная информация.
SkyRL-SQL
Основан на RL-дообучении базовой модели так, чтобы она могла в несколько шагов решать поставленную задачу (Multi-Turn RL). По сути, этот подход наиболее близок тому, что будет описано в этой статье.
Соответственно, совокупность дообучения моделей, формирования контекста и выстраивание пайплайнов позволяют сделать генерацию SQL стабильным и перспективным мероприятием.
Бенчмаркинг
Как оценивать SQL-генераторы? Можно на глаз на известной задаче, но это несерьезно. Обычно прибегают к помощи хорошо сконструированных, проверенных комьюнити и временем бенчмарков. Для задачи text-to-SQL есть 2 общепринятых бенчмарка — Spider и BIRD. Посмотрим, как выглядит каждый из них.
Spider
Классический кросс-доменный text-to-SQL-бенчмарк проверяет способность модели обобщаться на новые схемы БД и генерировать сложные SQL (joins, nested, group by/having и так далее).
Главные метрики:
Exact Match (EM) — точное совпадение с эталонным SQL.
Execution Accuracy (EX) — совпадение результатов выполнения предсказанного и эталонного SQL.
Примеры из бенчмарка:
Вопрос |
Ответ |
How many heads of the departments are older than 56 ? |
SELECT count(*) FROM head WHERE age > 56 |
List the name, born state and age of the heads of departments ordered by age. |
SELECT name , born_state , age FROM head ORDER BY age |
List the creation year, name and budget of each department. |
SELECT creation , name , budget_in_billions FROM department |
What is the maximum and minimum budget of the departments? |
SELECT max(budget_in_billions) , min(budget_in_billions) FROM department |
What is the average number of employees of the departments whose rank is between 10 and 15? |
SELECT avg(num_employees) FROM department WHERE ranking BETWEEN 10 AND 15 |
BIRD
Большой бенчмарк для text-to-SQL, который ближе к реальному миру. Основной акцент сделан на корректном вычленении значений из таблиц.
Метрики:
EX/EM.
Soft F1 по результатам выполнения (снижает чувствительность к порядку столбцов и пропускам).
Пример из бенчмарка:
Вопрос |
Ответ |
What is the ratio of customers who pay in EUR against customers who pay in CZK? |
SELECT CAST(SUM(CASE WHEN |
In 2012, who had the least consumption in LAM? |
SELECT |
What was the average monthly consumption of customers in SME for the year 2013? |
SELECT AVG( |
What was the difference in gas consumption between CZK-paying customers and EUR-paying customers in 2012? |
SELECT SUM(CASE WHEN |
Which year recorded the most consumption of gas paid in CZK? |
SELECT SUBSTR( |
Как дообучать думающие модели?
Почему не работает классический SFT?
Под SFT (Supervised Fine-Tuning) обычно понимают обучение модели на размеченных парах (вход → желаемый выход) с минимизацией какого-либо функционала, например кросс-энтропии. То есть модель просто учится имитировать ответы.
Для задач, где требуется длинное рассуждение (SQL, код, математика), такой подход быстро упирается в два фундаментальных ограничения.
Во-первых, нет готовых reasoning-датасетов. Открытые наборы либо вообще не содержат CoT (chain-of-thought), либо включают короткие, шумные и несогласованные пояснения.
Во-вторых, нет соответствия конечной цели. SFT оптимизирует правдоподобие целевых токенов, тогда как практические метрики — это правильность исполнения (EX для SQL), предпочтения пользователей и все такое.
Получается, что модель лучше пишет, но не обязательно лучше решает.
Как доработать датасет под SFT? Генерация reasoning-части
На практике эту дыру пытаются закрыть reverse-майнингом CoT: берут сильную модель, дают ей исходную задачу с правильным ответом или возможностью проверить себя и просят восстановить правдоподобную цепочку рассуждений.
Дальше — многократная генерация вариантов, автоматическая проверка, выбор лучших траекторий, нормализация стиля и упаковка в формат (вход → reasoning + ответ). Получается датасет, на котором уже можно делать SFT так, чтобы ученик учился формату и логике рассуждения, а не только финальному выводу.
Этот путь работает, но у него есть три неприятные особенности:
Дорого и долго. Сильная модель стоит денег и времени; верификация тоже не бесплатна (поднятие окружений, исполнение запросов, перегенерации при неудаче).
Не всегда эффективно. Учитель иногда реконструирует красивую, но несвязную цепочку мыслей. В результате после фильтрации остается меньше качественных примеров, чем хотелось бы.
Трудно исключить утечку априорной информации из промпта. Чтобы reverse-майнинг вообще сработал, в промпт обычно добавляют подсказки: ответ или структуру решения. Все это априорные знания, которые затем оказываются в данных для SFT, что в целом делает SFT бессмысленной затеей.
Что такое Reinforcement Learning?
Обучение с подкреплением (RL) — это идеология, в которой модель рассматривается как политика, выбирающая действия так, чтобы максимизировать ожидаемое вознаграждение.
Есть три основные сущности: состояние (контекст для модели), действие (следующий токен или целая последовательность) и награда. В классическом виде агент взаимодействует со средой, пробует варианты, получает обратную связь и обновляет политику, чтобы повысить суммарную награду.
В контексте LLM наградой могут быть результаты исполнения (прошел ли тест, правильно ли выполняется SQL), предпочтения (какой ответ пользователи считают лучшим). Ключевое идеологическое отличие от SFT в том, что RL оптимизирует именно целевую метрику, а не правдоподобие ответов.
Если нам важна EX, мы обучаемся так, чтобы ее повысить; если важны пользовательские предпочтения, награда формулируется из суррогатной модели предпочтений и направляет обновления политики.
GRPO
GRPO (Group Relative Policy Optimization) — это вариант PPO для RL-дообучения, где обучение ведется по группам из нескольких сэмплов на один и тот же запрос.
Здесь важно то, что оптимизируется политика не по абсолютным наградам, а по относительным преимуществам внутри группы ответов. Это снижает дисперсию градиента и убирает необходимость в Value Model (подробнее про PPO).

Для каждого запроса сэмплируем
ответов
из старой политики
, считаем sequence-level награды
и превращаем их в относительные преимущества:
Отношение вероятностей берется на уровне токена:
Оптимизационный функционал в стиле PPO:
GSPO
GSPO (Group Sequence Policy Optimization) — относительно новый подход к RL-дообучению языковых моделей, который очень схож с GRPO. Отличается он тем, что градиент функционала зависит не от вероятностного распределения конкретных токенов, а от всей последовательности сразу.
Достигается это следующим изменением в формуле:
Как оказалось, такое изменение приводит к более стабильному обучению и быстрой сходимости, особенно для длинных цепочек рассуждений и MoE-моделей.
Больше интересных подробностей про GRPO vs GSPO можно узнать здесь.
GGPO?
К параметрам сэмплирования в vLLM можно добавить и грамматику в формате GBNF, как я описывал в предыдущей статье. Она позволит избежать синтаксически неверных выражений в ходе генерации эквивалентных SQL, однако для этого необходимо иметь на руках соответствующую грамматику.
Таким образом, мы можем в целом озаглавить этот фреймворком, как GGPO (Guided Grammar Policy Optimization).
Датасет
Для обучения было специально подготовлено 2 датасета:
https://huggingface.co/datasets/Safreliy/BIRD-train-postgresql
https://huggingface.co/datasets/Safreliy/synthetic_text_to_sql
Каждый из них отфильтрован так, чтобы сэмплы отрабатывали на СУБД PostgreSQL.
Во-первых, необходимо, чтобы БД создавались и заполнялись без ошибок, как в случае BIRD, так и в случае synthetic.
Во-вторых, необходимо, чтобы SQL-запросы корректно выполнялись и возвращали непустой результат.
Еще есть демодатасет с reasoning-контентом для предварительного SFT-дообучения, но его качество еще нужно верифицировать:
https://huggingface.co/datasets/Safreliy/BIRD-reasoning-postgresql
Результаты
Техническая информация:
Обучение производили на A100 80GB на протяжении трёх суток.
Фреймворк дообучения — TRL (GRPO + sequence-level sampling = GSPO).
Количество генераций в группе на один сэмпл равняется 10.
LoRA:
,
.
Генерация rollouts при помощи vLLM.
Параметры сэмплирования: temperature=0.7, top_p=0.9, top_k=20.
Batchsize = 20.

На валидационном датасете мы получили прирост целевой метрики на ~11% относительно базовой модели, что достаточно неплохо для такой крохи, как Qwen3-0.6B. При этом эпоха завершилась лишь на 28%, очевидно, что потенциал дальнейшего улучшения метрики присутствует.
Попробовать модель самому можно здесь.
Прогон на BIRD-бенчмарке
А как ведет себя модель на mini-dev-версии BIRD-бенчмарка?
Проведем эксперимент — 30 раз прогоним 500 примеров из бенчмарка на базовой модели Qwen3-0.6B (назовем ее BASE) и на нашей LLM, дообученной при помощи GSPO (назовем ее FT). И там, и там поставим temperature=0.3 и top_p=0.9.
Примеры из бенчмарка разбиты на 3 категории: simple, moderate и challenging. По каждой из них будем считать метрику EX. Также посчитаем суммарный total по всем категориям. Таким образом, будем мерить улучшение или ухудшение метрики по 4 разным показателям.
Также стоит помнить, что по примерам распределение неравномерное. Так, в simple входит 148 примеров, в moderate входит 250, а в challenging — 102, в сумме total=500.
Посмотрим на сводную табличку с подсчитанной статистикой:
simple (N=148) |
24.055 ± 1.964 |
23.807 ± 2.021 |
−0.248 |
−1.03 |
[−1.239; 0.744] |
0.63647 |
0.88056 |
−0.123 |
✖ |
moderate (N=250) |
6.627 ± 1.006 |
6.053 ± 0.865 |
−0.573 |
−8.65 |
[−1.040; −0.120] |
0.02310 |
0.06930 |
−0.603 |
✖ |
challenging (N=102) |
2.744 ± 1.322 |
3.659 ± 1.285 |
+0.915 |
+33.33 |
[0.261; 1.568] |
0.01120 |
0.04480 |
+0.692 |
✅ |
total (N=500) |
10.993 ± 0.797 |
10.820 ± 0.846 |
−0.173 |
−1.58 |
[−0.587; 0.234] |
0.44028 |
0.88056 |
−0.208 |
✖ |
Получилась интересная картина: BASE-модель уже неплохо генерирует SQL, и по total обогнать ее пока не удалось. Но если посмотреть по категориям, картинка становится яснее. Больше всего мы преуспели в challenging — самой малочисленной группе: относительный прирост EX составил +33%.
Из-за малого вклада этой категории в общий счет это почти не сдвинуло total. При этом есть некоторая просадка в moderate (−8.65%) — самой многочисленной категории. Однако после учета множественных проверок (Holm-adj p) она не считается статистически значимой.
Мы используем пермутационный тест со статистикой Уэлча как основной.
Идея такая: считаем t-статистику Уэлча (устойчивую к разным дисперсиям) для наблюдаемых данных, затем многократно перемешиваем метки групп (BASE/FT), каждый раз пересчитываем ту же статистику и смотрим долю значений статистики, не менее экстремальных, чем наблюдаемое.
Такой подход не требует предположений о распределении и хорошо работает при умеренных объемах выборки.
Для оценки величины эффекта мы приводим Hedges’ g — это стандартизированная разница средних. Это значение удобно интерпретировать: ~0.2 (малый эффект), ~0.5 (средний эффект), ~0.8 (сильный эффект).
Итог: дообучение надежно усилило модель на самых сложных задачах, но немного ослабило на средних, что, тем не менее, незначимо с точки зрения статистики.
Дальше для улучшения метрик можно:
Сделать предварительное обучение при помощи SFT на reasoning-датасете с постобучением при помощи GSPO.
Сделать калибровку датасета так, чтобы получить равномерное распределение по всем категориям сложности задач.
Увеличить ранг LoRA.
Увеличить время обучения так, чтобы покрыть больше примеров из TRAIN.
Добавить грамматику для guided decoding, покрывающую обучающий и валидационный датасеты.
Akina
Вот уж точно - "ближе к реальному миру". Наверное, нужно было обучать модель на запросах, построенных SQL-программистами, а не аналитиками. Ну или доверить написание эталонных ответов SQL-программистам. Я как-то не понял, с какой стороны смотреть-то надо.
А запросы - ну обняться и плакать. Несмотря на то, что вроде бы дадут верный результат. Хотя да - мы же аналитика заменяем. Тогда чего это я вдруг...