惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
Vercel News
Vercel News
N
Netflix TechBlog - Medium
M
MIT News - Artificial intelligence
F
Full Disclosure
GbyAI
GbyAI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
Hacker News: Ask HN
Hacker News: Ask HN
V
Visual Studio Blog
IT之家
IT之家
N
News and Events Feed by Topic
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
月光博客
月光博客
H
Heimdal Security Blog
T
The Blog of Author Tim Ferriss
腾讯CDC
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Troy Hunt's Blog
小众软件
小众软件
Jina AI
Jina AI
博客园 - Franky
Scott Helme
Scott Helme
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CERT Recently Published Vulnerability Notes
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
N
News and Events Feed by Topic
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
TaoSecurity Blog
TaoSecurity Blog
F
Fortinet All Blogs
Y
Y Combinator Blog
P
Privacy & Cybersecurity Law Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
量子位
H
Help Net Security
MongoDB | Blog
MongoDB | Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Last Watchdog
The Last Watchdog
博客园 - 三生石上(FineUI控件)
L
LINUX DO - 最新话题

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как и зачем мы писали семантический слой для ИИ аналитики – SLayer
Айван · 2026-05-26 · via Все публикации подряд на Хабре

Средний

13 мин

6.7K

1. Введение

1.1. Предыстория

Мы в Motley AI разрабатываем гибкую систему автоматизированной аналитики и отчётности с ИИ для B2B компаний, и это не просто чат с GPT и файлами на выходе или базовая автоматизация через n8n, а платформа с документами, шаблонами, источниками данных различных форматов и constrained AI – то есть, упор на воспроизводимость и детерминизм с минимизацией рисков галлюцинирования. ИИ у нас используется в нескольких случаях:

  1. Агент помогает формировать шаблоны, что можно делать и вручную в веб приложении или по API, но нормальной речью проще, а вручную остаётся лишь править детали.

  2. Можно скормить агенту пример документа и он его обобщает в шаблон, заменяя фактические формулировки, числа, графики и диаграммы на шаблонные элементы с настройками, формулами, промптами.

  3. В процессе резолва/рендеринга шаблона по заданным входным параметрам, большинство шаблонных блоков обрабатываются чисто алгоритмически, однако текстовые блоки могут использовать LLM для описания числовых результатов, составления выводов, гипотез и рекомендаций (особенно если в ваших источниках данных есть подходящий контекст с описанием бизнес-логики), а также для красивого оформления чисел в удобный текст.

  4. Интеграция результатов нашей системы с другими технологиями – часто в ad-hoc формате посредством диалога агента с подключенными MCP к нашей платформе и сторонним сервисам.

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

1.2. Семантические слои

Что это за технология-посредник над базами данных вроде бы неплохо рассказывают эти статьи:

  1. Почему важны семантические слои, и как собрать такой слой на DuckDB,

  2. Семантический слой для Аналитики ключевых метрик – dbt Metrics vs Cube.

От себя скажу, что это нечто вроде data mart с OLAP кубами поверх OLTP баз данных со star schema, а-ля вьюхи с бизнес-метриками над голыми транзакционными данными, только независимые от конкретных типов СУБД – одинаково работая на SQLite, MySQL, Postgres, BigQuery, Snowflake и многом другом. Зачастую их называют headless BI – семантические слои часто используются как источники показателей, высокоуровневых данных в дашбордах или для разработки своих аналитических подсистем.

По существу, семантические слои позволяют определять:

  1. Модели – обёртки над таблицами с метриками и измерениями, и зачастую это не плоская обёртка над таблицей в БД, а полноценный SQL подзапрос, где можно очищать данные или определять виртуальные колонки полезные для последующих вычислений.

  2. Метрики/показатели (measures) – колонки, обычно числовые значения, которые используются для агрегации, и часто агрегация сохраняется в модели как часть метрики (revenue_avg, revenue_sum, revenue_min/max)

  3. Измерения (dimensions) – колонки, которые используются как ключи группировки, часто это просто текстовые/строковые идентификаторы.

Помимо просто такой терминологии поверх колонок и таблиц баз данных, семантические слои привносят дополнительную пользу: кэширование и предагрегация данных, независимость от вида БД и диалекта SQL, и прочее в зависимости от конкретной технологии.

1.3. Мотивирующие недостатки

