Август 2026 AI-аналитик 12 мин

Продакт задал двадцать вопросов AI-аналитику

Что он унесёт неверного, откуда это берётся и что с этим делать


Я спросил у системы, какой у нас средний чек.

Она ответила: 353 рубля 40 копеек. Правильный ответ — 360.18.

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

И этих двух процентов ровно хватает, чтобы решение перевернулось. По моему же правилу чек ниже 360 значит «идём разбираться», выше — «наблюдаем». То есть я бы пошёл разбираться с проседанием, которого нет, и не получил бы ни единого повода усомниться.

Дальше — как это вышло, ещё два таких же случая и что с ними делать.


Зачем всё это

Продакты хотят обходиться без аналитиков. Спросить систему напрямую, получить число, принять решение. Вендоры говорят, что уже можно. Аналитики говорят, что нельзя. Данных нет ни у кого.

И не появляются они по скучной технической причине: на реальных данных никто не знает правильного ответа. Система говорит «выручка за март — 12.4 миллиона», и проверить это можно ровно одним способом — написав тот же запрос руками. А если бы кто-то хотел писать руками, система была бы не нужна.

Поэтому мир пришлось построить. Синтетический ритейл на миллион позиций заказов за два года, где заранее известно всё: какая заложена сезонность, сколько дублей от повторных нажатий «оплатить», через сколько дней приходит возврат. Устройство сборки и семнадцать встроенных в неё проверок я разбирал отдельно — Как я строил AI-аналитика. Здесь важно одно: на каждый вопрос есть эталон, посчитанный независимо от агента.

Двадцать вопросов — те, что продакт задаёт на самом деле. Сколько заработали в марте. Выросла ли выручка год к году. Какие категории тянут больше всего. Стоит ли расширять промо. Половина ложится на определённые метрики, половина нет — в жизни ровно так и бывает.

Пороги я объявил до прогона, чтобы потом не подгонять: продакты правы и аналитик не нужен, если система отвечает не меньше чем на 80% вопросов, а доля тихо-неверных — тех, где число неверно, а на глаз не отличить — не выше 5%.

И записал прогноз, тоже заранее: ответит почти на всё, ответы будут убедительными, тихо-неверных выйдет процентов двадцать.


Из чего сделан мир

Чтобы дальнейшее было предметным — вот что там внутри.

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

Таблица Строк Что внутри
order_items1 053 969позиция заказа: SKU, количество, цена, скидка
orders~425 000заказ: клиент, магазин, статус, время
returns92 525возврат: заказ, SKU, количество, дата
customers60 000клиент: канал, дата регистрации
products2 200SKU, категория, себестоимость, прайс
promotions1 400акция: SKU, период, глубина скидки

Два года истории, восемь категорий, двадцать четыре магазина.

Грязь заложена намеренно — десять видов. Не для правдоподобия: каждый вид даёт честный способ посчитать одно и то же двумя способами.

Вид Сколько Что двоится
дубли от повторной оплаты2.0%, 20 929 строксчитать выручку с дедупликацией или без
отменённые заказы в общей таблице6.0%фильтровать или нет
гостевые заказы без клиента11.9%, 50 578сколько у нас клиентов
возврат приходит через 3–21 деньдоля возвратов в свежем периоде
смешанные часовые пояса6 магазинов из 24что значит «за 1 марта»
цена выше прайса1.0%, 10 264считать ли это продажей
правый цензор выгрузкидекабрь выглядит лучше, чем есть
время регистрации событиячто мы знали на дату X
асимметрия флагов между витринамипородила ошибку из этой статьи
шкала времени выгрузки«данные на 1 марта» — это про какой март

Слои над данными:

Слой Объектов
сырые таблицы6
модели dbt (staging + marts)9
тесты витрин35
определения метрик3
знание вне данных (store_timezones.csv)1 файл
проверки мира39
уровни контекста для агента6
вопросы с эталонами20

