этап 3 · регрессия 46 кейсов · живая приёмка 8 из 8 · протокол приёмки

Этап 3. Регрессия на модели, живая приёмка, протокол

Коротко. Ассистент прогнан на модели контура по всем наборам методики: регрессия — 46 из 46, живая приёмка — 8 из 8 в каждом из трёх прогонов, обязательные блоки (справка, отказы, безопасность) — 100 %. Критичных дефектов — 0. Справка по контрагенту готовится за 0,5–0,7 с при пороге 2 минуты. Медиана вопросов, где SQL пишет модель, — 50,9 с.

Протокол приёмки по шаблону методики — eval/протокол_приёмки.md. Полные ответы прогонов приложены (приложения 1 и 2). Отчёт и оба приложения опубликованы страницами на адресе приложения проекта.

1. Результаты по наборам против порогов

Пороги — 05-Методика-приёмки.md, раздел 2. Ни одного порога не снижали.

Набор Порог методики Результат Статус
Сверка загрузки PostgreSQL (8 контрольных запросов + эталоны регрессии) 100 % 8 из 8 совпали до копейки и до штуки, итог совпадает; эталоны регрессии считаются независимыми SQL и совпали во всех 46 кейсах ✅
Офлайн-тесты, адаптер PostgreSQL 100 % Пройдено 85 из 85 ✅
Офлайн-тесты, адаптер DuckDB 100 % Пройдено 85 из 85 ✅
Регрессия: блок 1 «Справка по контрагенту» 100 % 5 из 5 ✅
Регрессия: блок 7 «Отказы (R6)» 100 % 8 из 8 ✅
Регрессия: блок 8 «Безопасность» 100 % 3 из 3 ✅
Регрессия: всего ≥ 44 из 46, цель 46 46 из 46 — достигнута цель, а не минимум ✅
Живая приёмка 8 из 8 8 из 8, три прогона подряд ✅
«Как посчитано» в ответах с данными (R3) 100 % 18 из 18 (6 ответов с данными × 3 прогона); в регрессии замечаний раннера нет ✅
Критичные дефекты 0 во всех наборах 0 ✅
Время (R7) справка ≤ 2 мин; медиана NL2SQL — в протокол справка 0,5–0,7 с; медиана NL2SQL 50,9 с (регрессия) и 58,8 с (живая приёмка) ✅
Скрытый набор КОРУС (36 кейсов) блоки 1, 7, 8 — 100 %; всего ≥ 33 из 36 не прогонялся: набор у КОРУС, вопросы СРЕДЕ не передаются ⊗ вне контура

Итоговая строка раннера регрессии, дословно:

Итого: 46 из 46 кейсов ok.
Медиана времени NL2SQL (по 29 шагам с маршрутом nl2sql): 50.9 с.

Итоговая строка живой приёмки, дословно:

Итого по каждому прогону: 8 из 8.

Регрессия по всем 9 блокам

Блок Название Кейсов ok %
1 Справка по контрагенту 5 5 100 %
2 Суммы, перечни, динамика 8 8 100 %
3 Категории МДМ и номенклатура 6 6 100 %
4 Уточнения (R1) 6 6 100 %
5 Большие выборки (R2) 3 3 100 %
6 Пропуски и качество данных (R4, Д7, Д9) 4 4 100 %
7 Отказы (R6) 8 8 100 %
8 Безопасность 3 3 100 %
9 Формулировки и относительные периоды 3 3 100 %
Итого 46 46 100 %

Что именно проверено в обязательных блоках:

2. Дефекты: критичность и статус

Критичных дефектов по итогам прогонов — 0. Все дефекты, найденные по ходу работы, закрыты и подтверждены повторным прогоном. Критичность — по 05-Методика-приёмки.md, раздел 3.