Сначала мы использовали Cube – современный, опенсорсный, удобный и производительный, написанный на JS/TS с performance-critical частями на Расте, а также имеющий хорошую интеграцию с Питоном, что мы интенсивно использовали для реализации массы кастомного функционала. Однако со временем приходило понимание, что многое из его функционала мы не используем, и слишком многое доопределяем на своей стороны костыльными путями, включая подзапросы / multi-stage queries и инжест БД и автоматическое формирование моделей. Многие семантические слои создавались для разовой ручной настройки моделей (с фиксацией нужных сложных подзапросов там же) и переиспользования командой продуктовых аналитиков, мы же, как B2B сервис, должны уметь легко подключаться к новым БД.

Из альтернатив никакие другие решения не дают нам нужной гибкости и универсальности, поэтому было принято ответственное решение разработать свой семантический слой, с учётом большого опыта команды как применения Куба в нашем проекте, так и разнообразных аналитических компетенций в прошлом – так родился SLayer aka Слеер!


2. Каким получился наш семантический слой

2.1. AI-First интерфейсы

Большинство семантических слоёв исторически проектировались как бэкэнд для BI-систем и дашбордов. Нам же было важно, чтобы слой одинаково удобно работал:

  • из Python,

  • через HTTP API,

  • через CLI,

  • для AI-агентов.

Мы сразу проектировали систему полноценно работающей по различным интерфейсам, включая конечно же Model Context Protocol (MCP) (через SSE и stdio) с доступом ко всему функционалу, администрированию и мета-данным, чтобы агенты могли помнить детали о конкретных таблицах и полях – для Клода и любого другого агента SLayer выглядит не как база данных, а как набор строго типизированных инструментов с чётким описанием, примерами и понятными текстами ошибок.

При этом, для классического бэкенда остаются доступны и чистый Python SDK, позволяя Слееру работать в одном процессе с нашими скриптами и серверами отдавая Pandas датафреймы; а также привычный HTTP REST API для запуска Слеера в виде отдельного сервиса; и CLI для быстрых вспомогательных задач или автоматизации различных воркфлоу, пайплайнов.

При этом, API и MCP-интерфейс нашей Motley Platform – это, по сути, надмножество над API и MCP SLayer'а: всё, что умеет SLayer, остаётся доступным «как есть» уровнем выше, без лишних прослоек, но с дополнением для работы с воспроизводимыми документами.

2.2. Взрыв кардинальности

"Корректная семантика кардинальности" звучит абстрактно, но на практике сводится к одному инварианту: добавление новой метрики в запрос никогда не должно менять ни количество строк, ни значения других метрик. Это та граница, за которой "обёртка над SQL" превращается в полноценный семантический слой – и именно об это регулярно спотыкаются самописные витрины данных и даже промышленные инструменты.

И тогда получается одна из самых неприятных проблем аналитических запросов – потеря контроля над кардинальностью. Достаточно одного неаккуратного JOIN’а:

SELECT
    customers.country,
    SUM(orders.revenue)
FROM customers
JOIN orders ...
JOIN order_items ...
GROUP BY 1

и внезапно SUM(revenue) начинает считать значительно большие доходы – руководство сначала разглядывает дашборды и радуется, а потом смотрит на банковские счета и грустит. Затем идёт к разрабам/аналитикам, и те начинают грустить ещё сильнее. И порой такие ошибки выглядят правдоподобно и подолгу живут в продакшене.

Поэтому внутри SLayer мы пришли к идее неявных подзапросов – джоины изолируются в подзапросы автоматически, что предотвращает подобные комбинаторные взрывы и обманчивые значения. Фактически семантический слой начинает выступать не просто абстракция/обёртка над SQL, а система сохранения корректной аналитической семантики.

2.3. Агрегации отделены от метрик

В большинстве классических инструментов (включая Cube и dbt) логика агрегации статична – вы сразу объявляете все нужные метрики на уровне модели:

revenue_sum
revenue_avg
revenue_min

Для этого надо или чётко знать, что вам понадобится, а что нет, либо, как это часто бывает, топорно объявлять декартово произведение колонок с методами агрегации, что быстро приводит либо к взрывному росту числа метрик, раздуванию моделей и контекстного окна ИИ агентов. Мы пошли немного другим путём:

revenue:sum
revenue:avg
revenue:min

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

price:weighted_avg(weight=quantity)
last(change(revenue:sum))
revenue:sum / time_shift(revenue:sum, -1, 'year')

2.4. Временной контекст без монструозных запросов

Сравнить текущий период с прошлым годом, посчитать скользящее среднее (rolling average), кумулятивную сумму или когортный LTV – для аналитика или LLM это обычно требует написания многоэтажных SQL-запросов с подзапросами и/или оконными функциями.

