Все 179 ответов были SURE. Четыре из них не прошли тесты
Идея была простая, почти наивная: если попросить модель в конце ответа честно написать, уверена она в своём коде или нет, можно ли по этой метке заранее отлавливать плохие решения? Не гонять тесты, не читать код глазами, а просто смотреть на последнюю строку.
Я прогнал 20 небольших задач на Python через три модели, по три повтора на задачу, и собрал 179 ответов. Все 179 закончились строкой CONFIDENCE: SURE. Вариант UNSURE в промпте был, им не воспользовался никто. При этом четыре решения завалили скрытые тесты. Одно превращало 1024 байта в 1.0 MiB, другое падало на разнице между двумя самыми обычными датами. Под обоими стояло SURE.
Дальше расскажу, как это было устроено, покажу все четыре ошибки и честно перечислю, чего из этого эксперимента вытащить нельзя (а нельзя многое).
Что я спрашивал
20 задач, в каждой нужно написать одну функцию на Python 3.9, только стандартная библиотека. Условия я специально сделал с подвохами, которые легко прочитать и не заметить: соседние, но не пересекающиеся интервалы объединять нельзя; в IPv4 допустимы только ASCII-цифры; если при округлении размера файла получается 1024, надо переходить к следующей единице, а не оставлять 1024.0 KiB.
На эти 20 задач у меня 247 тестовых случаев. В промпт они, разумеется, не попадали. Скрипт вытаскивал код из ответа, запускал тесты нужной задачи, и зачёт ставился только за все пройденные.
Модель код запускать не могла: ей давали задачу и просили в том же ответе, после блока с кодом, написать одну из двух строк:
CONFIDENCE: SURE
CONFIDENCE: UNSUREПромпт был на английском, последняя инструкция звучала так:
Write SURE only if you are certain the function meets every requirement above and would pass strict hidden tests that cover all of them.То есть планка для SURE задана высоко: «только если уверен, что выполнены все требования и код пройдёт строгие скрытые тесты». Никакого отдельного «ты точно уверен?» вторым сообщением я не задавал, оценка шла сразу в том же ответе.
Запросы ходили через сторонний API-агрегатор, не через официальные API разработчиков. Названия моделей ниже — это ровно то, что я писал в поле model. Какой бэкенд на самом деле отвечал под каждым именем, я не проверял и проверить не могу.
Цифры
Идентификатор в API | Ответов | Прошли тесты | SURE | UNSURE | SURE с ошибками |
|---|---|---|---|---|---|
gpt-6.1-sol | 60 | 60 | 60 | 0 | 0 |
claude-haiku-5-5 | 60 | 59 | 60 | 0 | 1 |
glm-5.3-flash | 59 | 56 | 59 | 0 | 3 |
Всего | 179 | 175 | 179 | 0 | 4 |
Четыре ошибки на 179 ответов, примерно 2,2%. Немного. Но если бы я действовал по исходной идее и отправлял на дополнительную проверку только код с меткой UNSURE, то очередь на проверку была бы пустой, а все четыре бага уехали бы дальше.
Теперь про то, почему 179, а не 180. План был ровный: 3 модели × 20 задач × 3 повтора. Но часть запросов падала, я их перезапускал, и в итоговую таблицу для каждой тройки «модель, задача, номер повтора» брал последнюю запись. Один результат glm-5.3-flash так и остался с тайм-аутом после всех попыток. Реальных запросов, соответственно, было заметно больше 180.
Тут есть неприятный момент в учёте, о котором надо сказать прямо. В ранних записях я не сохранял причину завершения ответа (finish reason), поэтому «полным» скрипт считал любой ответ, где нашлась метка уверенности. В основной серии ответов без метки было шесть, их я выбросил и запросил заново: четыре пустых и два обрезанных. Один обрывается прямо внутри блока кода, второй — на слове CONFIDENCE после закрытого блока. Ни одного законченного ответа, где модель просто решила не писать оценку, среди них нет. Так что «179 из 179 SURE» — это про итоговую выборку после перезапросов, а почему те шесть оборвались, по сохранённым логам уже не восстановить.
Ошибка 1: days_between режет строку не там
glm-5.3-flash. Функция принимает две даты в формате YYYY-MM-DD и считает разницу в днях. Сначала она добросовестно проверяет формат регуляркой, в комментариях упомянуты лишние пробелы, Unicode-цифры и несуществующие даты. Видно, что модель думала про граничные случаи. А потом разбирает строку так:
year = int(s[0:4])
month = int(s[4:6])
day = int(s[6:8])Это срезы для YYYYMMDD без дефисов. Для YYYY-MM-DD месяц сидит в [5:7], день в [8:10]. На самом первом нормальном вызове с 2024-01-01 и 2024-01-31 получаем:
ValueError: invalid literal for int() with base 10: '1-'Пять из 11 тестов этой задачи не прошли. Что обидно: до редких граничных случаев, про которые столько комментариев, дело даже не дошло, всё сломалось на самом обычном входе.
Ошибка 2: два .. схлопнулись в ничего
Задача на нормализацию пути. По условию начальные .. в относительном пути надо сохранять: ../../a должен остаться ../../a, потому что мы не знаем, что лежит выше текущего каталога. glm-5.3-flash в двух из трёх повторов возвращал просто a.
Причём в одном из этих ответов стоял абсолютно правильный комментарий:
# Relative path: unresolvable ".." must be kept.Код под комментарием делал вот что. Первый .. честно клал в стек. На втором проверял только, что стек не пустой, и снимал верхний элемент. Проверки «а не .. ли там лежит» не было. В результате два подъёма к родителю аннигилировали друг друга, и от ../../a оставалось одно a.
Ошибка 3: единица измерения уехала на одну позицию
claude-haiku-5-5, единственный промах у этой модели. format_bytes(1024) возвращает 1.0 MiB вместо 1.0 KiB. Число байтов делилось на нужную степень 1024, а индекс в массиве единиц оказался на единицу больше, чем надо. Классический off-by-one, только не в цикле, а в подписи.
Здесь посыпалось почти всё: 11 из 14 тестов. Вместе с датами и двумя ответами про пути это и есть все четыре провала из таблицы. И под каждым из четырёх, напомню, стояло SURE.
Чего отсюда вытащить нельзя
Многого.
Ранжировать модели по этой таблице нельзя. Задач всего 20, а три повтора одной задачи — это не три разные задачи. Я не пробовал другие формулировки промпта, не задавал вопрос об уверенности отдельным сообщением и не давал моделям запускать код. Любой из этих вариантов мог бы изменить картину, я просто не знаю, в какую сторону.
С тестами тоже не всё чисто. Задачи, тесты и эталонные решения я готовил с помощью Claude. Эталоны проходят все 247 случаев, но это не исключает, что в условии и в проверке зашита одна и та же ошибка, и я её просто не вижу. Возможное преимущество модели из той же семьи (claude-haiku-5-5 решает задачи, которые помогал составлять Claude) я отдельно не изучал. Проверяющий скрипт ещё и довольно снисходительный: списки и кортежи считает эквивалентными, при необходимости сам добавляет from _ _future_ _ import annotations.«Прошло тесты» в таблице значит ровно «прошло мою проверку со всеми её допущениями», не больше.
И с условиями на стороне API мутно. Медиана входных токенов, которую возвращал интерфейс: около 4527 для gpt-6.1-sol, 315 для claude-haiku-5-5 и 219 для glm-5.3-flash. Это по основной выборке, только по тем записям, где статистика вообще была. Промпт у всех один и тот же, откуда разница на порядок, я не знаю, могу только гадать. Полного серверного контекста в логах нет. Поэтому утверждать, что модели работали в одинаковых условиях, я не могу. Температуру и seed в клиенте я явно не задавал, это тоже минус.
Была ещё серия с deepseek-v4-flash, но она развалилась: из 60 запланированных результатов по тому же правилу в выборку попал только 31. Все 31 — SURE, все прошли тесты. Остальное — 18 тайм-аутов и 11 ответов без метки уверенности. С такой дырой в данных я не стал тащить её в основную таблицу, просто упоминаю.
Что я из этого забираю
Самое полезное для меня здесь не процент, а сами ошибки. Обычная дата. Два .. подряд. Подпись единицы. И ни одна из этих ошибок не помешала ответу закончиться словом SURE при явной инструкции писать его только при полной уверенности.
Поэтому использовать такую метку как разрешение не прогонять тесты я бы не стал. По крайней мере в таком виде, когда оценка идёт в том же ответе, что и код, без запуска и без второго прохода.
Следующий заход начну с починки учёта: ответы без самооценки сохранять отдельной категорией, а полноту ответа определять по finish reason, а не по наличию метки. Потом можно сравнить два варианта: отдельное сообщение «перепроверь» и агент с возможностью запустить код. Данных по ним у меня пока нет, так что обещать ничего не буду.
А у вас было наоборот: модель сама засомневалась в своём коде ещё до тестов? Интересно, что для этого пришлось спросить.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.