| # NL→SQL Assistant — архитектура (максимально нагруженный вариант) |
|
|
| **Дата:** 2026-05-10 |
| **Статус:** v1 — superseded after CX/KM review (2026-05-10). Документ оставлен как исторический. Действующая baseline: `02_architecture_v2.md`. |
|
|
| > ⚠️ **Известные ошибки v1**, исправлены в v2: |
| > - **BIRD Mini-Dev = 500 примеров** (не 1500/11 БД, как ниже в разделе 5) |
| > - **codestral-2501 deprecated** с ноября 2025 → переход на `codestral-latest` (v25.08) |
| > - 11-узловой pipeline → 6 узлов |
| > - 4 коллекции Chroma → 2 |
| > - стек: убраны Prometheus + OTel + Redis (избыточно для solo-demo) |
| > - Vega-Lite от LLM → детерминированный chart picker + Plotly шаблоны |
| > - Mistral-only → provider abstraction + 30-question bakeoff |
| > - eval target: 50% EA → baseline 35-40%, stretch 50%, hard checkpoint неделя 3 |
|
|
| > «Максимально нагруженный» здесь = всё, что реально нужно для серьёзного |
| > демо-проекта уровня Senior Data Engineer, без фейкового overengineering'а. |
| > Каждый компонент обоснован задачей; ничего «на будущее». |
|
|
| --- |
|
|
| ## 1. Системная диаграмма |
|
|
| ```text |
| ┌──────────────────────────┐ |
| │ Web UI (Next.js + React)│ |
| │ ─ chat input │ |
| │ ─ table / chart / SQL │ |
| │ ─ history + bookmarks │ |
| └────────────┬─────────────┘ |
| │ HTTPS |
| ▼ |
| ┌──────────────────────────────────────────┐ |
| │ FastAPI gateway (auth, rate-limit, CORS)│ |
| │ /ask, /databases, /history, /eval/run │ |
| └────────────┬─────────────────────────────┘ |
| │ |
| ┌────────────────────┼────────────────────────────┐ |
| │ │ │ |
| ▼ ▼ ▼ |
| ┌──────────────┐ ┌────────────────┐ ┌────────────────────┐ |
| │ LangGraph │ │ Eval harness │ │ Schema indexer │ |
| │ NL→SQL graph │ │ (BIRD/Spider) │ │ (offline pipeline) │ |
| └──────┬───────┘ └────────┬───────┘ └─────────┬──────────┘ |
| │ │ │ |
| ▼ ▼ ▼ |
| ┌──────────────────────────────────────────────────────────────┐ |
| │ Shared services layer │ |
| ├──────────────┬──────────────┬─────────────┬──────────────────┤ |
| │ Mistral API │ Chroma DB │ Postgres │ Redis │ |
| │ codestral │ schema chunks│ target DBs │ result cache │ |
| │ large-2 │ few-shot Q→S │ (multi-DB) │ rate-limit state │ |
| │ mistral-embed│ │ + traces DB │ │ |
| └──────────────┴──────────────┴─────────────┴──────────────────┘ |
| │ |
| ▼ |
| ┌──────────────────────────┐ |
| │ Observability │ |
| │ Prometheus + OpenTelemetry│ |
| │ Langfuse traces │ |
| └──────────────────────────┘ |
| ``` |
|
|
| ## 2. LangGraph pipeline |
|
|
| Реиспользуем структуру из RAG_Support_Assistant |
| (`classify → retrieve → rerank → generate → verify → evaluate`), |
| но узлы заточены под NL→SQL: |
|
|
| ```text |
| ┌────────────────┐ |
| │ classify_intent│ intent = aggregation | ranking | filter | |
| └────────┬───────┘ time_series | comparison | lookup | |
| │ distribution |
| ▼ |
| ┌────────────────┐ |
| │ select_database│ если в системе несколько БД — выбрать целевую |
| └────────┬───────┘ по интенту + ключевым словам |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ retrieve_schema│ Chroma: relevant tables + columns + value samples |
| └────────┬───────┘ |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ retrieve_examples│ Chroma: top-k похожих Q→SQL пар (few-shot) |
| └────────┬───────┘ |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ generate_sql │ codestral-2501 + structured output (JSON-mode) |
| └────────┬───────┘ { "sql": "...", "rationale": "..." } |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ static_validate│ sqlglot parse → SELECT-only guard → schema check |
| └────────┬───────┘ (table/column existence vs catalog) |
| │ FAIL ──────────► retry_loop (max 2) |
| │ OK |
| ▼ |
| ┌────────────────┐ |
| │ explain_plan │ EXPLAIN на целевой БД, отказ если cost > threshold |
| └────────┬───────┘ (защита от full-scan на больших таблицах) |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ execute │ read-only коннект, statement_timeout, LIMIT-guard |
| └────────┬───────┘ |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ verify_result │ проверки: непустой? типы соответствуют интенту? |
| └────────┬───────┘ аномалии? (нулей/null'ов слишком много) |
| │ FAIL ──────────► retry_loop |
| │ OK |
| ▼ |
| ┌────────────────┐ |
| │ choose_format │ intent + result shape → |
| └────────┬───────┘ scalar | sentence | table | chart |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ render_answer │ mistral-large-2: NL-объяснение + chart-spec (Vega-Lite) |
| └────────┬───────┘ |
| │ |
| ▼ |
| ┌────────────────┐ |
| │ persist_trace │ sqlite traces + Langfuse span + Prometheus counter |
| └────────────────┘ |
| ``` |
|
|
| **Retry loop:** при фейле узлов `static_validate`, `execute`, `verify_result` |
| граф возвращается к `generate_sql` с приклеенным error-context'ом |
| (текст ошибки + предыдущий SQL + разъяснение что не так). Лимит — 2 попытки, |
| после чего отдаётся диагностический ответ. |
|
|
| ## 3. Schema-RAG: устройство индекса |
|
|
| **Проблема:** в BIRD есть БД с 50+ таблицами. Полная схема в промпт не лезет |
| и зашумляет генерацию. |
|
|
| **Решение:** offline-пайплайн `Schema indexer` строит несколько коллекций в Chroma: |
|
|
| | Коллекция | Чанк | Эмбеддится | |
| |---|---|---| |
| | `schema_tables` | таблица | имя + описание + список колонок + 3 sample строки | |
| | `schema_columns` | колонка | имя + тип + описание + min/max/nunique + 5 sample значений | |
| | `fewshot_qsql` | Q→SQL пара | вопрос + аннотация интента (SQL не эмбеддится) | |
| | `relations` | FK-связь | from_table.col → to_table.col + семантика | |
|
|
| При вопросе `retrieve_schema` делает гибрид BM25 + dense на `schema_tables`, |
| дотягивает топ-N колонок из `schema_columns` для отобранных таблиц, |
| добавляет связи между ними. Получается компактный «срез схемы» под вопрос — |
| обычно 5-15 таблиц вместо 50+. |
|
|
| `retrieve_examples` достаёт из `fewshot_qsql` 3-5 наиболее похожих |
| вопросов с эталонными SQL — это мощно поднимает качество на сложных диалектах. |
|
|
| ## 4. Безопасность исполнения SQL |
|
|
| Read-only — это не «обещание промптом», а реальные гарды на четырёх уровнях: |
|
|
| 1. **БД-роль:** отдельный postgres-пользователь с GRANT SELECT ONLY, |
| без CREATE/INSERT/UPDATE/DELETE/TRUNCATE/ALTER. |
| 2. **Парсер:** `sqlglot` AST-валидация — отказ при не-SELECT, при множественных |
| стейтментах, при наличии CTE с DML, при `pg_*`/`information_schema` без whitelist. |
| 3. **EXPLAIN-gate:** `EXPLAIN (FORMAT JSON)` перед `EXECUTE`, |
| отказ если `Total Cost > X` (порог настраивается на БД). |
| 4. **Runtime:** `SET statement_timeout = 30s`, обязательный `LIMIT 10000` |
| если в запросе нет агрегации. |
|
|
| ## 5. Eval harness |
|
|
| Отдельный модуль `eval/`, не часть онлайн-пайплайна: |
|
|
| ```text |
| eval/ |
| ├── datasets/ |
| │ ├── bird_mini.jsonl # 1500 Q→SQL пар, 11 БД |
| │ └── stackexchange_gold.jsonl # 20 наших эталонных вопросов |
| ├── runner.py # прогон через граф, сравнение |
| ├── metrics/ |
| │ ├── execution_accuracy.py # сравнение result-set'ов |
| │ ├── exact_match.py # SQL string match (слабая метрика) |
| │ └── component_match.py # сравнение по AST-компонентам |
| └── reports/ |
| └── 2026-05-10-baseline.html # отчёт по прогонам |
| ``` |
|
|
| CI прогоняет smoke-eval на 50 примерах при каждом merge в main. |
| Полный прогон — вручную или nightly. |
|
|
| **Целевое число:** Execution Accuracy ≥ 50% на BIRD-mini dev. |
| Опубликованные результаты codestral-2501 на BIRD ~57%, так что 50% |
| своими силами на узком сабсете — реалистично. |
|
|
| ## 6. Multi-DB switching |
|
|
| В `config/databases.yml` описаны подключения: |
|
|
| ```yaml |
| databases: |
| - id: stackexchange |
| dsn: postgresql://nlsql_ro@localhost/stackexchange |
| description: "StackOverflow public data — posts, users, votes" |
| schema_index: chroma://stackexchange |
| sample_questions: ["топ-10 тегов...", "распределение..."] |
| - id: bird_california_schools |
| dsn: sqlite:///data/bird/california_schools.sqlite |
| description: "California schools — performance, demographics" |
| schema_index: chroma://bird_california_schools |
| - id: chinook |
| dsn: sqlite:///data/chinook.sqlite |
| description: "Music store — invoices, tracks, customers" |
| schema_index: chroma://chinook |
| ``` |
|
|
| UI даёт переключатель «target DB», граф читает её из state. |
|
|
| ## 7. UI (Next.js + React) |
|
|
| Минимально, но без обрезков: |
|
|
| - **Chat-style вход** с подсветкой SQL в ответе и copy-кнопкой. |
| - **Multi-format ответ** — компонент сам решает, что рендерить |
| (scalar / sentence / DataGrid / Vega-Lite chart). |
| - **«Show working»**: разворачивающийся блок с retrieved schema, few-shot, |
| rationale, EXPLAIN-планом, временем выполнения. |
| - **History + bookmarks** в localStorage + опционально на бэке. |
| - **DB switcher** + список sample-вопросов под каждую БД. |
|
|
| Отдельная страница **`/eval`** — таблица результатов eval-прогонов, |
| графики динамики Execution Accuracy по коммитам. |
|
|
| ## 8. Стек целиком |
|
|
| | Слой | Технология | Почему | |
| |---|---|---| |
| | LLM | Mistral API: codestral-2501, mistral-large-2, mistral-embed | Жёсткое требование задачи | |
| | Orchestration | LangGraph | Уже знакома по RAG_SA, retry-loop из коробки | |
| | API | FastAPI + Pydantic v2 | Стандарт, типобезопасность | |
| | Vector DB | ChromaDB | Уже знакома, локально без отдельного сервиса | |
| | SQL parser | sqlglot | Multi-dialect, AST-валидация, dialect translation | |
| | Target DB | Postgres 16 (StackExchange) + SQLite (BIRD, Chinook) | Реализм + простота | |
| | Cache | Redis 7 | Кэш результатов SQL, rate-limit | |
| | Charting | Vega-Lite (через спеку из LLM) + Plotly fallback | LLM хорошо генерит Vega-spec'и | |
| | Frontend | Next.js 15 + Tailwind + shadcn/ui | Быстрый красивый UI | |
| | Observability | Prometheus + OpenTelemetry + Langfuse | Стандартный стек, переиспользуется из RAG_SA | |
| | Tests | pytest + httpx + testcontainers (Postgres) | Реальная БД в CI | |
| | Lint/Type | ruff + mypy strict (api/, agent/) | Как в DE_project | |
| | CI | GitHub Actions | smoke-eval + pytest + ruff + mypy | |
| | Deploy | docker-compose (dev) + Dockerfile multi-stage (prod) | Достаточно для демо | |
| |
| ## 9. Структура репозитория |
| |
| ``` |
| NL_SQL/ |
| ├── api/ # FastAPI app, routers, middleware |
| ├── agent/ # LangGraph nodes, prompts, state |
| │ ├── graph.py |
| │ ├── nodes/ |
| │ │ ├── classify.py |
| │ │ ├── retrieve_schema.py |
| │ │ ├── retrieve_examples.py |
| │ │ ├── generate_sql.py |
| │ │ ├── validate.py |
| │ │ ├── execute.py |
| │ │ ├── verify.py |
| │ │ ├── render.py |
| │ │ └── retry.py |
| │ └── prompts/ |
| ├── llm/ # Mistral provider, retry, cost guard |
| ├── schema_index/ # offline indexer for Chroma |
| │ ├── extractor.py # introspect Postgres/SQLite catalog |
| │ ├── enricher.py # описания, sample values, stats |
| │ └── builder.py # build Chroma collections |
| ├── execution/ # SQL guards, EXPLAIN gate, runner |
| ├── eval/ # см. раздел 5 |
| ├── frontend/ # Next.js UI |
| ├── config/ # databases.yml, prompts.yml |
| ├── data/ # BIRD dump, Chinook, sample dumps (gitignore) |
| ├── tests/ |
| ├── docker-compose.yml |
| ├── Dockerfile |
| └── docs/ |
| ├── 00_task.md |
| ├── 01_architecture.md ← вы здесь |
| ├── 02_eval_methodology.md ← TODO |
| └── 03_demo_questions.md ← TODO |
| ``` |
| |
| ## 10. Roadmap (этапы) |
|
|
| | # | Этап | DoD | |
| |---|---|---| |
| | 1 | **Bootstrap** | poetry/uv проект, FastAPI hello, Mistral provider, тесты на провайдер с моком | |
| | 2 | **Target DBs ready** | docker-compose поднимает Postgres со StackExchange dump + SQLite Chinook + BIRD dump в `data/` | |
| | 3 | **Schema indexer** | offline скрипт строит Chroma-коллекции, smoke-тест на retrieval | |
| | 4 | **Pipeline v1** | LangGraph граф работает на Chinook (простая БД), single-shot без retry | |
| | 5 | **Guards & verify** | sqlglot guard, EXPLAIN gate, retry-loop, тесты | |
| | 6 | **Eval harness** | runner + execution_accuracy метрика, baseline на BIRD-mini | |
| | 7 | **Multi-format render** | scalar/sentence/table/chart с автоопределением + Vega-Lite spec'и | |
| | 8 | **UI v1** | chat + DB switcher + history, end-to-end на 3 БД | |
| | 9 | **Polish & deploy** | docker-compose prod-like, README, демо-видео, eval-страница | |
| |
| Этапы 1-3 — фундамент (~неделя на каждом темпе). |
| Этапы 4-6 — суть проекта (~2 недели). |
| Этапы 7-9 — витрина (~неделя). |
| |
| Итого: ~5-6 рабочих недель в спокойном темпе или 2-3 в плотном. |
| |
| ## 11. Риски |
| |
| | Риск | Вероятность | Митигация | |
| |---|---|---| |
| | codestral-2501 даёт <40% на BIRD | средняя | улучшить few-shot retrieval, добавить chain-of-thought, schema-linking шаг | |
| | StackExchange dump слишком большой для локалки (≥100GB) | высокая | взять mini-dump (`gaming.stackexchange.com`, ~1GB) — реализм без боли | |
| | EXPLAIN-gate ломает легитимные тяжёлые запросы | средняя | tune порог на БД, дать override-флаг для админа | |
| | BIRD dataset лицензия | низкая | CC-BY-SA-4.0, для демо OK | |
| | Mistral API rate limits на eval-прогоне | средняя | local cache на (prompt → response), батчинг, exponential backoff | |
| |
| ## 12. Что в этой архитектуре «нагруженного» |
| |
| Если сравнить с минимальным NL→SQL (один промпт + один вызов LLM + execute): |
| |
| - **+ LangGraph pipeline на 10+ узлов** с retry-loop и error-context. |
| - **+ Schema-RAG из 4 коллекций** вместо «вся схема в промпт». |
| - **+ Few-shot retrieval** из эталонных Q→SQL пар. |
| - **+ Static validate (sqlglot AST) + EXPLAIN-gate + 4-уровневая защита**. |
| - **+ Multi-DB** с переключателем и per-DB индексами. |
| - **+ Eval harness на публичном бенчмарке** с измеримой метрикой. |
| - **+ Multi-format рендер** (4 формата + auto-выбор графика). |
| - **+ Полноценный observability stack** (Prom + OTel + Langfuse). |
| - **+ Web-UI** с историей, eval-страницей, «show working». |
| |
| Это потолок того, что осмысленно делать для демо-проекта без скатывания |
| в production-overhead (мульти-тенант, RBAC, OIDC, freshness monitor и т.д. — |
| всё то, что в RAG_SA уместно, а здесь было бы фейк-нагрузкой). |
|
|