Дабы уменьшить когнитивную нагрузку на наших пользователей, будь то человек или ИИ, мы заложили удобные механики для работы со временем в ядро SLayer'a – first, last, change, change_pct, etc. – чтобы всевозможные смещения времени выглядели как простые функции в запросе, а движок сам генерирует диалектный SQL с нужными подзапросами под целевую СУБД – опять же, сохраняя кардинальность оригинального запроса!

2.5. Query Lists aka подзапросы как граф вычислений

На практике часто бывает нужным выполнять сложные, вложенные или многоэтапные запросы:

  • промежуточнык rollup'ы

  • дедупликация

  • оконные вычисления

  • обогащение метрик

  • un-pivot

И человеку, и LLM с этим трудно – приходится одновременно удерживать:

  • гранулярность каждой стадии,

  • порядок вычислений,

  • алиасы,

  • последовательности группировок и агрегаций,

  • и совместимость между уровнями вложенности.

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

Поэтому в SLayer мы сделали Query List – механизм именованных многостадийных запросов, где подзапросы описываются обычными объектами Слеер-запросов в списке/массиве, однако теперь они могут ссылаться не только на модели, но и друг на друга, а движок сам строит DAG (ациклический граф зависимостей) и генерирует итоговый SQL.

2.6. Динамический инжест и Запросы как Модели

Традиционный семантический слой – это статическая вещь, которую дата-инженер настраивает неделями. Но когда в B2B SaaS продукте новый клиент подключает свою БД, мы должны уметь начинать работать с ней здесь и сейчас.

SLayer умеет интроспектировать схему БД на лету, формируя модель или валидируя её на соответствие действительности. Помимо этого, мы реализовали сохранение запросов в модели (как объект в нашем формате, а не чёрный ящик SQL), что позволяет определять более комплексные метрики и переиспользовать сложную логику.

2.7. Помнящие агенты

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

  • Строки в каких таблицах нужно дедуплицировать

  • Какие поля "грязные" и могут требовать предобработки

  • Какие поля, метрики deprecated, что вместо чего использовать

  • Где дата-время с нормализованными часовыми поясами, а где нет

И много других мелких, но важных моментов, влияющих на адекватность аналитических отчётов.

Для этого мы добавили механику «уроков» (learnings) – агент, работающий со SLayer'ом, может сохранять заметки на естественном языке привязанные к каноническим идентификаторам сущностей вида mydb.orders.amount. В следующей сессии, когда агент ищет что-то в моделях, эти заметки возвращаются ранжированными вместе с самой документацией моделей. Удалили колонку – SLayer каскадно почистит теги из всех связанных заметок. Сам поиск – это слияние BM25 по тегам, full-text индекса tantivy по всем сущностям и плотных эмбеддингов через Reciprocal Rank Fusion. Полнотекстовый индекс пересобирается в памяти на каждый запрос – звучит как безумие пока не замеришь: на нескольких тысячах сущностей это миллисекунды, зато мы избавились от целого класса багов связанных с устаревшими индексами.

2.8. Чего пока нет

На данный момент в Слеере отсутствует следующий функционал, который встречается в других технологиях:

  • Auth/IAM – вместо выбора и навязывания какого-то одного метода аунтентификации и авторизации, мы проектировали SLayer гибким в том числе изнутри, он легко расширяется, поэтому в случае Motley Platform за управление идентификацией и доступом отвечает наша интеграция.

  • Web UI – в связи с тем, что наши основные интерфейсы это MCP и Python, у нас мало потребности в графическом интерфейсе, хотя возможно со временем мы и его добавим.

  • Кэширование и пре-агрегация – в наших задачах важно гибкое построение сложных запросов и актуальные данные, это сложно грамотно и эффективно кэшировать, поэтому пока что мы полагаемся на оптимизации СУБД – когда упрёмся в производительность, то будем думать, что и как оптимизировать и кэшировать на нашей стороне. Может перепишем ядро на мой любимый Rust 🦀

2.9. Open Source

SLayer изначально задумывался как внутренняя утилита, но чем дальше мы проектировали, тем больше приходили к пониманию, что эта технология и описанный функционал будут полезны как внутри Motley Platform, так и сами по себе, широкому кругу пользователей. Поэтому мы сразу опубликовали SLayer под MIT – и для прозрачности, и как потенциальную ступеньку к нашему основному продукту, и потому что хорошие инструменты обычно живут и развиваются ярче и дольше за пределами одной компании.


3. На практике

3.1. Пример запроса