Три метрики — выручка, активные клиенты, доля возвратов. Каждая с явно объявленными решениями: окно, что считается активностью, как обходятся гостевые, отсечка по времени регистрации.

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


Что там под капотом

Теперь про то, кто это всё обрабатывает.

Что под капотом: один агент и шесть слоёв кода вокруг него Вопрос продакта Отбор контекста схема · таблицы · метрики Аналитик — LLM выбирает метрику, пишет SQL единственный, кто «думает» Петля исполнения упал → чинить, до 3 раз Ответ число + SQL рядом ПРОВЕРКИ — весь код, ни одного вызова модели Пересчёт мира 39 проверок Тесты витрин 35 тестов Белый список решений что агент вправе выбрать Спроси трижды воспроизводимость Разложение сверка составляющих Эталон — считается независимо, по сырым данным Не через метрики и не через витрины: у правды и проверяемого не должно быть общего родителя. Агент эталона не видит никогда. Красным — то, что связано с находками статьи: агент как единственный источник суждения и проверка, которая поймала ошибку.
Что под капотом

Агент здесь один. Он получает вопрос и контекст, выбирает метрику или пишет SQL сам, исполняет запрос. Всё остальное — код: отбор контекста, петля починки упавших запросов, проверки, подсчёт эталона.

Это не упущение, а правило. Модель там, где нужно суждение; код там, где нужна одинаковость. Каждый «умный» слой добавляет источник произвола ровно туда, где нужна воспроизводимость.

Роль Чем Что делает
АналитикLLMвыбирает метрику, пишет SQL, отвечает
Отбор контекстакодрешает, какие таблицы и определения показать
Петля исполнениякодзапрос упал → ошибка обратно модели, до трёх попыток
Белый списоккодсверяет выбранные решения с допустимыми
Проверкикод39 на данные, 35 на витрины, плюс поверх
Эталонкодсчитает верный ответ по сырым данным

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


Раунд первый: он не врёт, он переспрашивает

Из двадцати вопросов агент дал число на шести. На одиннадцати попросил уточнить. На трёх отказался.

Покрытие — 40% при пороге 80. Тихо-неверных — ноль, но это ноль от нуля: там, где он всё-таки посчитал, он совпал с эталоном до последнего знака.

Уточнения оказались не отговорками. На «сколько мы заработали в марте» он спрашивает: за какой год, считать выручку до вычета возвратов или после, и относить возврат к дате возврата или к дате заказа. Хороший аналитик спросил бы ровно это.

Так что продакт, задавший двадцать вопросов, унёс бы не двадцать неверных чисел, а одиннадцать встречных вопросов. И отвечать на них некому — аналитика-то нет.

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

Мой прогноз не сбылся зеркально. Я ждал, что наврёт убедительно; он переспросил по делу.


Раунд второй: я отвечаю

Сел и ответил на одиннадцать уточнений так, как ответил бы аналитик. Март 2025. После возвратов. По дате возврата. Только известные клиенты. Окно 90 дней.

Покрытие стало девять из девяти. Разброс между тремя повторами — ноль в восьми случаях из девяти.

Раунд 1 агент переспрашивает Раунд 2 ответил на уточнения 0 50% 100% 40% 100% 0% 20% порог покрытия 80% покрытие доля тихо-неверных Полное покрытие покупается тихо-неверным
Полное покрытие покупается тихо-неверным

Заодно выяснилось, что два «безответных» вопроса были просто недоспрошенными: как только появилось определение, ответ нашёлся. Дело было не в данных, а в постановке.

И вот тут появилось первое настоящее тихо-неверное.


Механизм первый: фильтр забыли в одной половине формулы

Тот самый средний чек. Разложим ответ на составляющие:

заказов выручка валовая вычтено возвратов средний чек сошлось сошлось +17.3% -1.9% Три составляющие из четырёх верны до копейки
Три составляющие из четырёх верны до копейки
эталон агент
заказов31 91331 913сошлось
выручка валовая12 744 925.7412 744 925.74до копейки
вычтено возвратов1 250 412.881 466 746.66+17.3%
средний чек360.18353.40−1.9%

Три величины из четырёх верны. Расходится одна.

Я просил считать только известных клиентов. Агент честно исключил гостевые заказы из числителя — и не исключил из вычитаемого. Возвраты гостей вычлись из выручки, в которой гостей нет. Гостевые дают около 13% стоимости возвратов, переплата того же порядка.

Обычная ошибка в SQL, такую делает и человек. Но интересно, почему она случилась именно здесь.

В витрине заказов лежит готовый флаг «гостевой». В витрине возвратов его нет — гостевой возврат опознаётся по пустому идентификатору клиента, и догадаться до этого надо самому. Агент отфильтровал там, где было имя, и не отфильтровал там, где имени не оказалось.

Я проверил, не подсказал ли ему это контекст. Во всём, что ему подали — а это шесть с половиной тысяч символов — ни одного примера SQL. Копировать было неоткуда.


Механизм второй: таблицу просто не показали

В сборке есть слой, который отбирает таблицы по вопросу и кладёт в промпт только нужные. Экономит около тысячи токенов, и я долго считал, что даром.

В вопросах про выручку и про категории нет слова «возврат». Значит таблица возвратов в контекст не попала. И агент написал: возвратов в схеме нет, поэтому считаю валовую выручку.

Он не соврал. Он описал то, что ему показали.

с возвратами без возвратов
рост выручки год к году19.75%21.02%
доля лидера категорий47.4%46.8%
второе местоhomeapparel

Расхождение доли — чуть больше процента, глазом не поймать. Решение при этом не меняется. Меняется ранг. Продакт, спросивший «куда вкладываться», унесёт apparel вместо home и не заподозрит ничего.

Оговорка к таблице: это контрфакт, а не ответы агента. Окно в обеих колонках зафиксировано, различается только основа — так цена подмены определения отделена от выбора периода. Сам агент на вопрос про год к году ответил «рост 163%»: он взял скользящие 365 дней, и «предыдущие 365» у него ушли в 2023 год, которого в данных нет. Об этом — в разборе ниже.


Механизм третий: развилка приехала как утверждение

Этот хуже двух предыдущих, потому что на него нельзя ответить.

На вопрос про долю выручки топ-10% клиентов агент задал уточнение — про окно расчёта. А внутри того же уточнения, между делом, утвердил: возвраты в схеме не представлены отдельной сущностью, поэтому считаю валовую.

Я ответил про окно. То есть ровно на то, о чём спросили. Подмена определения осталась на месте.

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

Чем этот класс опасен, помимо прочего: он выглядит как компетентность. Техническое обоснование читается убедительнее, чем «не знаю».


Кто, собственно, виноват

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

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

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

Сноска, а не четвёртый механизм. Пока я верстал эту статью, встроенный SVG от matplotlib принёс внутри <defs> блок <style> с селектором *. В отдельном файле он безобиден; встроенный в страницу — это правило на всю страницу, и оно переопределило бы отрисовку линий везде. Тот же класс, случившийся при вёрстке текста про этот класс.


Почему семнадцать проверок молчали

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

И вот главное, чего я не ожидал: по итоговому числу не видна ни одна из трёх.

ошибкаответ агентаэталончто сделал автоматический подсчёт
подмена основы, рост выручкирост 163%19.75%«несравнимо»: сравнил 79 412 272 с 19.75
подмена основы, доля категорий46.84%47.4%«ответ не число»: сравнил «electronics» с 47.4
фильтр в половине формулы353.40360.18поймал — пометил тихо-неверным

Из трёх ошибок сверка результата поймала одну. Две другие она не отвергла — она их не смогла сравнить и промолчала: ответы не привелись к одному виду с эталоном, и подсчёт положил их в клетку «несравнимо». Там, где сравнение всё-таки состоялось, расхождения были невидимы: 1.2% у доли категорий и 1.9% у среднего чека при пороге видимости 5%.

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