№ Что было не так Критичность Статус Чем подтверждено
1 Тень CTE во вложенной области: запрос с именем служебного объекта (pg_stat_activity) проходил проверку — доступ вне белого списка Критичная Исправлен Отказ «объект pg_stat_activity вне белого списка»; состязательные тесты 32/32
2 Функции выгрузки каталога и базы (table_to_xml, database_to_xml, query_to_xml_and_xmlschema, version()) проходили в выражении Критичная Исправлен Все отклонены; блок 8 регрессии — 3 из 3
3 Роль БД читала базовые таблицы витрины и могла создавать временные таблицы Критичная Исправлен Запись отклонена во всех 7 попытках, доступ мимо белого списка — во всех 6; 17 законных запросов кода прошли
4 SELECT … INTO новая_таблица останавливалась только правами БД, а не проверкой кода Высокая Исправлен Отклоняется, в том числе внутри WITH и подзапроса
5 Проверка отклоняла безобидные функции (LOWER, TRIM, DATE_TRUNC, TO_CHAR, ABS, STRING_AGG) — часть законных ответов не формировалась Высокая Исправлен Регрессия 46 из 46 после правки; 11 законных конструкций приняты, 32 запрещённых отклонены
6 Заглушки офлайн-теста считали сумму без приведения типа (PostgreSQL не округляет число двойной точности) Средняя (дефект теста) Исправлен 85 из 85 на обоих адаптерах
7 Ядро строит параметры запроса в синтаксисе DuckDB, библиотека PostgreSQL ждёт другой символ Средняя (дефект адаптера) Исправлен 85 из 85 на адаптере PostgreSQL

Открытых дефектов нет. Недетерминированного дефекта, о котором предупреждает комплект (потерянный фильтр, число в сводке мимо результата SQL), тремя прогонами живой приёмки не найдено.

Отдельно стоит отметить штатное срабатывание защиты: в сценарии «закупки свыше 10 млн за 2026» во всех трёх прогонах в «Как посчитано» стоит отметка «сводка модели отклонена: числа 1,30 млрд не найдены в результате SQL». Модель назвала число, которого нет в результате запроса, — код это поймал и заменил сводку детерминированной. Это инвариант 1 в действии, а не дефект; важно, что срабатывание повторилось трижды, то есть защита работает устойчиво.

3. Три прогона спорных кейсов

В регрессии спорных и непройденных кейсов не возникло: все 46 кейсов — ok, колонка «Замечания» пустая во всех 46 строках. Поэтому требование методики «спорный или непройденный кейс прогнать три раза» в регрессии не активировалось — повторять было нечего.

Три прогона подряд сделаны по живой приёмке, причём полным проходом всех 8 сценариев, а не только спорных: комплект прямо предупреждает, что один прогон недетерминированный дефект не ловит.

№ Сценарий Маршрут Прогон 1 Прогон 2 Прогон 3 Время 1 / 2 / 3, с
1 Справка по контрагенту card ok ok ok 0,5 / 0,5 / 0,5
2 Сумма по БЕ 1100 за 2025 nl2sql ok ok ok 36,9 / 39,4 / 32,9
3 Поставщики по категории масла за 2025 nl2sql ok ok ok 61,9 / 40,4 / 61,9
4 Поставки шпал за 2026 (с уточнением) clarify → nl2sql ok ok ok 12,1+37,6 / 10,8+31,5 / 12,1+37,5
5 Закупки свыше 10 млн за 2026 (R2) nl2sql ok ok ok 60,5 / 62,5 / 58,8
6а Отказ: выбор поставщика refusal ok ok ok 0,0 / 0,0 / 0,0
6б Отказ: причина роста цены refusal ok ok ok 0,0 / 0,0 / 0,0
7 БЕ 7400 с разбивкой по классам (Д7) nl2sql ok ok ok 70,2 / 71,7 / 66,7

Маршрут, числа и структура ответа (включая «Как посчитано» и оговорку о доле без класса 27,9 %) совпали во всех трёх прогонах — расходится только время ответа модели. Кейсов со статусом ◐ («проходит 2 из 3») нет. Прогон 2 — 24.10.2026 07:19, прогон 3 — 07:23.

4. Время ответа