Допустим, вам нужен примерно такой запрос:

  • Метрика: процентное изменение суммы дохода

  • Время: 2026 год в разбивке по месяцам

  • Группировка: по имени региона

  • Фильтр: показать только прирост

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

{
  "source_model": "orders",
  "measures": [{
    "formula": "change_pct(revenue:sum)",
    "name": "revenue_growth_pct"
  }],
  "dimensions": [
    "customers.regions.name"
  ],
  "time_dimensions": [{
    "dimension": "created_at",
    "granularity": "month",
    "date_range": ["2026-01-01", "2026-12-31"]
  }],
  "filters": [
    "change(revenue:sum) > 0"
  ]
}

Какой получится фактический SQL запрос:

WITH monthly_revenue AS (
  SELECT
    r.name                            AS region,
    DATE_TRUNC('month', o.created_at) AS month,
    SUM(o.amount)                     AS revenue
  FROM orders     o
  LEFT JOIN customers c ON o.customer_id = c.id
  LEFT JOIN regions   r ON c.region_id   = r.id
  WHERE o.created_at >= '2026-01-01'
    AND o.created_at <  '2027-01-01'
  GROUP BY r.name, DATE_TRUNC('month', o.created_at)
),
with_prev AS (
  SELECT
    region, month, revenue,
    LAG(revenue) OVER (PARTITION BY region ORDER BY month) AS prev_revenue
  FROM monthly_revenue
)
SELECT
  region, month,
  (revenue - prev_revenue) / NULLIF(prev_revenue, 0) AS revenue_growth_pct
FROM with_prev
WHERE revenue - prev_revenue > 0;

Что из этого проще писать и понимать вам и вашим близким агентам?

3.2. Как запустить

Одним из практических требований к SLayer с самого начала была простота использования и запуска в различных сценариях, включая встраивание в приложение как изнутри Питона, так и по HTTP REST API, использование через CLI в пайплайнах, и конечно же простой MCP для ИИ агентов – и чтобы это всё можно было легко поднять локально без особой инфраструктуры. Поэтому у нас есть много разных способов запуска описанных в документации описанных в Getting Started. Рассмотрим здесь основные:

UV – чтобы использовать Слеер через CLI или по HTTP REST или MCP, проще всего установить SLayer через UV, установить можно так:

uv tool install motley-slayer
slayer

Или запустить разово, без формальной установки:

uvx --from 'motley-slayer' slayer serve --demo

Классическая установка с PyPI – Если нужно встроить в питонячий скрипт или приложение:

pip install motley-slayer

Docker – изолированная среда для более серьёзного продакшена на Compose, Kubernetes, etc. У нас пока нет официального публичного Докер-образа, но наш репозиторий весит немного и Докер-файл собирается быстро.

3.3. Где данные?

Своя БД

Слеер может подключаться к различным СУБД – Postgres, MySQL, SQLite, DuckDB, BigQuery, Snowflake и другие.

Подробнее смотрите в Getting Started для каждого конкретного способа запуска и в Configuration: Storage.

Демо

Чтобы не заставлять пользователей сразу подключать свои базы данных, мы добавили поддержку тестового набора данных Jaffle Shop. Это классический демонстрационный ecommerce-датасет, который часто используется для проверки аналитических моделей. Он содержит типичную структуру с покупателями, заказами, товарами, магазинами, и прочее, что позволяет сразу попробовать сильные стороны Slayer'a на реалистичные аналитических запросах.

Запускается аргументом командной строки: --demo


4. Инженерные детали

Небольшая экскурсия по памятным внутренностям SLayer'a для самых любознательных.

4.1. Как оно устроено под капотом

Модель в SLayer'е это Pydantic-объект содержащий такие основные поля:

  • Источник – имя исходной таблицы, или SQL-запрос, или SLayer-запрос.

  • Список колонок – мы решили унифицировать метрики и измерения на уровне моделей.

  • А также именованные формулы, список join'ов, кастомные агрегации, стандартные фильтры.

Запросы у нас это конечно же тоже Pydantic-объекты, основное содержимое:

  • Источник – имя модели или другой запрос.

  • Формулы и метрики для агрегации.

  • Измерения для группировки.

  • Фильтры.

Когда вы вызываете engine.execute(query), SLayer обогащает запрос – резолвит имена колонок, раскрывает отсылки к разным моделям, desugars (дешугарит?) магические функции-трансформации в подзапросы, затем, используя мощную библиотеку sqlglot, конструирует AST (абстрактное синтаксическое дерево) (никакой ручной работы с SQL-строками), и транслирует в диалект SQL'a целевой СУБД. Выполняются запросы через SQLAlchemy и по возможности асинхронно, в зависимости от доступных драйверов БД.

