Выручка за сентябрь: как AI‑агент получает три разных ответа

Всем привет, меня зовут Сергей Прощаев, я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce и преподаю на курсах разработки и архитектуры в ОТУС.
В этой статье расскажу, почему AI‑агент, который блестяще отвечает на вопросы по демо‑базе, начинает путаться на настоящем хранилище данных, и что с этим может сделать аналитик ещё до выбора модели.
Знакомая картина.
На демо вендор открывает чат, пишет «покажи выручку за сентябрь по регионам», и через пять секунд на экране таблица и красивый график. Пилот согласован. Через месяц агента подключают к рабочему хранилищу, финансовый директор задаёт тот же вопрос и получает число, которое не совпадает ни с отчётом бухгалтерии, ни с дашбордом продаж. Переформулирует вопрос — получает третье.
Модель здесь обычно не главная проблема. Она хорошо пишет SQL там, где задача уже формализована.
В корпоративном хранилище часть этой формализации живёт вне схемы: в определениях метрик, в кардинальности связей, в терминах компании и в том, по какой дате принято резать период.
Если этот контекст не дан машине явно, модель восстанавливает его наугад.

От 91% до нуля: почему цифры бенчмарков не переносятся на ваше хранилище
Задача, о которой пойдёт речь, называется enterprise text‑to‑SQL — генерация SQL по вопросу на естественном языке поверх корпоративного хранилища.
Я посмотрел на неё через три работы разных команд. Сразу оговорюсь: в одну шкалу эти цифры не складываются, у исследований разные данные, модели и протоколы.
Но тенденция одна: переход от открытых учебных данных к закрытым корпоративным резко усложняет задачу.
Первая — бенчмарк Spider 2.0. В версии, опубликованной на ICLR 2025, авторы собрали 632 задачи из реальных корпоративных сценариев: базы на BigQuery и Snowflake, нередко больше тысячи колонок, несколько диалектов SQL. Их агент на модели o1-preview решил 21,3% задач, тогда как на учебном Spider 1.0 — 91,2%, на BIRD — 73,0%. На 69,9 процентного пункта ниже, примерно в 4,3 раза.
Честности ради: исходный вариант бенчмарка с тех пор убрали, а на новом Spider 2.0-Snow (547 задач на Snowflake) лучшие агентные системы уже выше 90%. Так что 21,3% — не потолок, а снимок конца 2024 года.
Вторая — бенчмарк BEAVER. Его авторы взяли запросы не у студентов, а из логов закрытых корпоративных хранилищ: 9 128 пар «вопрос — SQL», 812 таблиц, 19 предметных областей. В среднем запрос затрагивает 4 таблицы и содержит 5,7 соединения, а заодно CTE, вложенные запросы и аналитические функции. Базовый агентный фреймворк ReFoRCE на GPT-5.2 показывает здесь 10,8%, на Claude 4.5 Sonnet — 11,4%.
Самое интересное в BEAVER — опыт с подсказками. Авторы делят задачу на пять подзадач: найти нужные таблицы, определить ключи соединения, сопоставить колонки, извлечь знание предметной области и разложить вопрос на шаги.
В среднем по моделям точность без подсказок — 9,5%. Подсказки по трём подзадачам схемы (таблицы, ключи, колонки) поднимают её до 18,9%, все пять — до 25,9%.
Цифра 30,1% из аннотации авторов получена в другом разрезе, в одну цепочку с этими её ставить не стоит.
И даже с полным набором подсказок ошибки остаются: 46,8% из них структурные (группировки, сортировки, сложные функции), 35,2% — снова выбор элементов схемы, 18,0% — фильтры.
Третья — статья Майкла Стоунбрейкера и Питера Чена в Communications of the ACM от 23 сентября 2026 года. Стоунбрейкер руководил проектом POSTGRES и получил премию Тьюринга, так что в нелюбви к базам данных его не заподозришь.
По их наблюдению, «чистая» LLM на BEAVER набрала ноль, RAG, промпт‑инжиниринг и агенты подняли результат примерно до 10%, а передача модели нужных таблиц и условий соединения — примерно до 30%. По словам авторов, это разница между «у технологии есть потенциал» и «технология не работает».
Меня в этих цифрах зацепила пропорция.
Подсказки о схеме и связях дают заметный прирост, хотя и не закрывают задачу целиком. Значит, один из главных дефицитов здесь — не столько умение писать SQL, сколько умение связать вопрос с конкретными данными, бизнес‑правилами и структурой запроса.
А это и есть работа аналитика.
История из первоисточника: хранилище, которое знает про J‑Term
В той же статье есть история, которую я бы показывал на каждом пилоте AI‑аналитики.
Команда Стоунбрейкера два года пробовала text‑to‑SQL на настоящих хранилищах. Начали с административного хранилища MIT: Oracle, больше 1 400 таблиц, к которому ходят многие подразделения университета.
Исследователи взяли реальные запросы из лога Oracle и вместе с пользователями восстановили, какой вопрос стоял за каждым.
Авторы называют четыре проблемы, которых почти нет в учебных бенчмарках:
Учебные данные лежат в открытом доступе, и модели могли их видеть при обучении. Корпоративные хранилища закрыты.
Схема «гниёт». Бизнес меняется, компания покупает и продаёт подразделения, приходят требования регулятора. Стоунбрейкер приводит пример: в разных таблицах оказывается шесть колонок «salary», и одна означает сумму на руки, а другая — до вычетов.
У каждого хранилища свой словарь. В данных MIT встречается J‑Term — месячный учебный период в январе, а здания там не названы, а пронумерованы. Из схемы этого не вывести.
Реальные запросы сложнее учебных: по наблюдению авторов, им обычно нужны два‑три соединения, а в BEAVER в среднем уже 5,7.
Помню, как на одном из проектов у меня была похожая ситуация, только без всякого AI. Три отчёта о выручке за один месяц расходились, и каждый автор был уверен в своей цифре.
Ушло несколько дней, чтобы выяснить: один считал по дате заказа, второй — по дате оплаты, третий забыл про возвраты.
Ни одна из команд не ошибалась в SQL. Они по‑разному понимали слово «выручка».
Эксперимент: три запроса на один вопрос
Чтобы не рассуждать вокруг темы, я собрал учебную схему интернет‑магазина в SQLite 3 и сгенерировал данные с фиксированным seed: 1 000 заказов с конца августа по конец сентября.
Типы намеренно упрощены: суммы — целые рубли, даты — строки вида ГГГГ‑ММ‑ДД. Часть заказов отменена, часть ждёт оплаты, 165 заказов оплачены двумя платежами, по 42 есть возвраты.
Всё, что ниже, можно повторить у себя: схема, данные, запросы и разбор ошибок лежат в репозитории text‑to‑sql‑dwh‑demo на GitHub, ссылка в конце статьи.
Команда ./run.sh собирает базу с нуля, печатает результаты и сверяет их с эталоном, а CI на каждый push проверяет, что данные воспроизводятся байт в байт. Нужен только sqlite3.
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER,
created_at TEXT, -- дата создания заказа
status TEXT, -- completed / pending / cancelled
amount INTEGER -- сумма заказа, рубли
);
CREATE TABLE payments (
payment_id INTEGER PRIMARY KEY,
order_id INTEGER, -- связь с заказом не объявлена внешним ключом
paid_at TEXT,
amount INTEGER
);
CREATE TABLE refunds (
refund_id INTEGER PRIMARY KEY,
order_id INTEGER,
refunded_at TEXT,
amount INTEGER
);Вопрос бизнеса: «Какая выручка за сентябрь?». Ниже три запроса. Каждый из них я видел в живых отчётах, и каждый правдоподобно мог бы сгенерировать агент, глядя только на схему.
Запрос A — «самый очевидный»: сумма заказов, созданных в сентябре. Зерно — заказ.
SELECT SUM(amount)
FROM orders
WHERE created_at >= '2026-09-01'
AND created_at < '2026-10-01';Запрос B — «аккуратный». Агент решает: раз показатель связан с оплатой, нужно присоединить payments, а отменённые заказы отбросить.
SELECT SUM(o.amount)
FROM orders o
JOIN payments p ON p.order_id = o.order_id
WHERE o.status = 'completed'
AND o.created_at >= '2026-09-01'
AND o.created_at < '2026-10-01';Запрос C — другая бизнес‑договорённость: чистые поступления денег. Деньги, пришедшие в сентябре, минус деньги, возвращённые в сентябре.
Я специально не называю это универсальной «выручкой»: в другой компании под выручкой может пониматься сумма отгрузок без НДС — в зависимости от принятой методологии учёта.
SELECT
COALESCE((SELECT SUM(amount) FROM payments
WHERE paid_at >= '2026-09-01' AND paid_at < '2026-10-01'), 0)
-
COALESCE((SELECT SUM(amount) FROM refunds
WHERE refunded_at >= '2026-09-01' AND refunded_at < '2026-10-01'), 0)
AS net_cash_collected;Результаты на одних и тех же данных:
Запрос | Что считает | Результат | Отклонение от C |
|---|---|---|---|
A | все заказы сентября по дате создания | 4 465 500 | +23,6% |
B | завершённые заказы, соединённые с платежами | 4 623 500 | +28,0% |
C | чистые поступления по дате движения денег | 3 613 500 | — |
Самое показательное — запрос B.
Он выглядит строже A: отменённые заказы отфильтрованы, а результат получился больше. Проблем у него две, и обе независимы.
Первая — соединение. У
paymentsдругое зерно: одна строка — один платёж. Само размножение строк ещё не ошибка, ошибка в том, что после него суммируетсяo.amount— мера уровня заказа. Заказ на 20 000, оплаченный двумя платежами по 10 000, после join становится двумя строками, иSUM(o.amount)даёт 40 000. В моих данных так задвоились 138 сентябрьских заказов.Вторая — время. Запрос задуман «про оплаты», а фильтр стоит по дате заказа: 44 заказа, созданные в сентябре, оплачены уже в октябре и всё равно попали в сумму.
Оба счётчика считает diagnostics.sql в репозитории.
Все три запроса синтаксически корректны и выполняются без ошибок. Различаются они не SQL, а семантикой: какое событие считается основанием для показателя, какое зерно допустимо и какие операции входят в расчёт. Ничего этого в схеме нет.
Пять наблюдений
Первое: модель промахивается не в синтаксисе, а в смысле. Проверкой «запрос отработал» такую ошибку не поймать, её видно только при сверке с эталоном.
Второе: самые дорогие ошибки сидят в соединениях. Неявная связь один‑ко‑многим тихо размножает строки. Внешних ключей в хранилищах часто нет, и модели приходится угадывать связь по совпадению названий колонок.
Третье: словарь компании не выводится из схемы. «Сентябрь» может быть календарным или фискальным, «Москва» — городом доставки, складом или юрлицом. Модель может узнать это из документации, каталога или логов запросов, если этот источник доступен системе и попадает в её контекст.
Четвёртое: учебные бенчмарки завышают ожидания. Данные из них могли попасть в обучение, и даже современные публичные бенчмарки не воспроизводят целиком сочетание закрытых данных, корпоративного словаря и сложности рабочих запросов. Цифра из презентации вендора ничего не говорит о точности на вашем хранилище.
Пятое: контекст о данных даёт заметный прирост, но не всё. В BEAVER подсказки по схеме почти удваивают точность. Ещё в 2023 году Секеда и коллеги из data.world получили похожую картину: GPT-4 отвечала на вопросы к корпоративной SQL‑базе с точностью 16%, а через граф знаний с онтологией и маппингами — 54%. При этом структурные ошибки остаются и с подсказками.
Что помогает: вынести часть смысла в машинно‑читаемую модель
Из пятого наблюдения следует подход, который сейчас обсуждают почти все, кто строит AI‑аналитику, — семантический слой.
Это описание данных на уровне бизнеса: сущности, связи и их кардинальность, зерно таблиц, определение каждой метрики.
В такой архитектуре агент выбирает из описанных метрик и измерений, а SQL строит движок слоя по записанным правилам.
Документация dbt прямо говорит, что MetricFlow знает типы ключей сущностей и поэтому избегает fan‑out и chasm join — ровно той ошибки, на которой споткнулся запрос B.
Отдельный рыночный сигнал дают опросы.
В исследовании Futurum Research среди более чем 800 руководителей по данным 44,5% планируют увеличить расходы на семантический слой в ближайшие 24 месяца, ещё 14,4% собираются его внедрить впервые.
Вот как выглядит метрика из запроса C в актуальной спецификации dbt Semantic Layer:
models:
- name: fct_payments
description: "Одна строка — один платёж. На один заказ бывает несколько платежей."
semantic_model:
enabled: true
agg_time_dimension: paid_at
columns:
- name: payment_id
entity: {type: primary, name: payment}
- name: order_id
entity: {type: foreign, name: order}
- name: paid_at
granularity: day
dimension: {type: time}
metrics:
- name: paid
label: Поступило денег
type: simple
agg: sum
expr: amount
- name: fct_refunds
description: "Одна строка — один возврат."
semantic_model:
enabled: true
agg_time_dimension: refunded_at
columns:
- name: refund_id
entity: {type: primary, name: refund}
- name: order_id
entity: {type: foreign, name: order}
- name: refunded_at
granularity: day
dimension: {type: time}
metrics:
- name: refunded
label: Возвращено денег
type: simple
agg: sum
expr: amount
metrics:
- name: net_cash_collected
label: Чистые поступления
description: >
Деньги, поступившие за период, минус возвраты за тот же период.
Дата — дата движения денег, а не дата заказа.
type: derived
expr: paid - refunded
input_metrics:
- name: paid
- name: refundedОбратите внимание на время. У каждой простой метрики своя дата агрегации: paid считается по paid_at, refunded — по refunded_at.
При запросе за сентябрь слой режет каждую по её собственной дате, и именно это решение в запросе B никто не принимал.
В description задокументированы зерно таблицы и определение показателя, для человека и для агента, а машинная часть кардинальности задаётся через entities.
Для простоты показан расчёт общей метрики по времени: чтобы группировать её по клиенту или региону, нужные сущности и пути соединения тоже придётся описать.
Не менее важно, что должен делать агент, если термина в слое нет. Если термин не определён или допускает несколько толкований, правильное поведение агента — не угадывать, а уточнить: «Считать по поступлению денег или по отгрузке?».
Для финансовой аналитики это важнее любой точности.
На рисунке 2 показаны пути вопроса к хранилищу: напрямую через схему и через описанный слой.