Главная проверка на воспроизводимость — задать вопрос трижды и сравнить ответы. За проект она поймала больше всех остальных. И вот её граница:

Проверка на повторах ловит недетерминированность, а не неправильность.

Средний чек воспроизвёлся трижды с разбросом 1.8%. Это худший разброс в наборе — остальные восемь вопросов дали ровный ноль. То есть единственный вопрос, который подсчёт пометил тихо-неверным, показал наихудшую стабильность в наборе — и она всё равно выглядела прекрасно.

Три согласованных числа означали не надёжность, а то, что модель воспроизводимо пишет один и тот же неверный джойн. Устойчивость, которой я радовался абзацем выше, работала против меня.

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

Это стало семнадцатой проверкой в наборе. Против детерминированной ошибки повторы бессильны, а разложение работает.

И тут же нашлась восемнадцатая. Считая деньги, я обнаружил, что запись в лог происходит в самом конце обработки вызова — а обрыв по потолку случается раньше и лога не оставляет вовсе. То есть суммирование логов систематически занижает счёт ровно на самых дорогих вызовах: тех, где модель сгенерировала весь потолок и всё выбросила.

Тот же класс, что и предыдущие: проверка не видит именно то, что должна была бы видеть в первую очередь.

И ещё одно, уже про меня. Чтобы разложить оставшиеся ответы, я расширил само разложение — и первая версия расширения сравнила промо-когорту с не-промо: ответ там в двух строках, а извлечение брало первую. Получилось «расхождение в 26 раз», которого нет. Это шестая по счёту починка измерительного инструмента в проекте, и все шесть ошибались в одну сторону — в сторону более эффектного результата. Числа, подтверждающие ожидание, не вызывают желания проверить; числа, ему противоречащие, проверяются немедленно.


Разбор пятнадцати добавил к трём механизмам ещё две мелочи, обе того же класса. Первая: агент утверждает, что в справочнике клиентов гостей нет по построению, — а их там 15 011. Вывод он делает правильный (гостевой заказ не привязать к человеку), но из неверного утверждения о схеме, и проверить это утверждение продакту нечем. На числа оно не повлияло ни разу.

Вторая — наоборот, в пользу агента. В вопросе про долю возвратов он применил обрезку окна на дозревание, объявленную в определении метрики значением по умолчанию, а эталон её сознательно не применил: декабрь дозрел целиком, обрезка не нужна. На числе это не сказалось. Но формально от объявленного отклонился эталон, а не агент — единственный такой случай из пятнадцати.


Побочный результат: правда перестала быть одна

Пока продакт молчит, эталон единственный — потому что все развилки закрыл аналитик, когда писал определения метрик.

Как только продакт начинает отвечать на уточнения, его ответ становится таким же решением. Мои ответы разошлись с эталоном в четырёх вопросах — по пяти развилкам:

я выбрал эталон
привязка возвратапо дате возвратапо когорте заказа
форма сравненияпомесячнополугодие к полугодию
основа для чекапосле возвратовваловая
гостевые в чекетолько известныевключены
окно для топ-10%90 днейвся история

Три из пяти меняют решение. Самая наглядная — форма сравнения: приток новых клиентов вырос на 11.31%, если сравнивать полугодие с полугодием, и на 6.91%, если считать помесячно. Порог стоит на десяти процентах, и ответ переезжает с «растём» на «наблюдать». Я выбрал разрез, а не базу сравнения, и не заметил, что выбрал вместе с ней.

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

Цену диалога я собирался мерить в токенах и кругах уточнений. Она оказалась не там: после диалога единственной правды больше нет.


Что с этим делать

Каждая починка следует из механизма, а не из общих соображений.

