Агент, если по-простому — это LLM с набором инструментов, который итеративно решает задачу.
На каждом шаге он решает, какой инструмент вызвать, с какими параметрами, и что делать с результатом.
Цикл продолжается, пока агент не соберёт достаточно данных для ответа.
Tools: как агент работает с данными
run_sql — основной инструмент
Принимает произвольный
SELECT-запрос, прогоняет через guardrails (об этом ниже), выполняет в ClickHouse, возвращает результат в виде таблицы. Если запрос упал с ошибкой — агент видит текст ошибки и может исправить SQL и попробовать снова.
get_reference_values — инструмент разведки данных.
Прежде чем строить сложный запрос, агент может спросить:
«Какие значения есть в столбце
subcategory для category = 'Мясная гастрономия'?»Технически tool делает
SELECT DISTINCT field
FROM ai.sellout_sales
WHERE ...
ORDER BY field
LIMIT 50
и возвращает список значений.
Но вызвать его можно только для 20 разрешённых полей (whitelist:
category, subcategory, group_name, brand, producer, client, region, city и другие справочные поля). Это защита — нельзя разведать, например, конкретные
store_addr или ean.Зачем это нужно? Пример: пользователь спрашивает «продажи в Дикси по творогу».
Агент вызывает
get_reference_values(
field='subcategory',
filter_clause="category = 'Молочная гастрономия'"
)
видит в ответе
Творожная группа (а не Творог), и строит SQL с правильным фильтром. Без этого шага LLM с высокой вероятностью напишет subcategory = 'Творог' и получит 0 строк.Системный промпт: 300 строк контекста для LLM
Промпт агента — это не просто «ты аналитик, пиши SQL»
Это 300 строк структурированных инструкций, разбитых на блоки:
- Роль и workflow из 5 шагов:
классификация запроса → уточнение у пользователя → разведка данных → SQL-запросы → анализ и выводы.
Если пользователь написал «привет» вместо аналитического вопроса — агент вежливо объясняет возможности и предлагает 5 конкретных примеров запросов.
- Полная схема таблицы — все 53 поля с типами и семантикой.
Агент знает, что
salesvalueVAT — это выручка с НДС (и что именно её нужно использовать, потому что salesvalue для ряда сетей равен нулю). Знает, что
reg_price_report содержит много нулей и для расчёта средней цены нужна формула средневзвешенной: sum(salesvalueVAT) / nullIf(sum(sales_weight_kg), 0).- Справочники и маппинги — точные значения
client по каждой сети с учётом регистра ('Пятёрочка' vs 'МАГНИТ' vs 'Ашан'), маппинг пользовательских терминов на поля (
"творог" → subcategory = 'Творожная группа'), иерархии (
chain -> client -> store_code_uni).- Подсказки для сложных запросов — паттерны SQL для типовых аналитических задач: сегментация через
ntile(), тренды через
lagInFrame(), pivot-матрицы через условную агрегацию (
sumIf/avgIf), расчёт индексов цен YoY с CTE.
И явные предупреждения:
linearRegression в ClickHouse не существует - не выдумывай.По сути, промпт — это сжатая документация по данным и по паттернам ClickHouse SQL, которую аналитик-человек держит в голове после месяцев работы с этой базой.
SQL Guardrails: безопасность на уровне валидации
Давать LLM возможность писать произвольный SQL — мощно, но опасно. Каждый запрос проходит через
validate_select_sql() до обращения к базе:- Разрешён только
SELECT или WITH ... SELECT.- Запрещены множественные statements (защита от
SELECT 1; DROP TABLE).- Denylist опасных ключевых слов:
INSERT, UPDATE, DELETE, DROP, ALTER, CREATE.- Автоматическое добавление
SETTINGS max_execution_time=45 — защита от тяжёлых запросов к 552 млн строк.- Отдельный read-only пользователь ClickHouse (
agent) с правами только на SELECT по ai.*.Невалидный SQL блокируется, а агент получает сообщение
SQL_VALIDATION_ERROR: ... с инструкцией исправить запрос. Тот же механизм работает для
SQL_EXECUTION_ERROR — если ClickHouse вернул ошибку (несуществующий столбец, синтаксическая ошибка), агент видит текст и пробует переписать запрос.Если интересно — могу поделиться системным промптом к агенту.
@alexs_journal