Главная мысль схемы: семантический слой не делает модель умнее. Он переносит знание о кардинальности, допустимых соединениях и определениях метрик из догадки модели в описание, которое можно проверить. А там, где описания нет, честнее спросить, чем выдать число.
Ограничения: где это не сработает
Семантический слой — не серебряная пуля.
Во‑первых, он не заменяет моделирование данных. dbt строит его поверх подготовленных моделей: если исходный слой запутан, сначала придётся собрать аналитические витрины, а уже на них фиксировать смысл. И кто‑то должен поддерживать описания: без этого слой гниёт так же, как хранилище, только незаметнее.
Во‑вторых, слой закрывает повторяющиеся вопросы про описанные метрики. Если нужной метрики в слое нет, системе нужен другой источник контекста: каталог, документация, примеры запросов. Без него агент снова восстанавливает смысл наугад.
И наконец, мой эксперимент демонстрирует механику ошибки, а не оценивает её вероятность в продакшене.
Плюс 23,6% и 28,0% — свойство моих синтетических данных, а не типичная погрешность. YAML выше — иллюстрация подхода: он лежит в репозитории рядом с запросами, но компиляция через dbt там не проверяется.
Что проверить у себя за вечер
Если вы планируете пилот AI‑аналитики или уже его ведёте, я бы начал не с выбора модели, а с такой проверки:
Выпишите 10–15 вопросов, которые бизнес задаёт чаще всего, и для каждого — эталонный SQL, согласованный с владельцем метрики.
Прогоните вопросы через агента и сравните результат, затем SQL и объяснение: какие метрики, таблицы, фильтры и даты он взял. Совпадение числа — необходимый сигнал, но не доказательство: две разные ошибки могут случайно дать одно число.
Для каждой таблицы, которая участвует в ответах, запишите зерно: одна строка — это заказ, платёж или позиция заказа.
Составьте словарь терминов компании: что такое выручка, активный клиент, регион, месяц.
Опишите пять самых спрашиваемых метрик в семантическом слое и повторите шаг 2.
Задайте агенту вопрос с термином, которого нет в словаре, и посмотрите: он переспросит или уверенно ответит.
Для разминки подойдёт и мой репозиторий: три таблицы и три вопроса, на которых видно, ошибётся ли ваш агент на соединении и на дате.
Мой вариант, который я обычно советую: шаги 1 и 2 сделать до разговора с вендором. Тогда на демо вы проверяете не красивый график, а свои пятнадцать вопросов.
Вывод
AI‑агенты сделали SQL дешёвым. Дорогим осталось другое — договориться о том, что значат данные, и записать это так, чтобы прочитала машина. Enterprise text‑to‑SQL — не один шаг генерации SQL, а цепочка разрешения контекста: какие данные нужны, как они связаны, что значат термины, по какой дате считать и какую структуру запроса построить. Часть этого контекста традиционно живёт в голове аналитика.
Поэтому, как мне кажется, роль аналитика с приходом агентов не уменьшается, а смещается: от написания каждого запроса к формализации смысла, на котором эти запросы строятся.
А как у вас: пробовали подключать AI‑агента к рабочему хранилищу? На чём он ошибался чаще всего — на соединениях, на терминах или на датах? Буду рад обсудить в комментариях.
Ссылка на мой репозиторий с экспериментом.
ИСТОЧНИКИ

Как интегрировать ИИ‑агентов с корпоративными системами и оценивать качество их работы?
Эти вопросы разберём на бесплатных демо-уроках OTUS:
20 октября, 20:00. «Tool Calling и MCP: интеграция ИИ‑агентов с корпоративными системами». Записаться.
27 октября, 18:00. «Langfuse в AgentOps: наблюдаемость и оценка LLM‑агентов». Записаться.
Больше тем об искусственном интеллекте, разработке и работе с данными — в октябьском дайджесте.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.