LLM-powered Natural Language Interface to a relational database, реализованный в формате Telegram-бота.
Проект реализует интеллектуальный слой доступа к данным (NL → SQL), позволяющий пользователям получать информацию из PostgreSQL на естественном русском языке без знания структуры БД и SQL. Система использует LLM для генерации запросов с учётом схемы базы, валидирует SQL, выполняет его в изолированном read-only окружении и возвращает результат пользователю. Архитектура включает кэширование, многоуровневую защиту от опасных запросов и разделение прав доступа на уровне БД.
Пользователь формулирует вопрос на русском языке, после чего система:
- Интерпретирует намерение с помощью LLM
- Генерирует SQL с учётом схемы БД
- Валидирует запрос (разрешены только безопасные SELECT)
- Выполняет его через read-only пользователя PostgreSQL
- Кэширует и возвращает результат
- Ollama - cloud LLM провайдер
- Qwen3-Coder - модель для генерации SQL
- Aiogram - Telegram Bot framework
- SQLAlchemy - SQL toolkit для Python
- PosgreSQL - База данных
- Redis - In-memory кэш для SQL-результатов
app/
├── alembic/
├── app/
│ ├── bot.py
│ ├── config.py
│ ├── db.py
│ ├── cache.py
│ ├── const.py
│ ├── llm_processor.py
│ └── models.py
├── scripts/
│ └── load_data.py
├── tests/
│ └── test_llm_processor.py
├── data/
│ └── db.json
├── docker-compose.yml
├── Dockerfile
├── init-db.sh
├── requirements.txt
└── .env
# init-db.sh - cкрипт инициализации базы данных.
# Используется для:
# 1. Создания read-only пользователя базы данных (POSTGRES_READONLY_USER),
# который используется Telegram-ботом.
# 2. Назначения прав только на чтение (SELECT) для всех таблиц схемы.
# db.json — Ваш дамп базы данных проекта (необходимо добавить).
На примере аналитики видео-роликов
- Docker и Docker Compose
- Telegram Bot Token (@BotFather)
- Ollama API Key (cloud)
# 1. Клонировать репозиторий
git clone <your-repo-url>
cd telegram-video-analytics-bot
# 2. Настроить переменные окружения
cp .env.example .env
# Добавить TELEGRAM_BOT_TOKEN и OLLAMA_API_KEY в .env
# 3. Положить данные и запустить
mkdir -p data
cp /path/to/db.json data/
docker-compose up -dГотово!
- Запустится PostgreSQL
- Применятся миграции БД
- Загрузятся данные из
db.json - Запустится бот
Создайте .env файл на основе .env.example:
# Telegram Bot
TELEGRAM_BOT_TOKEN=your_bot_token_here
# PostgreSQL - Админ юзер
POSTGRES_ADMIN_USER=admin
POSTGRES_ADMIN_PASSWORD=admin_password
# PostgreSQL - Юзер только для чтения
POSTGRES_READONLY_USER=readonly_user
POSTGRES_READONLY_PASSWORD=readonly_password
# PostgreSQL - Основные настройки
POSTGRES_DB=video_analytics
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# Ollama конфигурация
OLLAMA_BASE_URL=https://ollama.com
OLLAMA_MODEL=qwen3-coder:480b-cloud # требуется облачная модель
OLLAMA_IMAGE=ollama/ollama:latest
OLLAMA_API_KEY=your_ollama_api_key_here
# Redis
REDIS_URL=redis://redis:6379/0
CACHE_TTL=86400services:
redis: # Redis 7 (кэш)
postgres: # PostgreSQL 16
migrations: # Применение миграций (admin user)
data-loader: # Загрузка данных (admin user)
bot: # Telegram бот (readonly user, cloud LLM)┌─────────────┐
│ Telegram │
│ User │
└──────┬──────┘
▼
┌───────────────────────────────┐
│ Telegram Bot (aiogram) │
└──────┬────────────────────────┘
▼
┌───────────────────────────────┐
│ Redis Cache │
│ - NL → SQL │
│ - SQL → Result │
└──────┬───────────────┬────────┘
│ cache hit │ cache miss
▼ ▼
┌───────────────────────────────┐
│ LLM Processor │
│ (Ollama Cloud) │
└──────┬────────────────────────┘
▼
┌───────────────────────────────┐
│ SQL Validator │
└──────┬────────────────────────┘
▼
┌───────────────────────────────┐
│ PostgreSQL (readonly user) │
└──────┬────────────────────────┘
▼
┌───────────────────────────────┐
│ Redis Cache (store result) │
└──────┬────────────────────────┘
▼
┌───────────────────────────────┐
│ User получает ответ │
└───────────────────────────────┘
"Сколько всего видео есть в системе?"
→ SELECT COUNT(*) FROM videos
"Сколько видео набрало больше 100000 просмотров?"
→ SELECT COUNT(*) FROM videos WHERE views_count > 100000
"На сколько просмотров выросли все видео 28 ноября 2025?"
→ SELECT COALESCE(SUM(delta_views_count), 0)
FROM video_snapshots
WHERE DATE(created_at) = '2025-11-28'- Использует aiogram 3.x (асинхронный)
- Обрабатывает команды
/start,/help,/clear_cache - Принимает текстовые вопросы на русском
- Отправляет результат пользователю
- Преобразует естественный язык → SQL
- Использует Ollama + Qwen3-coder (облачный)
- Валидирует сгенерированный SQL
Ключевые особенности:
- Промпт-инженеринг: Детальное описание схемы БД + инструкции + примеры запросов
- Обработка дат: "28 ноября 2025" → "2025-11-28"
- Безопасность: Только SELECT запросы, блокировка опасных операций
Пример промпта для Qwen3-Coder:
### Task
Generate a SQL query to answer: "Сколько видео набрало больше 100000 просмотров?"
### Database Schema
CREATE TABLE videos (
id VARCHAR(36) PRIMARY KEY,
creator_id VARCHAR(32) NOT NULL,
views_count BIGINT NOT NULL,
...
);
### Instructions
- Return ONLY the SQL query
- The query must return a single number
- Use COALESCE for NULL handling
### Examples
Question: "How many videos got more than 100000 views?"
SQL: SELECT COUNT(*) FROM videos WHERE views_count > 100000
...
### SQL Query
-
SQLAlchemy 2.0 с asyncpg (асинхронный драйвер PostgreSQL)
-
Два режима подключения:
-
Admin (для миграций и загрузки данных) - полные права
-
Readonly (для бота) - только SELECT
Безопасность на уровне БД:
# Бот использует readonly пользователя
db.init(use_admin=False)
# Даже если LLM сгенерирует DROP TABLE
# PostgreSQL откажет: "permission denied"- SQLAlchemy ORM модели
- Две таблицы:
videosиvideo_snapshots - Индексы для оптимизации запросов
- Redis 7 (in-memory)
- Асинхронный клиент
- TTL для всех ключей
- Кэширования результатов SQL-запросов
Проект использует два PostgreSQL пользователя:
- Admin (
admin:admin_password)
- Полные права на БД
- Используется ТОЛЬКО для:
- Применения миграций (
migrationsконтейнер) - Загрузки данных (
data-loaderконтейнер)
- Readonly (
readonly_user:readonly_password)
- Только
SELECTна все таблицы - Используется ботом для выполнения запросов
- НЕ МОЖЕТ выполнить
INSERT,UPDATE,DELETE,DROP
┌─────────────────────────────────────┐
│ Уровень 0: Redis Cache │
│ - Повторные запросы не доходят │
│ до LLM и БД │
└─────────────┬───────────────────────┘
▼
┌─────────────────────────────────────┐
│ Уровень 1: SQL Validator (Python) │
│ - Блокирует не-SELECT запросы │
│ - Проверяет опасные ключевые слова │
└─────────────┬───────────────────────┘
▼
┌─────────────────────────────────────┐
│ Уровень 2: PostgreSQL permissions │
│ - Readonly user физически не может │
│ изменить данные │
└─────────────────────────────────────┘
Пример:
Если LLM ошибётся и сгенерирует:
DELETE FROM videos WHERE id = '123'- SQL Validator откажет: "Non-SELECT query rejected"
- Если валидатор пропустит → PostgreSQL откажет: "permission denied for table videos"
Бот понимает вопросы на русском:
"Сколько всего видео есть в системе?"
→ SELECT COUNT(*) FROM videos;
"Сколько видео набрало больше 100000 просмотров?"
→ SELECT COUNT(*) FROM videos WHERE views_count > 100000;
"На сколько просмотров выросли все видео 28 ноября 2025?"
→ SELECT COALESCE(SUM(delta_views_count), 0)
FROM video_snapshots
WHERE DATE(created_at) = '2025-11-28';# Запустить всё
docker-compose up -d
# Остановить
docker-compose down
# Посмотреть логи
docker-compose logs -f bot
# Перезапустить бота
docker-compose restart bot
# Проверить статус
docker-compose ps# Запустить тесты
docker-compose run --rm bot pytest
# Тесты с подробным выводом
docker-compose run --rm bot pytest -v