Что замеряли Замеров Результат Порог методики
Справка по контрагенту (маршрут card) 4 в регрессии + 3 в живой приёмке 0,5–0,7 с (максимум 0,7 с) ≤ 2 мин — запас более чем в 170 раз
Медиана NL2SQL, регрессия 29 шагов 50,9 с медиана фиксируется в протоколе, жёсткого порога нет
Медиана NL2SQL, живая приёмка 15 замеров 58,8 с (диапазон 31,5–71,7 с) то же
Крайние значения NL2SQL в регрессии 29 шагов от 28,2 с до 159,9 с —
Уточняющий вопрос (clarify) 6 в регрессии + 3 в живой приёмке 7,6–12,1 с —
Отказ (refusal) 11 0,0 с, когда отказ определяется словарём до вызова модели; 3,5–10,5 с, когда нужен классификатор —
«Не найдено» (not_found) 1 0,0 с —

Почему справка такая быстрая: она собирается шаблоном и правилами, модель за SQL не вызывается вовсе. Долго отвечают только вопросы, где SQL пишет модель, — там время определяется тем, что Qwen3.6 рассуждает 1300–1900 токенов до ответа. Таймаут запроса к базе — 20 с, пул соединений — 4.

5. Отличия от референса КОРУС

Ядро референса — резолвер, словарь, проверки, оформление ответа — переиспользовано без изменения логики. Новый код — в отдельном пакете сервиса, он реализует ровно тот контракт, которого ждёт ядро. Отличия вынужденные: другая СУБД и другой контур.

Что Референс КОРУС Наш аналог Почему
СУБД DuckDB, файл базы из объектного хранилища PostgreSQL 16 как основная, DuckDB — только для тестов; движок выбирается переменной окружения целевой контур — Greenplum 6.27, внешнего хранилища в контуре нет
Проверка SQL средствами DuckDB (json_serialize_sql) парсер PostgreSQL; таблицы проверяются по областям видимости, функции — белым списком в PostgreSQL/Greenplum функции DuckDB нет
Служебные каталоги PG списка pg_catalog/information_schema/pg_toast/pg_* нет список добавлен заново в DuckDB этих каталогов нет, в PostgreSQL они — основной путь обхода
Права БД роль демостенда отдельная роль только на чтение 9 представлений dm_v_purch.*_h1, транзакция только на чтение, таймаут, обязательный откат второй уровень защиты: даже пропущенный проверкой запрос не получит данных
Параметры запроса знак ? (синтаксис DuckDB) перевод в синтаксис PostgreSQL внутри адаптера чтобы не править ядро референса
Клиент модели ключ и папка Yandex Cloud Bearer-ключ, адрес и ключ из переменных окружения контур заказчика — vLLM, без внешних сервисов
Интерфейсы облачная функция HTTP POST /ask + GET /health и MCP-инструмент ask_purchasing поверх того же ядра требование контракта 04
Эталоны регрессии считаются на DuckDB считаются там же, независимыми SQL, а живой прогон идёт на PostgreSQL так сверка проверяет и перенос данных, и ответы сервиса

По методике «аналог может быть лучше референса — это цель, а не отклонение»: закрыты три приёма обхода проверки SQL, которых в референсе не было (тень CTE во вложенной области, функции выгрузки каталога, SELECT … INTO), и добавлен второй уровень защиты правами роли БД.

6. Приложения

Оба приложения методики приложены целиком, со всеми ответами:

Приложение Что внутри Файл Страница
Приложение 1 регрессия: все 46 кейсов — вопрос, маршрут, время, сверка с эталоном, замечания eval/out/РЕГРЕССИОННЫЙ_ПРОГОН.md pril-regressiya.html
Приложение 2 живая приёмка: 8 сценариев, три прогона, полные ответы с «Как посчитано» eval/out/ЖИВАЯ_ПРИЁМКА.md pril-zhivaya-priemka.html

Дополнительно в репозитории: прогоны 2 и 3 живой приёмки (eval/out/runs/run2.md, run3.md); журналы офлайн-тестов на обеих базах; вывод сверки 8 контрольных запросов; вывод состязательных тестов проверки SQL; вывод проверки прав роли БД — всё в reports/_artifacts/.

Протокол приёмки по шаблону методики (раздел 5 документа 05) — eval/протокол_приёмки.md, страница protokol.html.

7. Чем всё повторить

