Почему «дать чат-боту доступ к базе» — плохая постановка задачи

Text-to-SQL обещает понятный интерфейс к управленческой аналитике: руководитель спрашивает обычным языком, система строит SQL и возвращает таблицу или короткий вывод. Для небольшого бизнеса это особенно привлекательно — не каждый вопрос нужно ставить в очередь аналитикам, а типовые срезы можно получать быстрее.

Но языковая модель не является системой управления правами и не гарантирует корректность запроса. Она может выбрать не ту таблицу, перепутать дату заказа с датой оплаты, забыть исключить отменённые операции или построить тяжёлое соединение. Если модель видит техническую схему целиком и исполняет произвольный SQL под широкими правами, ошибка превращается из неточного ответа в риск для конфиденциальности и доступности базы.

Поэтому безопасная архитектура начинается не с выбора модели. Сначала бизнес определяет, какие вопросы разрешены, какие показатели считаются официальными и какие данные может видеть конкретная роль. Модель в такой системе — переводчик намерения в черновик, а не администратор базы.

Правильная граница: отдельный аналитический контур

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

В этом контуре стоит публиковать не все исходные таблицы, а небольшой набор представлений с понятными бизнес-названиями. Например, вместо десятков таблиц заказов, платежей и возвратов можно открыть представление `sales_daily` с уже согласованными определениями выручки, оплаченного заказа, возврата и канала продаж. PostgreSQL позволяет управлять правами на объекты, а представления могут скрывать ненужные столбцы и фиксировать допустимую форму данных.

Это даёт три преимущества:

  • модель получает короткую и однозначную схему;
  • чувствительные поля не попадают в доступный каталог;
  • изменение внутренней структуры ERP не требует каждый раз переучивать логику помощника.

Представление само по себе не заменяет контроль доступа. Некоторые простые представления в PostgreSQL могут быть обновляемыми, поэтому роли всё равно нужно выдавать только необходимое право `SELECT`, а настройки `security_invoker`, `security_barrier` и политики базовых таблиц — проверять на реальных сценариях.

Семантический слой важнее большого промпта

Модель не должна угадывать смысл показателей по названиям колонок. Рядом со схемой ей нужен компактный каталог метрик:

  • определение показателя и формула;
  • допустимые измерения и фильтры;
  • часовой пояс и календарь;
  • правила работы с возвратами, НДС и отменами;
  • владелец показателя и дата обновления;
  • примеры корректных вопросов и запросов.

Если в компании два определения маржи, система должна не выбирать одно по вероятности, а запросить уточнение. Это важный класс отказа: хороший помощник умеет сказать, что вопрос неоднозначен, и объяснить, каких данных не хватает.

Каталог можно хранить как версионируемый YAML или JSON и подмешивать только релевантный фрагмент. Для старта не нужен сложный граф знаний: десять–двадцать согласованных метрик обычно полезнее, чем автоматический доступ к сотням таблиц.

Модель предлагает запрос, шлюз решает, можно ли его выполнить

Рекомендуемый поток состоит из нескольких независимых этапов:

1. Пользователь проходит обычную корпоративную аутентификацию; система получает его роль, подразделение и допустимый контур данных.
2. К вопросу добавляются только разрешённые описания витрин и метрик.
3. Модель возвращает структурированный черновик: намерение, SQL, использованные наборы данных, ожидаемую детализацию и предположения.
4. Детерминированный шлюз разбирает SQL в синтаксическое дерево и проверяет политику.
5. База строит план запроса без исполнения; шлюз оценивает объём, стоимость и риск.
6. Разрешённый запрос запускается в read-only транзакции с лимитами.
7. Результат проверяется, сокращается и только затем передаётся модели для объяснения.

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

Минимальный список проверок:

  • ровно один оператор и только `SELECT`;
  • разрешённые схемы, представления и столбцы;
  • запрет `INSERT`, `UPDATE`, `DELETE`, `MERGE`, DDL, `COPY` и вызовов неизвестных функций;
  • ограничение количества соединений, подзапросов и временного диапазона;
  • обязательный `LIMIT` для детальных выборок;
  • запрет чтения системных каталогов и технических схем;
  • проверка контекста пользователя и версии каталога метрик.

Белый список функций особенно важен. Формально запрос может начинаться с `SELECT`, но вызванная функция способна иметь побочные эффекты. PostgreSQL отдельно управляет правом `EXECUTE` на функции; сервисной роли помощника не нужно наследовать широкие разрешения.

Три независимых предохранителя в базе

Шлюз снижает риск, но не должен быть единственной защитой. В самой базе нужны независимые ограничения.

