Skip to main content
Glama
Eshanya1

sql-specialist-mcp

by Eshanya1

sql-specialist-mcp

Небольшая дообученная модель с открытыми весами, которая отвечает на вопросы на естественном языке о базе данных SQLite и работает как MCP-инструмент — любой MCP-клиент (Claude Desktop, Claude Code, кастомные агенты) может вызывать его напрямую. В основе лежит eval-харнесс, измеряющий точность исполнения: он сравнивает специалиста с запросами к frontier-модели по точности, задержке и стоимости.

Смысл проекта не в том, чтобы «сделать демо text-to-SQL» — а в том, чтобы показать части LLM-инжиниринга, которые лежат ниже промптирования: взять маленькую модель, адаптировать её под одну задачу через LoRA, эффективно обслуживать и доказать с помощью настоящего eval-а, основанного на исполнении, что дешёвый специалист конкурентоспособен с запросами к frontier-модели (или превосходит их) для этой узкой задачи.

Попробуйте интерактивное демо — пройдите все 28 реальных eval-вопросов и посмотрите, какой SQL реально сгенерировал специалист, его задержку и результирующие строки рядом с frontier-базлайном. Устанавливать ничего не нужно.

Зачем это существует

Большинство «AI-портфолио» проектов text-to-SQL — это быстрые старты на LangChain. Здесь два отличия:

  1. Eval строгий, а не «по ощущениям». Каждый золотой запрос исполняется против базы данных на этапе сборки датасета (139/139 проверено), а скоринг сравнивает результирующие наборы, а не текст запроса — семантически корректный запрос с другим порядком колонок всё равно получает «верно». Предиктор, который просто повторяет золотой SQL, показывает результат 100%; предиктор, который всегда возвращает заведомо неверный запрос, показывает 0%. Оба проверены как sanity-тесты (tests/test_harness_oracle.py), так что корректность самого харнесса не постулируется.

  2. Это полезный инструмент, а не просто демо-репозиторий. Дообученная модель доступна как настоящий MCP-инструмент (nl_to_sql) — наведите Claude Desktop или Claude Code на mcp_server/server.py, и он сможет реально выполнять запросы к базе данных в ходе разговора.

Related MCP server: mcp-sqlite-chat

Результаты

Полный конвейер запущен end-to-end на реальном железе с обеих сторон: реальное LoRA-обучение, реальное слияние, реальная GGUF-квантование, реальный Ollama-сервер, реальный eval — а также настоящий фронтир-базлайн через живой Claude API. Базовая модель: Qwen/Qwen2.5-Coder-0.5B-Instruct (выбрана ради быстрой итерации на ноутбуке; см. Fine-tuning ниже о пути к 1.5B).

Предиктор

Точность

n

p50 задержка

p95 задержка

Стоимость / 1 тыс. вызовов

frontier: Claude Haiku 4.5 (промпт)

53.6%

28

1055ms

1884ms

$1.06

sql-specialist (дообучен, квантован, локально)

92.9%

28

207ms

371ms

$0.00

Читайте это с оговоркой, а не с заголовком. Я вручную просмотрел все 13 измеренных «провалов» Claude Haiku на этом eval-наборе: ни один из них не был логической ошибкой в SQL. Все 13 — рассинхрон по выбору колонок или порядку строк: например, вывод (name, email), когда золотым ответом был только (name), или верные строки в другом порядке, чем курс по ORDER BY, которого исходный вопрос вообще не задавал. Строгая метрика execution-accuracy (в eval/execution.py сравниваются строки результата колонка-к-колонке) оценивает это точно так же, как действительно неверный запрос, чего дообученный специалист никогда не дасть, потому что он запомнил стандарты этого датасета из 111 обучающих примеров — а фронтир-модель, вызванная zero-shot, просто не может об этом знать. Полная порций-по-провалам таксономия в COMPARISON.md.