У нас есть пара уровней и синтаксисов выражений:

  1. Внутри формул и фильтров запросов имеется небольшой DSL: revenue:sum / *:count, rank(amount:sum) <= 10, арифметические и логические операторы, универсальные функции. Обрабатывается на данный момент Питонячим AST парсером с кастомной обработкой.

  2. Внутри же Column.sql вы можете задавать определение колонки посредством чистого SQL выражения (sqlglot всё распарсит), и вам доступны диалектно-специфические формулы и конструкции вроде CASE WHEN, json_extract и многого другого. Аналогично SQL доступен в Model.aggregations для определения кастомных агрегаций.

4.2. sqlglot – иногда обманывающий спаситель

Весь кросс-диалектный SQL внутри SLayer'a собирается как AST с помощью чудесного пакета sqlglot и потом транспилируется для целевой СУБД. sqlglot вытягивает 99% различий между Postgres / DuckDB / SQLite / MySQL / ClickHouse / BigQuery / Snowflake / Trino без нашего участия. Однако оставшийся 1% выходит боком.

Свежий пример, на который мы наступили буквально на прошлой неделе при настройке CI для MySQL-примера: MySQL-диалект sqlglot'а переписывает VAR_POP(x) в VARIANCE_POP(x) – функцию, которой в MySQL не существует (и получаешь 500). Заодно VAR_SAMP(x) переписывается в VARIANCE(x), а VARIANCE в MySQL – это алиас для VAR_POP. То есть выборочная дисперсия тихо превращается в популяционную. Никакой ошибки, просто слегка не то число на дашборде. Сразу два бага всего в восьми символах SQL. Лечим это через exp.Anonymous – единственный узел AST, который sqlglot не переписывает, и мы с хирургической точностью прописываем там подобную специфику.

Мораль: кросс-диалектные генераторы SQL – прекрасная вещь, но они не идеальны, к этой асимптоте совместимости можно приближаться бесконечно, и если вы хотите гарантировать совместимость с различными СУБД, то вам обязательно нужно разрабатывать и запускать интеграционные тесты для каждого диалекта, который вы поддерживаете. Именно поэтому у нас в CI поднимаются живые SQLite, DuckDB, Postgres, MySQL и ClickHouse.

4.3. В SQLite нет статистических агрегаций. Так что мы сделали свои.

Многие СУБД предоставляют большой набор встроенных статистических функций вроде median, percentile_cont, stddev_samp, stddev_pop, var_samp, var_pop, corr, covar_samp, covar_pop – ничего из этого в SQLite нет. Мы регистрируем их как внешние Python-функции при подключении. SLayer вроде бы семантический слой, но на SQLite он по совместительству ещё и тихая библиотека статистики. Оказалось весёлым писать однопроходный алгоритм Welford'а для потоковой корреляции, когда альтернатива – NotImplementedError.

4.4. Миграции живут только в Pydantic классах

Мы несколько раз меняли схему данных – SlayerModel уже на v6, а SlayerQuery на v3. При этом, любой тип хранилища – YAML-файлы, SQLite, и всё что позже добавим мы или пользователи – все получают поддержку миграций автоматически, потому что цепочка конвертации висит на @model_validator(mode="before") самих Pydantic-классов. Движок видит только последнюю версию и даже не знает, что миграции существуют.


5. Заключение

Иронично, что несмотря на AI-first позиционирование, основные сложности при разработке SLayer'a оказались абсолютно классическими – программными, алгоритмическими и аналитическими. Корректная семантика кардинальности, кросс-диалектный SQL, миграции схем, асинхронный движок, удобный DSL – всё то, чем разработчики занимались задолго до появления GPT и LLM. Большая языковая модель это отличный пользователь хорошей системы, но плохая замена самой системе. Чем строже и предсказуемее инструмент, тем адекватнее и полезнее агент, который им управляет.

Кому и когда SLayer пригодится

  • AI-командам, которым нужно дать агенту структурированный и надёжный доступ к данным клиентов вместо «держи SQL REPL и удачи».

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

  • Разработчикам дашбордов и аналитических продуктов, которым хочется headless-BI без вендор-лока на конкретный SQL-диалект.

  • Аналитикам, уставшим от громоздких моделей в Cube/dbt и хрупких монструозных SQL запросов.

Если вы узнали что-то из перечисленного – добро пожаловать:

А если вам нужен не только семантическое, а готовое решение под ключ для аналитики и отчётности – добро пожаловать на Motley Platform!