Первый предохранитель — отдельная роль с минимальными правами. Она получает `CONNECT`, доступ только к аналитической схеме и `SELECT` на конкретные объекты. Она не владеет таблицами, не может создавать объекты и не имеет `BYPASSRLS`.

Второй — режим read-only. Документация PostgreSQL указывает, что read-only транзакция запрещает обычные команды изменения данных и DDL. Это защита от случайной записи, а не универсальная песочница: временные объекты и функции требуют отдельного контроля. Поэтому read-only нужно сочетать с правами и белым списком функций.

Третий — Row-Level Security. Политики RLS ограничивают строки в зависимости от роли или выражения, а при включённой RLS без подходящей политики действует отказ по умолчанию. Так филиал может видеть только свои операции, даже если модель забыла фильтр. Важно помнить об исключениях: суперпользователи, роли с `BYPASSRLS` и обычно владелец таблицы обходят политики. Сервисная роль text-to-SQL не должна относиться ни к одной из этих категорий.

Как не положить базу «безопасным» SELECT

Запрос только на чтение всё равно может занять процессор, память и диски. Поэтому перед запуском полезно выполнять обычный `EXPLAIN` без `ANALYZE`: он показывает план, оценочную стоимость и ожидаемое число строк, не исполняя запрос. `EXPLAIN ANALYZE` для автоматического шлюза не подходит как предварительная проверка, потому что он действительно запускает оператор.

Оценки планировщика не являются гарантией, поэтому после предварительного фильтра нужны жёсткие лимиты исполнения:

  • `statement_timeout` для сессии помощника;
  • небольшой лимит возвращаемых строк и размера ответа;
  • отдельный пул соединений и ограничение параллельных запросов;
  • очередь с приоритетом ниже критичных операций;
  • отмена запроса при разрыве клиентского запроса;
  • мониторинг p95 времени, таймаутов и объёма прочитанных данных.

Лучший рубеж — отдельная реплика или витрина с собственным лимитом ресурсов. Тогда тяжёлый вопрос не блокирует оформление заказа. Но реплика добавляет задержку: пользователь должен видеть отметку свежести данных, а ответы о «сегодня» — учитывать лаг.

Ответ должен быть проверяемым

Показывать только красивую фразу опасно. Пользователь должен видеть:

  • использованный период и фильтры;
  • определение ключевого показателя;
  • время актуальности данных;
  • число строк или агрегатов в расчёте;
  • SQL либо его понятное раскрытие;
  • предупреждение об усечении результата;
  • уровень уверенности и нерешённые неоднозначности.

Для финансовых, кадровых и договорных показателей полезен режим «двух шагов»: сначала система показывает запрос и ожидаемую выборку, затем человек подтверждает выполнение или публикацию результата. Любые действия на основе ответа — изменение цены, рассылка, запись в CRM — должны проходить через отдельный типизированный инструмент, а не через тот же SQL-канал.

Логи стоит разделить. В техническом журнале достаточно идентификатора пользователя, хеша нормализованного запроса, версии политики, времени, оценки плана и результата проверки. Сами строки результата могут содержать персональные данные, поэтому их хранение должно быть отдельным решением с понятным сроком и доступом.

Как провести пилот за две недели

Начните не со свободного чата по всей базе, а с 20–30 повторяющихся вопросов одного подразделения. Например: продажи по неделям, доля возвратов, просроченная дебиторская задолженность и загрузка менеджеров.

Для каждого вопроса зафиксируйте эталонный SQL, допустимые формулировки, правильные фильтры и владельца метрики. Затем прогоните реальные вариации вопросов и измеряйте отдельно:

  • долю запросов, прошедших шлюз;
  • точность выбора метрики, периода и фильтров;
  • совпадение результата с эталоном;
  • долю правильных отказов при неоднозначности;
  • p95 времени ответа и долю таймаутов;
  • стоимость одного принятого ответа с учётом проверки человеком.

Первый релиз разумно оставить в режиме черновика: помощник строит запрос и объяснение, а аналитик подтверждает результат. Автоматический показ руководителю включается только для классов вопросов, где качество стабильно и цена ошибки невысока.

Что взять руководителю

Text-to-SQL — не способ заменить права доступа естественным языком. Это управляемый аналитический продукт с узким каталогом метрик, отдельным read-only контуром и несколькими независимыми предохранителями.

Практический следующий шаг: выберите одну витрину, заведите отдельную роль только с `SELECT`, опишите десять ключевых метрик и соберите двадцать эталонных вопросов. Такой пилот быстро покажет, где мешает модель, а где настоящая проблема — несогласованные определения и качество данных. И главное: даже очень убедительный SQL остаётся черновиком, пока политика и база не разрешили его выполнить.