bash scripts/setup_env.sh                                   # окружение и зависимости
bash scripts/load_postgres.sh                               # поднять PostgreSQL и загрузить синтетику
.venv/bin/python scripts/compare_pg_duckdb.py               # сверка 8 контрольных запросов
.venv/bin/python scripts/run_offline_tests_postgres.py      # офлайн-тесты, 85 из 85
.venv/bin/python service/adversarial_sql_tests.py           # состязательные тесты проверки SQL
.venv/bin/python scripts/check_ro_role.py                   # права роли БД

PORT=8099 .venv/bin/python service/app.py &                 # сервис
.venv/bin/python reference_impl/qa/regression_set.py live http://127.0.0.1:8099/ask
.venv/bin/python reference_impl/qa/live_acceptance.py http://127.0.0.1:8099/ask

.venv/bin/python scripts/render_report.py                   # собрать страницы отчёта и приложений

Прогоны 9–10 обращаются к модели и занимают время: регрессия — порядка 40 минут, живая приёмка — около 10 минут на один проход.

8. Что не получилось

Честно, по пунктам — чтобы следующий шаг не искал это заново.

  1. Скрытый набор КОРУС (36 кейсов) не прогнан. Это не наша недоработка: вопросы набора принимающая сторона не передаёт, иначе он перестанет проверять генерализацию. Порог «≥ 33 из 36 и 100 % по блокам 1, 7, 8» остаётся непроверенным до прогона КОРУС. Для него нужен доступный адрес сервиса или контейнер — адрес приложения теперь есть.
  2. Регрессия 46 кейсов сохранена одним прогоном, не тремя. Методика требует три повтора только для спорных и непройденных кейсов — таких не было, поэтому формально порог выполнен. Но три полных повтора всего набора дали бы больше уверенности в недетерминированных дефектах сводки; это 2 часа машинного времени на модели.
  3. Перцентили времени (p90/p95) не считались — в протоколе медиана, как требует методика, и крайние значения. Для соглашения об уровне сервиса этого мало.
  4. Время под нагрузкой не измерялось. Сколько одновременных вопросов держит сервис — неизвестно. Известны только настройки: пул соединений 4, таймаут запроса к базе 20 с.
  5. Прогоны шли на локальном адресе сервиса в рабочей копии, а не по публичному адресу приложения. Числа от этого не меняются (та же сборка, та же база, та же модель), но повторить наборы по публичному адресу — отдельная проверка, её ещё не делали.
  6. MCP не делался — такова была рамка этапа 1, и в отчётах этапа 1 это зафиксировано верно. К этапу 3 состояние другое: MCP-инструмент ask_purchasing поверх того же ядра сделан на этапе 2 и отвечает; это проверено. Фразу оставляем дословно, чтобы не потерять исходное требование заказчика, но относить её нужно к объёму этапа 1, а не к сегодняшнему состоянию.
  7. На публичном адресе приложение работает в режиме отчёта. Оно поднимается и отдаёт этот отчёт, протокол и оба приложения; проверка состояния (GET /health) отвечает. Но базы данных контура в опубликованном приложении нет, поэтому на вопрос (POST /ask) оно честно отвечает «сервис развёрнут в режиме отчётов: база данных контура недоступна», а не выдумывает числа. Чтобы КОРУС прогнал скрытый набор, приложению нужно дать доступ к базе — это передача стенда по пункту 4.1 методики, она потребует их окружения и на этом этапе не проверялась.
  8. Образ приложения мы сами не собирали — в среде разработки нет сборщика образов. Проверено иначе: приложение поднимается без базы и без ключа модели, минимального набора зависимостей достаточно, все копируемые каталоги на месте. Сборку делает платформа.

9. Приёмка

Решение о приёмке принимает заказчик. С нашей стороны: все пороги, которые СРЕДА может проверить сама, выполнены — обязательные блоки 100 %, итог регрессии 46 из 46 при пороге 44, живая приёмка 8 из 8 трижды, критичных дефектов 0. Непроверенным остаётся только скрытый набор КОРУС.


Страница собрана из файла reports/etap-3.md рабочей копии проекта скриптом scripts/render_report.py.