Отбирать таблицы по определению метрики, а не по словам вопроса. Если в определении выручки написано «после возвратов», таблица возвратов обязана попасть в контекст — даже когда в вопросе этого слова нет.

Дать агенту знать, что контекст отобран. Сейчас он не может отличить «в схеме этого нет» от «мне этого не показали»: видит поданное и честно описывает как полную картину. Достаточно сообщать ему сам факт отбора.

Сверять составляющие, а не итог. Разложение ловит то, против чего бессильны повторы, и стоит ноль дополнительных вызовов — считается по уже сохранённым запросам.

Записывать ответы продакта в семантический слой. Он сказал «март 2025, после возвратов» — это решение, и жить оно должно рядом с определением метрики, а не в переписке. Иначе следующий прогон выберет другие умолчания, и сравнивать будет не с чем.


Сколько это стоит

Раз уж всё меряли — посчитал и деньги.

Один ответ обходится в три цента (медиана по 33 прогонам, разброс от цента до восьми). Весь эксперимент — оба раунда, все прогоны, включая упавшие и переснятые — 4 доллара 47 центов.

Если продакт задаёт двадцать вопросов в неделю, год работы стоит 31–36 долларов. Разброс — от того, спрашивает он подряд или раз в день: кэш промпта живёт пять минут, и на редких обращениях префикс приходится записывать заново.

Только это цена ответов, а не цена доверия к ним.

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

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

Цены API объявлены в долларах, поэтому и счёт здесь в них: рублёвые цифры устаревают быстрее выводов. По курсу ЦБ на 29.08.2026 — 85.60 ₽ за доллар — это 2 рубля 54 копейки за ответ, 382 рубля за весь эксперимент и 2 600–3 100 рублей за год работы.

Два урока про саму оптимизацию

Мы экономили не на той стороне. Разложил счёт за медианный ответ:

токенов доля счёта
вход7174.8%
чтение из кэша2 0841.4%
выход2 78493.8%

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

А одна оптимизация оказалась в минус. Я снизил потолок ответа с 16 000 токенов до 4 000, рассуждая так: медианный выход в полторы тысячи, потолок вдвое выше, экономия очевидна.

Платится не разрешённое, а сгенерированное. Экономии не было вовсе — зато десять вызовов упёрлись в потолок, оборвались на полуслове и были выброшены. Минус полдоллара плюс перепрогон.

Оптимизация без замера обошлась дороже, чем не делать ничего.


Знаменатель — пятнадцать случаев: столько вопросов, где агент дал число и есть с чем сверить. «Три из пятнадцати» и «три из полутора тысяч» — очень разные утверждения, и первое означает гораздо меньше.

И три — это нижняя граница, а не потолок. У ответов первого раунда не сохранялись строки результата, только запрос и сведённое число, — поэтому разложение на части применимо лишь ко второму раунду. Восемь вопросов из пятнадцати проверены чтением SQL и сверкой чисел, но не разложением. Если там есть ошибка того же типа, что в среднем чеке, этот разбор её не увидит.

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

Три механизма описаны каждый на одном-двух случаях. Механизму не нужна статистика, чтобы существовать, но как часто он срабатывает — я не мерил.


Так может или не может

Обойтись без аналитика — нет. Но не потому, что агент врёт.

Он переспрашивает, и переспрашивает по существу. На его вопросы нужен человек, который знает, какой март имеется в виду, что делать с гостевыми и от какой даты отсчитывать окно. Отказ отвечать оказался самой надёжной защитой и самой дорогой: ноль тихо-неверных в первом раунде куплен нулевым покрытием.

А когда продакт отвечает и покрытие становится полным, тихо-неверное появляется — и приходит не оттуда, откуда его ждут. Не модель фантазирует. Код вокруг неё молча меняет определение метрики, и число выходит совершенно правдоподобным.

Откуда это растёт

null, картотека математики: Парадокс Симпсона · золотой набор

Если интересно обсудить — напишите в Telegram.

← Все статьи