Итак: разрыв по точности реален, но отчасти это артефакт того, что оценивает eval, а не чисто разрыв в рассуждениях. Разрыв по задержке и стоимости — не артефакт — 207ms/локально/бесплатно против 1055ms/$1.06 за 1 тыс. вызовов — это фактический результат запуска локально-квановой 0.5B модели вместо вызова API, и именно на этом сравнении основана посылка проекта.

Собственные 2 провала специалиста (из 28) были настоящими логическими ошибками, а не несоответствием форматирования: галлюцинирование правдоподобной колонки orders.total, которой нет в этой схеме, и исключение квалификатора таблицы в одном многократном SELECT. Обучение сходилось стабильно на 3 эпохах (eval loss 0.060 → 0.048 → 0.008), а квантованизация (988MB f16 → 373MB q4_k_m) обслуживается через Ollama примерно за 200мс..

Что здесь реально

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

  • Генерация базы и датасета доказуемо корректны. shopsphere.db создаётся детерминированно (сид = 42); каждая из 139 золотых пар (вопрос, SQL) в data/*.jsonl создаётся из параметрических шаблонов и выполняется против реальной базы на этапе сборки — шаблон, дающий невалидный SQL, проваливает сборку, а не молча выдаёт неверную метку.

  • Корректность eval-харнеса сама протестирована, а не принята постулатом — tests/test_harness_oracle.py утверждает, что оракуляровый предиктор (возвращающий золотой SQL дословно) даёт ровно 100%, а умышленно неверный предикков даёт ~0%, прежде чем чьему-то числу доверяют.

  • Execution-accuracy, а не сравнение строк. eval/execution.py сравнивает результирующие множества (не учитывая порядок, если только в золотом запросе нет ORDER BY) — запрос, написанный иначе, но семантически эквивалентный, всё равно засчитывается как корректный.

  • Дообучение реальное, на этой машине, с подтверждённой сходимости. LoRA (8.8M обучаемых параметров, 1.75% от модели) за 3 эпохи, eval loss монотонно снижается на каждой эпохе. См. Engineering notes ниже о двух реальных багах, найденных и исправленных по пути.

  • SQL-выполнение настоящим образом песорчено, а не просто проинструктировано «вести себя хорошо»: read-запросы валидируются с помощью regex-списка разрешённых и выполняются на настоящем read-only SQLite-соединении (mode=ro на уровне ОС) — даже баг в regex-защите не приведёт к записи. Это важно не только для eval-харнеса, потому что та же защита работает в MCP-сервере, где SQL приходит от модели, отвечающей на вопрос агента, а не из курируемого eval-набора.

  • MCP-сервер — это реальный вызываемый инструмент с настоящим дообученной моделью, проверено end-to-end: nl_to_sql("Какие сотрудники не имеют назначенного менеджера?") → генерация SQL через квантованную модель на Ollama → выполнение запроса read-only → возвращает настоящие строки → пишет задержку/стоимость в observability.

  • Observability реализована самостоятельно и не имеет зависимостей — observability/logger.py логирует каждый вызов (задержку, токены, примерную стоимость, успех/неуспех) в локальный файл SQLite, без стороннего аккаунта, по той же схеме, что и pr-review-agent.

  • Frontier-база тоже настоящая — eval/baseline_frontier.py делал запросы к живому Claude API (Claude Haiku 4.5), а не только через красивый импорт. Его «провалы» оказались реальной находкой для методологии evala — см. Результаты выше иождения.

Инженерные заметки: два реальных бага, найденных при реальном запуске

Фактическое выполнение дообучения (а не оставление его в теории) выявило два настоящих бага с памятью в PyTorch, оба исправлены в текущем коде:

  1. Бесконечный рост кэшем MPS. Обучение на Apple Silicon через MPS-задний план transformers.Trainer приводило к раздуванию процесса до 23GB RSS и зависание, из-за динамического добавления padding по батчам — каждая уникальная форма (batch, seq_len) получает собственный памятьpool в MPS-аллокаторе PyTorch, который не возвращает OSS-память. Исправление: --device cpu в finetune.py, и, в фундаменталь, фиксированная длина padding (ниже), чтобы этот класс багов не повторился на любом бакенде.

  2. Обращение с Trainer/DataLoader, а не сама модель. Непосредственнная прямая propagation+обратная propagation показала 1.6 секунды на примере; тот же расчёт через transformers.Trainer простаивал по несколько минут между логами без соответствующей нагрузки. Причина — изолировать фактическую модель+LoRA с ручным замером времени, прежде чем принять, что баг в коде модели. Исправление: заменить Trainer на цикл обучения околоставим вручную (~40 строк) в training/finetune.py — та же LoRA-установка, прямой контроль над циклом батчей, без запредельных оверхейдов. Также перевели сборку батчей с динамического на фикированную длину padding (все батчи одинаковой формы) — это отдельно исправило паттерн фрагментации аллокатора из бага #1.

Ни то, ни другое — не в обходит и not workaround — оба видны в training/finetune.py как единственная реализация, а не как альтернативный путь.

Архитектура

data/build_dataset.py ──▶ data/{train,eval}.jsonl   (139 examples, template-generated,
                                                       every gold SQL executed at build time)
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                      ▼
training/finetune.py   eval/baseline_frontier.py   tests/test_harness_oracle.py
  (LoRA on a small        (prompt Claude Haiku/       (sanity-checks the harness
   open model)             Sonnet as the baseline)     itself before trusting scores)
        │                     │
        ▼                     │
training/merge_and_quantize.py
        │                     │
        ▼                     ▼
serving/ollama_predictor.py ──┴──▶ eval/harness.py ──▶ eval/report.py ──▶ COMPARISON.md
        │                          (execution-accuracy scoring,
        │                           same logic for every predictor)
        ▼
mcp_server/server.py  (nl_to_sql tool -- installable in Claude Desktop/Code)
        │
        ▼
observability/logger.py  (latency, tokens, cost -- local SQLite, no external account)

Структура проекта

schema/           synthetic "ShopSphere" e-commerce DB (7 tables) + seeded generator
data/              templated gold (question, SQL) dataset -- every query build-time validated
eval/              execution-accuracy harness, frontier baseline, comparison report
training/          LoRA fine-tuning pipeline + LoRA-merge/quantize script
serving/           Ollama-backed and in-process HF predictors, Ollama Modelfile template
mcp_server/        the installable MCP tool (nl_to_sql)
observability/     self-built call logging (latency/tokens/cost), no external account
tests/             harness sanity checks (oracle predictor must score 100%)

Установка

python3.11 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt          # base: anthropic, mcp, requests
python schema/generate_data.py           # build the seeded database
python data/build_dataset.py             # build + validate the gold dataset
python tests/test_harness_oracle.py      # confirm the eval harness itself is sound

requirements-train.txt добавляет torch/transformers/peft/trl для пути fine-tuning — тяжелее, держится отдельно, чтобы путь eval/serving/MCP ставился быстро.

Запуск полного пайплайна

1. Baseline на fронтире (требуется ANTHROPIC_API_KEY):

export ANTHROPIC_API_KEY="..."
python -m eval.baseline_frontier --model claude-haiku-4-5
# writes eval/results_claude-haiku-4-5.json

2. Fine-tune специалиста (это то, что реально было запущено для получения результатов выше — ~15 минут активного вычисления на CPU ноутбука, хотя wall clock сильно depends на нагрузке; GPU намного быстрее — см. ниже):

pip install -r requirements-train.txt
python -m training.finetune --base-model Qwen/Qwen2.5-Coder-0.5B-Instruct --device cpu
python -m training.merge_and_quantize --base-model Qwen/Qwen2.5-Coder-0.5B-Instruct
# then follow the printed llama.cpp + ollama create instructions

3. Замерить специалиста так же, как бейзлайновый путь:

python -c "
from eval.harness import run_eval, report_to_dict
from serving.ollama_predictor import OllamaPredictor
import json
report = run_eval(OllamaPredictor())
json.dump(report_to_dict(report), open('eval/results_specialist.json', 'w'), indent=2)
"

4. Сгенерировать отчёт по сравнению:

python -m eval.report eval/results_claude-haiku-4-5.json eval/results_specialist.json

Тюнинг: масштабирование

Uказанные выше результаты используют Qwen2.5-Coder-0.5B-Instruct на CPU — для быстрой локальной итерации. training/finetune.py --base-model принимает любую HF causal-LM репо (или локальный каталог) — Qwen2.5-Coder-1.5B-Instruct — это логичная замена для лучшего качества, и один достаточноGPU (хватит T4 для этого размера датасета) обучает любой размер за пару минут вместо ~15:

pip install -r requirements-train.txt
python -m training.finetune \
  --base-model Qwen/Qwen2.5-Coder-1.5B-Instruct \
  --epochs 3

--device {cuda,mps,cpu} переопределяет автоопределение. MPS на Apple Silicon определяется автоматически, но пока не рекомендуется для этой задачи — см. Engineering notes выше.

MCP-сервер

# Backend defaults to a locally-served model via Ollama:
python -m mcp_server.server

# Or run against a prompted frontier model instead (no fine-tune needed --
# useful for trying the tool before training anything):
SQL_SPECIALIST_BACKEND=frontier SQL_SPECIALIST_MODEL=claude-haiku-4-5 \
  ANTHROPIC_API_KEY=... python -m mcp_server.server

Добавьте в MCP-конфиг Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "sql-specialist": {
      "command": "/absolute/path/to/sql-specialist-mcp/.venv/bin/python",
      "args": ["-m", "mcp_server.server"],
      "cwd": "/absolute/path/to/sql-specialist-mcp"
    }
  }
}

Затем спросите Claude что-то вроде «С помощью инструмента sql-specialist, какие клиенты никогда не делали заказ?» — он вызывает nl_to_sql, получает реальные строки из базы и отвечает на основе фактических данных..

Безопасность

  • SQL-выполнение доступно только для чтения на двух независимых уровнях: regex-правило, разрешающее только SELECT/WITH, а также настоящая уровня ОС за RZ-соединение SQLite (file:...?mode=ro) — второй уровень надёжности.

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

  • В репозитории нет секретов. ANTHROPIC_API_KEY читается только из окружения.

Что я построил бы дальше

  • Нормализовать eval для superset-колонок — считать предсказание корректным, если значения требуемых золотым колонок присутствуют, а не требовать точного совпадения колонка-к-колонке. Это фикс, который заявлен в «Таксономия ошибок» в COMPARISON.md; он очень наверноем закроет большую часть измеренного разрыва 53.6% → 92.9% и даст сравнение, которое изолирует собственно способность рассуждать от подгонки под стандарты.

  • Запустить eval/baseline_frontier.py и против Claude Sonnet — чтобы точка сравнения была сильнее (Haiku — это дешёвый/быстрый уровень; Sonnet — вопрос «насколько сама по себе сила модели закрывает разрыв»).

  • Дообучить Qwen2.5-Coder-1.5B-Instruct на GPU и сравнить точность с 0.5B результатом 92.9% — чтобы напрямую количественно оценить tradeoff размера и качества.

  • DPO по направлению двух известных fail-мод специалиста (галлюцинируемые колонки, пропажа квалаifiers таблиц в multi-join) — теперь, когда реальные данные про провалы естьи-точается.

  • vLLM-serving для измерения пропускной способности в сравнении с каналом Ollama/GGUF.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables querying and managing a SQLite database using natural language, with an MCP server and Groq LLM.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to query a SQLite database using natural language through the Model Context Protocol (MCP). Includes security guardrails that block destructive SQL operations.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP tool server providing SQLite database access for AI agents.
    MIT