Довольно широко цитируемый пост утверждает, что динамические языки и/или языки, представляющие вещи более компактно, эффективнее по токенам. Пост цитируют так часто, что с этим тезисом согласны даже поисковые сводки LLM. Например, при запросе "dynamic vs static language token cost" (без кавычек) AI-сводка Google открывалась словами:
Динамически типизированные языки в целом требуют меньше токенов LLM, чем традиционные статически типизированные языки, потому что отсутствие явных объявлений типов делает код компактнее.
AI-сводка Google ссылалась на тот же пост, где утверждается, что некоторые компактные динамические языки требуют примерно в 2-3 раза меньше токенов, чем статические языки типа Rust, Go, C++ и другие. Автор поста пишет:
Разница в 2,6 раза между C (наименее эффективным по токенам языком в сравнении) и Clojure (наиболее эффективным) весьма значительна.
Позже он попробовал язык J:
Он доминирует, показывая в среднем всего 70 токенов — почти вдвое меньше, чем Clojure (109 токенов). Языки массивов могут быть крайне эффективны по токенам, если избегают экзотических наборов символов. Если токен-эффективность окажется ключевым фактором, это, возможно, интересный путь эволюции языков.
Ещё одно похожее сравнение динамических и статических языков по токенам — этот бенчмарк, поддерживающий тот же вывод. Прежде чем читать дальше, можно взглянуть на обе ссылки и подумать о проблемах методологии оценки — это своего рода часть 8 серии материалов о бенчмарках, оценках и планировании экспериментов.
Без запуска собственного эксперимента видно, что проблема первого исследования уже в тривиальности задач — это ясно из приведённой выше цитаты: задача, решаемая за 70 токенов в J и 109 в Clojure, вообще не является полноценной задачей (использовался Rosetta Code). Как показывал разбор caveman mode против собственных оценок, тривиальные задачи, где основная работа — просто вывести ответ, дают совершенно другие результаты по сравнению с чуть менее тривиальными задачами, требующими какой-то реальной работы. Заявленные преимущества caveman mode и их подтверждение в репликациях исчезают, как только рассматриваются задачи сложнее нескольких токенов. В общем случае производительность на тривиальных задачах не обобщается.
Проблемы во второй ссылке несколько тоньше, поэтому большинство из них разобраны в приложении, но среди них — тест, который обращается к неверному, несуществующему пути и падает из-за этого. Один из последующих агентов создаёт symlink несуществующего пути на собственный исполняемый файл — это решает проблему для его случая, но в результате все последующие тесты запускают именно этот исполняемый файл вместо правильного. Автор бенчмарка пытается делать выводы о том, что означают некоторые провалы у Rust, но на самом деле это означает лишь то, что оценка Rust выполнялась раньше, чем агент Go подменил через symlink исполняемый файл для этого сломанного теста.
Вместо того чтобы полагаться на эти оценки, можно провести собственные эксперименты. Как показывают эти оценки, а также оценки, разобранные в предыдущих материалах, очень легко создать эксперимент, который на самом деле не говорит того, что, как кажется его создателю, он говорит. Эти эксперименты не станут исключением и будут иметь свои недочёты (подробнее — в приложении ниже).
Для выработки интуиции полезно заранее фиксировать предположения до просмотра результатов1. Вот некоторые предположения, зафиксированные вместе с друзьями заранее:
- Высокая уверенность (95%): общий тезис "динамические против статических" не подтвердится
- По причинам, изложенным выше: это похоже на ситуацию с caveman-оценкой, где результат в лучшем случае размывается по мере роста сложности задачи
- Низкая уверенность (60%): статические языки будут немного лучше динамических при максимальном уровне усилий модели (ultra)
- Очень слабая уверенность в том, что на уровне ultra оболочка будет быстрее подавать модели обратную связь, и это даст какое-то преимущество по корректности или эффективности, но было бы вполне разумно, если бы это оказалось не так по множеству причин — например, замечено, что codex при вызове компилятора Rust очень часто совершает ровно одну и ту же ошибку, а потом её исправляет; возможно, такие вещи перекрывают эффект гипотетически более быстрого цикла обратной связи
- Высокая уверенность (98%): превосходство "странных" языков типа J не подтвердится
- Та же логика, что и с общим тезисом о статических/динамических языках, плюс дополнительная мысль о том, что лаборатории AI вкладывают гораздо меньше (а возможно и вовсе не вкладывают) усилий в создание синтетических RL-окружений для малоизвестных языков
Zstd
Для первого эксперимента агентам давали RFC zstd (плюс список известных ошибок в тексте) и просили реализовать полноценный декодер zstd (агенты работают в изолированном контейнере без доступа к интернету). Тесты агентам не показывались. Для проекта такого масштаба, как zstd, разумно не ожидать, что тесты покроют все возможные случаи. Например, несмотря на то что zstd — довольно хорошо протестированный проект, в нём как-то была найдена ошибка порчи данных. Набор тестов не предназначен для выявления экстремальных крайних случаев, которые могут скрываться годами, а призван проверить различные случаи, которые можно "легко" вывести из RFC и которые должны работать.
Ниже по оси X — стоимость, по оси Y — оценка корректности (лучше — вверх и влево, хуже — вниз и вправо); усреднённый результат для среднего (medium) и максимального (ultra) уровней усилий с GPT-5.6 Sol. Если смотреть только на medium (и игнорировать то, что результаты часто сильно различаются от задачи к задаче), можно прийти к выводу, похожему на вывод из поста Алдерсона: динамические языки эффективнее и лучше при использовании LLM, потому что (не считая относительно малоизвестных языков) кластер динамических языков оказывается выше и левее кластера статических (для наглядности использовано цветовое кодирование Алдерсона для статических/динамических языков). Но если смотреть на уровень ultra, результаты гораздо более смешанные: пара статических языков показывают лучшие результаты, и среди лучших результатов статических языков больше, чем динамических.
Графики ниже также позволяют переключить ось X со стоимости на время. В mame/ai-coding-lang-bench отмечалось, что важно получать результаты быстрее (лично автору это не кажется существенным, поскольку результаты занимают достаточно много времени, чтобы просто заняться чем-то другим, а не ждать), поэтому этот аспект тоже рассматривается. Аналогичным образом видно, что ни один тип языков не доминирует над другим, хотя при среднем уровне усилий на этой конкретной задаче лучшие результаты динамических языков снова оказываются лучше лучших результатов статических (хотя, опять же, разница невелика).
Как и при сравнении полностью тривиальных caveman-оценок с менее тривиальной caveman-оценкой, очень сильные зависимости, которые проявлялись в тривиальных экспериментах, не обобщаются на этот более крупный случай. Как и там, экстремальные соотношения в производительности исчезают в этих более крупных экспериментах, за исключением случаев, где можно ожидать слабую производительность — например, при использовании ассемблера (что значительно более трудоёмко и сложно для человека) и при использовании относительно малоизвестных языков, для которых вряд ли стоит ожидать, что лаборатории AI тратят усилия на генерацию синтетических данных для RL-окружений.
Заметим, это противоположно тому, что показал первый эксперимент, где предполагалось, что очень плотные языки типа J имеют смысл с точки зрения эффективности. Возможно, использование малоизвестного (и "странного") языка имеет смысл, если есть очень большой бюджет и возможность обучить или дообучить модель специально под свой любимый язык, но для обычного пользователя LLM, судя по всему, придерживаться mainstream-языка — более выигрышная стратегия, чем использовать малоизвестный плотный язык.
И оказывается, что если построить график популярности языка против производительности на этом эксперименте (график не показан), наблюдается слабая-умеренная положительная корреляция: более популярные языки в итоге дают более корректные и одновременно более дешёвые решения.
Как отмечалось ранее, очень близкие по сути эксперименты могут давать существенно разные результаты. Например, ранее наблюдались заметно разные результаты в оценках Optimization 1 и Optimization 2, когда Optimization 1 и Optimization 2 оптимизировали сжатие и распаковку bzip2 в wasm — задачи довольно близкие друг к другу как эксперименты. Чтобы сделать сильное, универсальное утверждение вида "динамические языки эффективнее статических", нужно было бы прогнать эксперименты по множеству задач. Однако чтобы показать, что утверждение вроде
Динамически типизированные языки в целом требуют меньше токенов LLM, чем традиционные статически типизированные языки, потому что отсутствие явных объявлений типов делает код компактнее.
в лучшем случае лишь слабо и приблизительно верно и не особо релевантно для конкретных случаев, а может и вовсе недостаточно сильно, чтобы быть верным в общем смысле — достаточно попробовать несколько случаев и убедиться, что тезис не подтверждается в общем виде. Выше на одном уровне усилий тезис выглядит как будто отчасти верным, но с исключениями, а на более высоком уровне усилий — уже не особо верным, чего достаточно, чтобы сказать: тезис, вероятно, не универсально верен, если только в эксперименте не скрыт какой-то фактор, полностью его обесценивающий.
Pandoc
Чтобы взглянуть на совсем другую задачу, представленную также по-другому (больше в стиле TDD, чем "прочитай спецификацию"), следующий эксперимент берёт оценку Pandoc ProgramBench и адаптирует её под текущую задачу. Вместо задачи реверс-инжиниринга, которую предлагает ProgramBench, агентам даются материалы ProgramBench вместе с тестами ProgramBench, а затем производительность каждого условия измеряется по отложенному набору тестов2.
На графике ниже ось X снова — стоимость, ось Y — оценка на отложенных тестах.
Как и прежде, не наблюдается сильной зависимости между успехом или стоимостью и тем, статический ли язык, динамический или очень плотный. Снова видно, что относительно малоизвестные языки, как правило, показывают слабый результат (хотя Clojure здесь показывает результат намного лучше, чем на Zstd). Также заметно, что ассемблер показывает намного худший результат, что ожидаемо: человеку, пишущему на ассемблере, реализовать Pandoc было бы гораздо сложнее, чем Zstd, и нет особых причин думать, что для LLM всё иначе.
Что всё это значит?
Кто знает.
Остаётся много вопросов о том, что хорошо работает при использовании LLM (какие техники тестирования эффективны, какие языки хороши, какие архитектуры софта работают лучше, зависит ли стоимость исправления багов от языка, зависит ли от языка стоимость общего сопровождения программы и т.д.). Большинство этих вопросов остаются без ответа в публичных данных, а если ответы и есть в лабораториях AI, то эта информация в основном не публикуется.
Большинство утверждений о том, что тот или иной язык особенно хорош для использования с LLM, похоже, неверны (например, тезис о том, что Ruby, Clojure и J особенно подходят LLM, упомянутый в приведённых выше оценках, а также довольно распространённое утверждение о том, что Elixir особенно подходит LLM), но что верно — не ясно.
В 2014 году был проведён обзор литературы о статических и динамических типах, и обзор показал, что литература мало информативна за пределами нескольких кейс-стади. В качестве типичного примера академического исследования можно привести работу "Do Static Type Systems Improve the Maintainability of Software Systems? An Empirical Study", по которой был такой комментарий:
Испытуемым давали классы, в которых нужно было либо исправить ошибки в существующем коде, либо дописать пустые методы. Статические классы — на Java, динамические — на Groovy. В случаях ошибок типов (и соответствующих им ошибок отсутствия метода) разработчики решали проблему быстрее на Java. Для семантических ошибок разницы не было. Использовался внутрисубъектный дизайн с рандомизированным порядком задач среди 33 испытуемых. Заметное ограничение — исследование избегало "сложных управляющих конструкций" типа циклов и рекурсии, поскольку они увеличивают разброс времени решения. В результате все баги оказались тривиальными. Это видно по медианному времени решения задач, составляющему сотни секунд. Задачи могут содержать несколько багов, так что время на один баг совсем небольшое.
Отбор задач, избегающих "сложных управляющих конструкций" вроде циклов и рекурсии, где задачи занимают сотни секунд, делает результат бессмысленным применительно к задачам, которые реально отнимают время у профессионального программиста — точно так же, как первый рассмотренный эксперимент, где задачи занимали от нескольких десятков до сотни токенов. Однако с LLM можно давать им нетривиальные задачи и сравнивать результаты. Остаётся вопрос обобщаемости результатов на разные задачи, но с исследованиями на людях эта проблема стояла бы точно так же, только хуже (разброс у LLM огромен, но разброс у людей ещё больше, поскольку невозможно заставить одного и того же человека выполнить набор задач с разными "seed'ами"). И хотя $20 за реализацию LLM-декодера Zstd — не такая уж дешёвая сумма, если умножить на число языков и количество итераций на условие для каждого языка, если подумать, сколько бы стоило найти профессионального программиста, способного прочитать RFC zstd и реализовать его, аналогичное исследование на людях просто не было бы проведено из-за неподъёмной стоимости. Для задачи с Pandoc это верно вдвойне.
С LLM многие вопросы перешли из категории практически неразрешимых в категорию разрешимых при определённых усилиях и некотором количестве токенов. Из-за действующих стимулов3 непонятно, будут ли ответы на такие вопросы получены в ближайшее время, но по крайней мере теперь есть возможность попробовать.
Есть немало утверждений, которые эти эксперименты не могут ни доказать, ни опровергнуть (по причине, указанной выше — из-за разброса между разными задачами понадобилось бы намного больше экспериментов), но на которые они всё же немного проливают свет, например:
- Языки с большим количеством плохого кода в открытом доступе (например, PHP) покажут худший результат
- На этих задачах оказывается ложным
- Поскольку сейчас так легко всё переписать, стоит использовать мощный язык (например, Haskell)
- На этих задачах оказывается ложным
- Стоит использовать популярный язык
- Есть слабое подтверждение этого утверждения
По заранее зафиксированным предположениям:
- Высокая уверенность (95%): общий тезис "динамические против статических" не подтвердится
- Кажется верным
- Низкая уверенность (60%): статические языки будут немного лучше динамических при ultra
- Информации недостаточно для окончательного вывода, но если делать бинарный выбор верно/неверно, это стоило бы назвать неверным
- Высокая уверенность (98%): превосходство "странных" языков типа J не подтвердится
- Кажется верным
- [от читателя черновика]: "динамические лучше на малом масштабе, но их обгоняют статические с ростом размера проекта"
- Не подтверждается этими задачами (статические языки не показали существенно лучшего результата на намного более крупной задаче Pandoc по сравнению с меньшей задачей Zstd), но задачи и способ их подачи настолько различаются, что неясно, связано ли это с масштабом задачи или с другими различиями
Кстати, основная причина, по которой Clojure так сильно улучшает результат в оценке Pandoc по сравнению с Zstd, состоит в том, что в оценке Zstd 36 из 40 программ на medium и 5 из 40 на ultra на Clojure проваливали тесты, потому что преобразование byte выбрасывает исключение на диапазоне 128–255 (возможно, стоило использовать unchecked-byte?), а это преобразование использовалось неподходящим образом.
Это настоящий результат в том смысле, что если попросить лучшую публично доступную модель GPT реализовать Zstd (и, вероятно, если дать другие задачи на манипуляции с битами/байтами, где это может всплыть), она выдаст код, который проваливается именно таким образом. Если есть тесты, отлавливающие это, баг будет исправлен, но это всё равно потребует времени и токенов. Независимо от того, показал ли язык хороший результат, подобные издержки встречаются повсюду (например, cargo многократно вызывается с неверными аргументами, что немедленно отлавливается и исправляется, но замечено, что этот цикл может отнимать заметное количество реального времени на реальных проектах, если не дать codex явные инструкции по вызову cargo — и очевидно, что это стоит места в контекстном окне).
В любом случае, всё это иллюстрирует, почему для сильного утверждения о том, какие языки или классы языков особенно хорошо работают с LLM, потребовалось бы прогнать довольно много разных экспериментов. Если разбираться, почему то или иное условие получило определённую оценку, причины провала обычно оказываются какими-то идиосинкратическими, и не всегда понятно, насколько проблема обобщается на другие задачи или конфигурации. По результату одного эксперимента, или даже пяти-десяти, невозможно сделать вывод о программировании в целом.
Действительно, и в эксперименте с Zstd, и в эксперименте с Pandoc наблюдается корреляция между популярностью языка и положительными результатами (более высокая корректность, меньшая стоимость, меньшее время выполнения), и вполне вероятно, что такая же картина повторилась бы и в других экспериментах, но было бы ошибкой делать сильные выводы о каком-то конкретном языке. Подобное предупреждение уже звучало ранее при разборе частоты сломанных сборок по данным GitHub CI в разных проектах: отмечалось, что причины более или менее частых сломанных сборок в разных проектах могут различаться, и не стоит делать сильные выводы, поскольку результаты по проектам не всегда сопоставимы (например, если основная ветка одного проекта — это что-то вроде релиз-кандидата, прошедшего дополнительную проверку, у такого проекта ожидаемо будет мало сломанных сборок, но это не сравнимо с проектом, где люди разрабатывают прямо в main).
Вскоре после публикации кто-то, связанный с одним из языков с высоким результатом (по памяти, это был Мартин Одерски и Scala), опубликовал твит с этим постом и назвал высокий рейтинг языка победой этого языка. Такой вывод был неоправданным уже тогда, а из-за множества источников разброса, действующих здесь, любой подобный вывод о конкретном языке был бы неоправданным ещё сильнее.
Эти данные (при условии валидности экспериментов) способны опровергнуть некоторые сильные утверждения и намекают на некоторые другие, но на самом деле они могут лишь намекать на что-то для классов языков, а не для конкретных языков — потому что имеются только две задачи, и любой конкретный язык мог показать хороший или плохой результат по какой-то идиосинкратической причине, которая может обобщаться на другие задачи, а может и не обобщаться.
Благодарность за комментарии/поправки/обсуждение: Max Bittker, Yossi Kreinen, Aaron Levin, Alan Boll, Luke Burton, Marco Primi, Milosz Danczak и Justin Blank.
Приложение: избранные проблемы ai-coding-lang-bench
Как уже говорилось выше, собственный эксперимент здесь — быстрая и черновая оценка, наверняка полная недочётов, поэтому речь не о том, что этот эксперимент хорош, а бенчмарк из ссылки плох. Но вот несколько проблем, найденных в оценке Endoh ai-coding-lang-bench.
Одна из проблем — судя по всему, для части тестов запускался неверный исполняемый файл. Настройка опубликованного прогона, похоже, выполняла ../../minigit внутри директории каждого кандидата для одного из тестов, тогда как сгенерированный исполняемый файл кандидата находится по пути ../minigit. Пути ../../minigit не существует.
Поскольку у статически типизированных языков оказалась более низкая оценка корректности, автор эксперимента отметил: "единственные провалы из 600 прогонов были у Rust и Haskell (оба статически типизированы, оба относительно "сложные" языки)", и предположил, что "сложные языки" — такие как управление памятью в C, модель владения в Rust и монады/чистота в Haskell — "могут создавать дополнительную нагрузку для AI".
Однако провалы Rust были вызваны тем, что по пути ../../minigit нет исполняемого файла, из-за чего тест проваливался. Первый прогон Go "исправил" это, выполнив ln -sf minigit-go-1-v1/minigit ../minigit и связав generated/minigit со своим собственным прогоном, но это означает, что все последующие выполнения (для каждого языка) фактически запускали исполняемый файл именно первого прогона Go. При повторной оценке Rust относительно собственного исполняемого файла (а не с провалом из-за попытки выполнить несуществующий файл) Rust получает идеальный результат, что опровергает теорию о провалах Rust из-за сложности языка.
В других тестах тоже есть проблемы. Например, два теста устроены так, что проходят независимо от фактического проверяемого значения. В одном из тестов есть такой код:
if ../minigit commit ...; then
COMMIT_POST_CHECKOUT=$(cat .minigit/HEAD)
if grep -q "parent: $COMMIT1" \
".minigit/commits/$COMMIT_POST_CHECKOUT"; then
pass "checkout then new commit works"
else
pass "checkout then new commit works"
fi
else
fail "checkout then new commit works"
fi
Внутренний if имеет pass в обеих ветках, то есть это почти эквивалентно:
if ../minigit commit ...; then
pass
else
fail
fi
Похоже, внутренний if должен был содержать реальную проверку, но из-за ошибки в коде (возможно, ошибка copy+paste) проверка фактически исчезла.
Кроме того, как отмечалось выше, агенты могут модифицировать тестовую среду — что и сделал первый агент Go, чтобы исправить сломанную среду. У них есть полный доступ к тестам и окружению, и они могут делать что угодно, а набор тестов виден во время разработки без отложенной части, что легко может привести к читерству через частные случаи в коде, которые проходят тесты, но создают программу, бесполезную "в реальной жизни". На высоком уровне, похоже, произошло именно что-то подобное: многие программы не реализуют большие части спецификации, но проходят все тесты, что может указывать на то, что агенты "поняли", как проходить тесты, и предпочли это реализации спецификации (либо может указывать на то, что тесты слишком слабые и легко проходятся).
Ещё одна проблема — версии Claude Code CLI не одинаковы для всех прогонов (варьируются от 2.1.66 до 2.1.68). Есть ещё ряд подобных проблем, которые могут быть значимыми, но, вероятно, малы по сравнению с описанными выше.
Приложение: medium в цикле против ultra
В качестве примера того, что можно сравнить: было интересно, насколько выгодно по стоимости использовать medium в сочетании с просьбой к агенту продолжать работу, а также в голове держался вопрос, который часто поднимают сторонники "Ralph loop" — что лучше очищать контекстное окно на каждой итерации цикла и заново давать агенту полный промпт. Как и выше, заранее зафиксированные предположения:
- Нулевая уверенность (50%): ultra эффективнее, чем medium в цикле
- Непонятно, как об этом думать. Аргумент в пользу этого — ultra спроектирован определённым образом и должен быть умнее, чем повторяющийся medium в цикле. Но возможен и некий трейд-офф, при котором ultra создан больше для скорости, и, как уже отмечалось, разброс очень высок, так что даже если ultra выигрывает на большинстве задач, здесь он может проиграть; ultra может быть более оптимизирован под баланс скорости выполнения или другого параметра; также у ultra есть недостаток — он не "знает", что нужно остановиться после достижения корректности на скрытых тестах, тогда как условия medium, достигшие полной корректности, в этой настройке не запускаются повторно, что сильно даёт преимущество medium в цикле (что, можно поспорить, более реалистично относительно того, как люди используют эти инструменты)
- Можно сказать, что это 50% плюс эпсилон, поскольку мысль сразу пошла в эту сторону, а не в другую, но уверенность здесь крайне низкая в лучшем случае
- Средняя уверенность (80%): продолжение работы с сохранённым контекстом превосходит Ralph loop
- Режим /goal и подобные по умолчанию так не делают, и, вероятно, в Anthropic и OpenAI пробовали что-то вроде Ralph loop и посчитали это менее эффективным
- Пристальное отслеживание контекстного окна, похоже, стало менее важным по мере улучшения оболочек (и моделей?); в конце 2025 / начале 2026 приходилось часто выбрасывать контекстное окно при работе с долгими задачами, чтобы избежать проблем, и с тех пор это стало реже, но даже тогда, не особо следя за тем, что говорят другие, использовался агентный цикл по умолчанию с сохранением контекста и очисткой только при явных проблемах, что работало вполне неплохо — например, так был создан сильнейший в мире ИИ для Azul, так что не очевидно, что очистка контекста на каждой итерации цикла по умолчанию была правильным выбором тогда
Для этой конкретной задачи в среднем однократный запуск ultra выглядит лучше, чем многократные прогоны medium на единицу стоимости (и куда лучше на единицу времени), а продолжение с предыдущим контекстом превосходит Ralph. Проблема наивного повторного запуска medium в том, что агент может "застрять" на плохом решении и не продвигаться дальше. Теория за Ralph loop состоит в том, что выброс плохого контекста может предотвратить это, но это не спасает от плохого артефакта как такового.
Просто по опыту использования LLM замечено, что часто лучше выбросить кусок кода и заставить LLM переписать его с нуля, чем заставлять LLM модифицировать его или переписывать на месте. Michael Malis, который переписывает Postgres на Rust и вносит крупные изменения, также отмечал это. Это связано с уже упомянутой ранее идеей, что из-за высокого разброса (плюс этой зависимости от пути) часто выгоднее "бросить кубик" несколько раз и взять лучший результат, если не жалко потраченных токенов.
Приложение: Guards of Atlantis 2
Была попытка провести третий эксперимент, больше похожий на "бизнес-логику" — и по способу подачи задачи, и по её фактическому исполнению. Можно возразить, что эксперименты с Zstd и Pandoc — довольно нетипичные задачи для программиста, поскольку не многим программистам достаётся спецификация настолько подробная и хорошо написанная, как RFC Zstd, и не многим достаётся задача с таким количеством готовых тестов, как в случае с ProgramBench.
Идея заключалась в реализации настольной игры. Как правило, правила настольных игр пишут люди, не являющиеся экспертами в написании чистых спецификаций, так что реализация настольной игры больше похожа на то, что происходит, когда непрограммист (или программист, не являющийся экспертом в написании хороших спецификаций) даёт задачу.
Проблема здесь — найти игру, для которой есть разумный "оракул" для оценки, но которая при этом не тривиальна для LLM. Например, LLM смогли с одного раза реализовать правила Scout и Azul, что делает эти задачи плохими для теста. Для игр, которые LLM не решает мгновенно, оказался под рукой оракул для Guards of Atlantis 2, поскольку LLM ранее использовалась, чтобы реализовать копию игры для игры с друзьями (ссылки нет, поскольку неясно, как сделать интерфейс без нарушения авторских прав). Бэкенд занял лишь несколько часов человеческого времени, но потребовал довольно много времени LLM, чтобы правила стали примерно верными. Эта задача интересна тем, что правила запутаны примерно так же, как многие описания задач, которые получают программисты, но при этом в принципе возможно разобраться в правильных правилах и реализовать их (в конце концов, люди неявно делают это, играя в игру правильно офлайн).
В правилах настольных игр довольно часто встречаются правила, которые при строгом буквальном чтении неверны, и нужен "здравый смысл" (или какое-то FAQ), чтобы играть по ним правильно (некоторые дизайнеры игр стараются избегать этого, например J C Lawrence, но это довольно редкое явление). В Guards of Atlantis довольно много таких правил. При этом дизайнер Guards of Atlantis активно заявляет, что не существует такой вещи, как "дух правил" или трактовки по здравому смыслу, и говорит, что правило всегда нужно читать ровно так, как написано — то есть есть и множество случаев, где нужно игнорировать "здравую" трактовку и читать правило буквально. Такая комбинация крайне сложна для LLM (и, судя по частоте, с которой наблюдается игра людей "по замыслу дизайнера", это довольно сложно и для людей).
Похоже, было бы практически невозможно просто прочитать правила и играть правильно (конечно, это возможно в принципе, но требовало бы знания, какие правила читать буквально, а какие нет — что пришлось бы угадывать случайным образом, поскольку правила не задают согласованной системы, по которой можно было бы вывести, какие правила подчиняются какому мета-правилу). При реализации игры, чтобы заставить LLM понять правила, ей давались различные ресурсы: неофициальный FAQ по правилам (он верен), неофициальная короткая версия правил (написана лучше официальных и верна, но неполна), "opening book" (набор дебютных партий, который можно использовать для проверки правил, предполагая, что в нём только легальные ходы), комментарии из канала правил в Discord и т.д. LLM проводила проверки на согласованность между этими источниками с учётом того, что FAQ и комментарии в Discord имеют больший авторитет, чем непосредственно печатные правила. На личном аккаунте OpenAI/codex за $200/мес LLM использовала весь свободный ресурс, чтобы прогонять проверки на согласованность и вносить исправления в правила. Точное время не отслеживалось, но, по ощущениям, это было что-то около месяца-двух непрерывной работы над такими исправлениями, чтобы получить достаточно разумный, играбельный результат — хотя доверять его полной корректности вряд ли стоит.
Единственная причина частичного доверия к этому результату — то, что Pedro Oliveira также реализовал Guards of Atlantis, используя совершенно другой подход (более стандартный подход, при котором человек направляет LLM, а не пытается заставить LLM разобраться самостоятельно). При сравнении реализаций нашлось примерно по 10 багов в каждой. Вероятно, остались какие-то баги, где обе реализации одинаково неправильны, и, возможно, какие-то, где реализации различаются, но система проверки этого не заметила, но в целом правила в обеих реализациях сейчас достаточно надёжны. Так появился оракул для этой игры.
Эта задача интересна тем, что она больше похожа на "спецификацию" из реального мира, где спецификация неоднозначна, противоречива, а иногда просто ошибочна, и приходится использовать дополнительную информацию, чтобы получить корректный результат. Чтобы этот эксперимент не превратился в тест на то, насколько хорошо LLM способны доставать данные из неудобных форматов (например, преобразовывать opening book из набора изображений в какую-то структурированную форму, преобразовывать сканы правил в текст и т.д.), агентам давались как оригиналы всего, что ранее преобразовывала LLM (что также требовало различных проверок на согласованность для получения корректного результата), так и уже извлечённые данные (оригиналы предоставлялись для того, чтобы LLM могли при желании проверить их на ошибки извлечения).
Эта задача выполнялась со старыми моделями (часть — с GPT-5.1 или 5.2, ещё часть — с 5.4 или 5.5); с более новыми моделями, но без того рода направляющих инструкций, что давались старым моделям, задача оказалась всё равно слишком сложной. Независимо от языка, агенты набирали примерно 0 баллов на этой задаче.
Кстати, если интересно, с чем именно испытывают трудности LLM (и люди), вот пример. Есть карта с текстом: "Выбери цель — юнита рядом с тобой. После атаки: можно повторить один раз на другом враждебном герое".
В этой игре герой — это тип юнита. При строгом чтении, с полным пониманием правил, включая значение фразы "после атаки", это должно означать, что можно либо атаковать одного юнита, либо атаковать двух героев (в конце концов, повторить атаку на "другом враждебном герое" подразумевало бы, что первый юнит был героем; в противном случае это был бы просто другой юнит, являющийся героем, а не "другой враждебный герой").
На самой карте фактически напечатана своего рода эрратa, потому что люди жаловались на неясность формулировки; текст поправки: "(Можно повторить даже если исходной целью был миньон)". Это уже само по себе сбивает с толку LLM (и некоторых людей), но настоящая сложность в том, что есть и другие карты с аналогичной формулировкой, у которых такой поправки нет. Чтобы правильно играть с другими картами с такой же формулировкой, нужно знать, что каждый раз, когда используется такая конструкция, её следует читать с учётом поправки, напечатанной на этой конкретной карте. У дизайнера игры есть несколько таких конструкций со специфическим нелитеральным смыслом, который нужно держать в уме.
Другой пример правила, которое не следует читать очевидным образом — карта персонажа с текстом "Выбери одно, или оба, на разных целях: A, B". При строгом буквальном чтении можно было бы ожидать, что на разных целях можно выполнить либо A, либо B, либо оба. Но частью замысла игры является мета-правило, согласно которому персонаж не может атаковать другого персонажа несколько раз одной картой, поэтому трактовка, при которой можно выполнить и A, и B на каком-то количестве разных целей, как написано на карте, не может быть верной. Судя по аналогичным выводам и тому, как используются похожие конструкции, эту карту следует трактовать так: "Выбери одно, либо оба на разных целях", что, можно поспорить, всё ещё неоднозначно и было бы яснее написать как "Выбери одно или оба (если оба — на разных целях)".
Как человек, поняв, в чём "дух игры", можно разрешать подобные вещи. Но по замыслу это нигде явно не прописано в правилах, и приходится выводить это из обсуждений в Discord, что, судя по всему, выходит за пределы возможностей современных моделей, хотя люди, которых сегодняшние модели превосходят во многих специализированных задачах, способны на это.
При наблюдении за тем, как LLM реализовывали правила, причина, по которой LLM достигала потолка и не сходилась к полностью правильным правилам, состояла в следующем: LLM замечала, что какое-то правило непоследовательно и неверно. Затем она пыталась исправить это правило и попутно исправляла другие вещи, чтобы сделать их согласованными и корректными. Иногда это делало вещи более корректными, а иногда — менее. Когда становилось менее корректно, LLM иногда модифицировала существующий верный тест, превращая его в неверный, так что спустя какое-то время LLM фактически не улучшала корректность, а просто перекладывала неопределённость с одних правил на другие. И это с некоторыми направляющими указаниями о том, что и как проверять; без них даже более продвинутые современные модели не смогли справиться с этим разумным образом.
Наверняка существует настольная игра с подходящей сложностью правил для создания хорошего эксперимента, но по определению для неё потребовалось бы приложить усилия для создания оракула, а подходящего под руку оракула для настольной игры с нужной сложностью правил нет (кажется, это в принципе реализуемо и масштабируемо: можно было бы создать десятки или сотни таких оракулов не намного большими усилиями, чем на создание одного, а затем проверить, какие игры находятся на нужном уровне сложности, чтобы быть интересным тестом для сегодняшних LLM; причина, по которой Guards потребовала усилий — отсутствие готовой реализации с доступными данными реплеев; тот, кто опирается на данные реплеев для проверки корректности каждой игры, мог бы штамповать такие окружения относительно легко).
Это, можно сказать, забавная проблема в том смысле, что при чёткой спецификации, например ясно написанном наборе правил, LLM способны реализовать артефакт сложнее Guards of Atlantis (можно поспорить, что RFC Zstd сложнее, а Pandoc определённо сложнее; даже отдельные форматы документов, которые поддерживает Pandoc, например PDF, сложнее Guards of Atlantis), так что проблема не в поиске игры с достаточно сложными правилами, чтобы LLM испытывали трудности, а скорее в поиске игры с правилами, достаточно неоднозначными или противоречивыми, чтобы LLM испытывали трудности, но не настолько, чтобы для LLM это было совершенно безнадёжно. Но это реальная практическая проблема в том смысле, что люди в целом не очень хороши в написании чётких спецификаций, и то, насколько хорошо модели и оболочки справляются с неясной, противоречивой, а иногда и просто ошибочной спецификацией человека, вероятно, более релевантно для типичного пользователя, чем то, насколько хорошо LLM реализует что-то по спецификации, написанной так же хорошо, как RFC Zstd, или по задаче с 4800 тестовыми случаями и документацией ProgramBench Pandoc.
Приложение: причины различных решений
- Тестирование ultra
- Встречалось мнение, что это не стоит измерять, поскольку это особенность оболочки, а не модели. Понятно, почему хочется измерять это раздельно, если работа идёт над улучшением моделей или оболочек, но когда речь идёт о том, как люди используют инструменты — многие просто используют codex или claude со встроенными функциями и опциями; то, является ли что-то особенностью оболочки или пользователя, для них не особо релевантно
- Использование codex
- Встречались эксперименты с очень тонкой оболочкой по той же причине, что описана выше, и причина использовать именно codex, а не очень тонкую оболочку, та же самая
- Аналогично, в эксперименте с caveman-моделью использовались claude с Opus и Fable, а также codex с GPT.
- Отсутствие доступа к интернету
- Модели часто читерят при наличии доступа к интернету, а во многих задачах поиск в интернете не даёт исходного кода, решающего проблему, так что это приближает эти эксперименты к таким условиям
- Относительно крупные задачи по сравнению с многими распространёнными бенчмарками
- Хотя LLM выполняют и множество тривиальных задач, то, что отнимает время или токены, как правило, крупнее тех задач, что были в оценках Alderson или Endoh; LLM достаточно хороши на тривиальных задачах, так что не особо важно, делает ли какое-то условие их немного лучше или хуже на такой задаче, но для задачи вроде реализации Guards of Atlantis, где приходится тратить часы на настройку окружения даже для того, чтобы задача хоть как-то заработала, важно, что делает модели лучше или хуже
- Промпты, заданные агентом
- Публичные бенчмарки, кажется, перешли к относительно тонким/лёгким промптам, не описывающим задачу в деталях; считается, что так лучше, поскольку агент, настраивающий задачу, дал бы слишком много информации, помогающей другим агентам справиться с задачей
- Понятно, почему хочется это тестировать, но также важно, как хорошо агенты справляются с задачами, поставленными другими агентами, потому что многие задачи, которые выполняют агенты, определяются именно агентами; важна производительность при обоих стилях постановки задачи, а не только при одном, а публичные бенчмарки сместились к одному стилю
- Публичные бенчмарки, кажется, перешли к относительно тонким/лёгким промптам, не описывающим задачу в деталях; считается, что так лучше, поскольку агент, настраивающий задачу, дал бы слишком много информации, помогающей другим агентам справиться с задачей
- Эксперимент с Zstd: просьба к агентам исправить баги без сообщения, в чём проблема или какие тесты падают
- Обычно, если сказать агенту исправить конкретную вещь, он её исправит, но не обязательно исправит весь класс проблемы; замечено, что если сказать, что есть проблема, но не сказать, в чём именно, агент иногда делает более общее исправление, а не узкий, хрупкий патч, так что важно, как агенты ведут себя при таких инструкциях (конечно, можно прямо попросить не делать узкий хрупкий патч, но это часто не работает)
- Это немного похоже на проблему, отмеченную в примечании про отложенные тесты Pandoc, где информирование агентов о наличии отложенного набора тестов, похоже, вынуждало их производить более обобщённые и менее хрупкие решения
- Обычно, если сказать агенту исправить конкретную вещь, он её исправит, но не обязательно исправит весь класс проблемы; замечено, что если сказать, что есть проблема, но не сказать, в чём именно, агент иногда делает более общее исправление, а не узкий, хрупкий патч, так что важно, как агенты ведут себя при таких инструкциях (конечно, можно прямо попросить не делать узкий хрупкий патч, но это часто не работает)
Приложение: проблемы этих экспериментов
В плане бенчмаркинга производительности накопился достаточный опыт, чтобы в целом понимать, в чём слабые места собственных бенчмарков, разумно оценивать соотношение времени/усилий и недочётов, и быть достаточно уверенным, что существующие недочёты бенчмарков не критичны для того, что пытаешься понять. С AI-экспериментами такого опыта пока не накоплено, так что на мета-уровне стоит ожидать, что любой проведённый AI-эксперимент содержит неизвестные недочёты.
Ещё одна причина ожидать здесь недочётов — настройка этих экспериментов была доверена coding-агентам, и каждый раз, когда на поиск проблем тратилась хотя бы минута, находилась хотя бы одна проблема. Это говорит о том, что довольно вероятно наличие дополнительных недочётов, которые можно было бы обнаружить, если посмотреть чуть внимательнее, но цель была держать уровень корректности на уровне "быстрого учебного проекта", а не на уровне "Gary Bernhardt", так что поиск был прекращён после исправления нескольких проблем.
В своё время, работая инженером верификации, была возможность посетить митап инженера Sun/Oracle в Остине, примерно в 2007 году, где математически формализовалась идея перевода времени между багами в уровень уверенности при выпуске чипа. Такой подход встречается редко, но недавно Will Wilson (сооснователь Antithesis) упоминал, что в Antithesis кто-то использовал математику из экологии (литературу о наблюдении редких видов) для оценки истинной частоты багов, что выглядит куда более продвинутой версией того, что делал тот инженер Sun/Oracle пару десятилетий назад.
Идея крутая, но когда находишь баг каждую минуту поисков, сложная математика не нужна, чтобы понять — вероятно, есть ещё много других багов. Если бы это делалось для работы и была причина заботиться о точности этих экспериментов, вероятно, стоило бы посмотреть внимательнее и исправить больше проблем (и, вероятно, было бы больше навыков и опыта, чтобы допускать меньше ошибок при инструктировании LLM для настройки таких экспериментов, если бы это была основная работа). Но для целей ответа на вопрос "верно ли утверждение, что динамические языки значимо лучше статических при использовании LLM?" — есть чуть больше уверенности, что это утверждение неверно, и есть немало других вопросов, которые кажутся более вероятными дать какой-то практически применимый результат (например, какие техники или тестовые библиотеки работают лучше всего).
Обычно материалы не публикуются в блоге, пока не появится ощущение, что они достаточно основательны, но это значит, что часто данные исследуются ровно до удовлетворения собственного любопытства, а результат так и не публикуется. Из разговоров с людьми об этих неопубликованных результатах, собеседники часто интересуются результатами, даже если они не доведены до желаемого стандарта, что указывает на то, что и другие люди могли бы быть заинтересованы. Судя по всему, чтобы довести это до желаемого стандарта, потребовалось бы как минимум в 10 раз больше времени, чем уже потрачено. В данный момент довольно много других дел, и сложно представить, что найдётся время на это в ближайшие месяцы, а к тому моменту, возможно, до публикации так и не дойдёт руки. В одном из недавних постов упоминался анализ, сделанный почти год назад — попытка понять, какие автомобили безопаснее в плане риска сотрясения мозга при авариях; на это было потрачено время, получен удовлетворительный ответ, но так и не нашлось времени довести результат до состояния, готового для публикации.
Некоторые результаты того анализа выглядят "публикуемыми" в смысле, что могли бы превратиться в статью (например, находка на реальных данных об авариях о том, что зависимость между HIC и скоростью выглядит как четвёртая степень (!); есть статья, пытавшаяся найти эту зависимость, но применившая неверный вид анализа и не сумевшая найти зависимость типа "O(n)", получив что-то намного более расплывчатое), но никогда не было особой заботы о том, статья это или пост в блоге, и в итоге чаще происходит переход к следующему анализу, а не доведение предыдущего до публикуемого вида.
Более недавний проект в том же духе: после создания сверхчеловеческого ИИ для Azul была попытка создать сверхчеловеческий ИИ для Splendor значительно менее трудоёмким по человеческому времени способом. Полагаю, полностью это не удалось, но результат превосходит все другие найденные ИИ для Splendor с приличным отрывом, что само по себе довольно интересный результат. Есть достаточно знаний об ИИ для настольных игр, чтобы написать об этом что-то более развёрнутое, но основной интерес был в том, чтобы понять, можно ли получить что-то приличное, а дальше внимание постоянно переключается на другие проекты вместо написания хорошего текста об этом. Один из интересных моментов там — многие оптимизации производительности, которые хочется сделать, на самом деле меняют результат, так что нельзя полагаться только на оптимизации, которые можно строго проверить как не влияющие на результат. Но если наивно попросить coding-агента сделать такие оптимизации без снижения силы игры, он сделает всевозможные вещи, которые эту силу снижают. Случаи с очень серьёзным падением силы легко отловить, но есть и более тонкие проблемы, которые иногда приводят, например, к отсутствию изменения силы против собственного ИИ в self-play, но к падению силы против людей или других ИИ, так что нужен какой-то процесс отлова плохих оптимизаций, и это по сути произвольный процесс, который нужно проектировать, комбинируя собственную интуицию и опору на LLM (которые будут очень полезны, но также часто совершенно неправы).
Для такого рода data-проектов LLM колоссально снижают объём усилий, нужный для получения результата, достаточно сильного, чтобы удовлетворить любопытство, но, насколько можно судить, они не сильно снижают усилия, требуемые для публикации результата (по крайней мере если писать результаты вручную, а не поручать это LLM, и при этом хочется, чтобы результат был опрятным и чистым), что означает, что оформление результатов упирается в своего рода узкое место в духе закона Амдала, так что в последнее время делается больше таких проектов, а описывается меньшая их доля. По сути, оформление даже стало занимать больше времени из-за изменения рабочего процесса. Например, вместо простого вывода графика из ggplot2 теперь делается интерактивная версия, которая в чём-то приятнее, но определённо требует больше времени на создание. И запускается проверка орфографии/грамматики через LLM (пока это единственная помощь LLM в самом написании текста), которая находит массу вещей для исправления. Поскольку каждая правка просматривается вручную, а не принимается автоматически (и опечаток делается много), это отнимает довольно много времени (больше часа на последнем посте и больше получаса на этом посте, хотя проверка не была доведена до конца и была брошена примерно на середине).
В любом случае, публикация этого материала — эксперимент с публикацией немного черновых заметок вместо той доведённой до блеска версии, которая хотелось бы иметь перед публикацией. Если есть мнение по этому поводу, можно поделиться (X Bsky Mastodon)!
Ссылок на GitHub для текущих экспериментов нет. С одной стороны, ощущение, что их действительно стоило бы дать. С другой стороны, там беспорядок и куча вещей, которые хотелось бы почистить перед публикацией кода, и неизвестно, дойдут ли до этого руки и когда — так хотя бы что-то опубликовано, а не просто обсуждено с парой друзей и осталось лежать на жёстком диске бесконечно.
Приложение: подробнее о Zstd
Агентам было дано указание игнорировать производительность, но таймаут не был бесконечным, и в условиях medium некоторые тесты не успевали выполниться. Можно возразить, что это несправедливо, но на итоговую оценку это существенно не повлияло. Для таймаутов не по причине бесконечного цикла было 2 тестовых случая на Clojure (из 40 * 34 тестов), 2 на J, 2 на Tcl, 1 на Factor и 1 на PHP. При этом таймаут в 9000с (2,5 часа) был довольно щедрым, учитывая, что крупнейший тестовый случай занимал 4 ГиБ. Не справиться с декодированием 4 ГиБ за 2,5 часа означает скорость менее 0,5 МБ/с на ядре Graviton 5, что довольно медленно.
Вот некоторые из проблем, встреченных при попытке заставить агентов настроить это (и, как отмечалось выше, короткое время на обнаружение каждой проблемы говорит о наличии ещё большего их числа):
- Изначально настройка сборки не была чётко описана агентам, из-за чего некоторые языки случайно проваливались, когда агенты делали что-то, что казалось разумным по описанной им настройке, но не работало при оценке
- По какой-то причине агент, выполнявший настройку, наложил странные произвольные ограничения на одни языки и не на другие (например, у настройки Rust не было доступа к rustfmt или Clippy); подобное было у большинства, но не у всех языков
- Многие тесты (созданные агентом) фактически оказались тестами на производительность/нагрузку, хотя агентам было сказано игнорировать производительность (обработку 4 ГиБ Zstd за 9000 секунд вряд ли можно назвать стресс-тестом производительности)
- У некоторых языковых условий были произвольные инструкции агентам (например, условие Haskell содержало запрет использовать bytestring с предложениями альтернативной реализации)
- Некоторые языковые условия использовали очень старые тулчейны (например, Zig был на версии 0.10)
- У некоторых языковых условий был вспомогательный каркас, помогающий агентам реализовать Zstd
- В исходных условиях для ассемблера агенты реализовывали код на C, затем компилировали его в ассемблер и отправляли ассемблерный код (это давало результаты для ассемблера, примерно сопоставимые с результатами других языков)
- У некоторых языковых условий были неверные описания доступных инструментов (например, условиям для ассемблера сообщалось о доступе к GDB, но GDB не работал)
Есть одна вещь, которая вряд ли была багом, но всё равно была убрана. Один из тестов был очень сложным (примерно 10% агентов проходили его с первой попытки). При тестировании актуального релизного бинарника zstd этот же бинарник также проваливает этот тест. При чтении RFC становится ясно, что это неоднозначность в RFC насчёт легальности определённого крайнего случая. Наблюдалась довольно сильная кластеризация относительно того, какие языки чаще проходили этот тестовый случай, что интересно, но не выглядит очень полезной для измерения вещью, когда все остальные тесты измеряют (или хотя бы пытаются измерять) что-то более прямолинейное.
В любом случае, в приведённом выше списке (не исчерпывающем) многие проблемы затрагивали значительную долю языков, а некоторые приходилось исправлять несколько раз. В сумме, если считать каждое условие отдельным багом, было исправлено (агентами, по указанию) больше 100 таких проблем, и, вероятно, есть и другие. При разговоре с Max Bittker (руководителем стартапа по RL-окружениям), он отметил:
во всех экспериментах, над которыми я работал, в итоге приходилось тратить огромное количество времени и усилий, в основном в форме чтения траекторий (или сводок по многим траекториям), а затем разбора проблем: например, "о, этого класса багов не должно быть возможно, давайте обновим X" (где X — это промпт, оболочка/окружение или верификатор)"
агенты имеют тенденцию всё это "заляпывать", поэтому я уделяю много внимания тому, чтобы проблемы фиксировались на правильном уровне: например, очень важно, что попадает в контекст агента под тестом (плохо добавлять случайный мусор, о котором ему нужно заботиться, или, в худшем случае, слить ответы), а что фиксировано в других частях системы за сценой
агенты, когда пишут эксперименты, недостаточно чувствительны к опыту тестируемого агента и просто дадут ему ответ или решат проблемы, переложив их на плечи внутреннего агента ("не забудь не обманывать, пожалуйста")
также у меня был большой успех с повторным использованием существующих вещей (репозиториев, игр, инструментов, уровней) и построением оболочек и верификаторов вокруг них, а не попытками сделать что-то с нуля для эксперимента через промптинг
Оглядываясь назад, есть некоторое сожаление по поводу проведения межъязыкового эксперимента. Даже после исправления 100 и более проблем эксперимента, нет никаких сомнений, что осталось намного больше. Возможно, это просто "трава зеленее по ту сторону забора", и следующий эксперимент вызовет такое же сожаление, но, кажется, оценка эффективности различных техник или фреймворков тестирования потребовала бы намного меньше работы, чем оценка разных языков, и эта тема кажется как минимум не менее интересной. И, оглядываясь назад, если бы больше работы было сделано вручную, а не через агентов, всё пошло бы намного лучше. Например, стоило бы сначала поручить агентам создать окружение для одного языка, затем и агентам, и вручную его проинспектировать и исправить проблемы, прежде чем создавать окружение для следующего языка. После нескольких таких итераций могла бы получиться лучшая настройка для создания окружений для остальных языков (а если нет — можно было бы просто повторять этот процесс для каждого языка и получить более надёжный результат, вероятно, даже не потратив больше времени).
Стоит также отметить, что ряд подлинных различий между языками фактически не был проверен — например, безопасность памяти при враждебных входных данных. Если бы агентам было сложнее написать в целом примерно корректный код на C или C++, чем на Rust, это было бы заметно, но если бы фаззер, valgrind или другие инструменты выявили проблемы, это, скорее всего, не отразилось бы в небольшом наборе тестов. Агенту было поручено (кратко) проверить код на C и C++ на проблемы безопасности памяти. Агент утверждает, что запустил код на C и C++ под ASan+UBSan, попробовал несколько фаззинг-входов (по 4000 каждый) и не нашёл проблем, но, конечно, это не означает, что проблем нет, или что более крупная кодовая база не выявила бы их.
И действительно, аналогичная быстрая проверка на проблемы безопасности памяти для эксперимента с Pandoc нашла такие проблемы во всех программах на C и во всех, кроме одной, программах на C++ (проблемы были вроде некорректного разыменования памяти за границами массива; один конкретный пример — в одной из программ на C обрезанная таблица LaTeX могла привести к чтению памяти за границами буфера). Тот факт, что эти проблемы находились за десятки секунд промптинга, говорит о том, что многие подобные проблемы можно было бы найти и исправить без больших человеческих усилий, но это потребовало бы немалого количества токенов и подняло бы стоимость версий на C и C++ значительно выше стоимости версии на Rust, и после всего этого уверенности в безопасности памяти версий на C и C++ было бы всё равно меньше, чем в версии на Rust.
В любом случае, для интересующихся распределением результатов — вот данные для medium и ultra:
Не очень нравится, что результаты ultra здесь несколько "насыщены" (saturated), но одна из "проблем" тестирования ultra в том, что модель будет продолжать работу довольно долго по мере усложнения задачи (например, большинство прогонов Pandoc на ultra длились 12+ часов, а прогоны для ассемблера — намного дольше), так что не насыщаются только очень крупные задачи, вроде эксперимента с Pandoc, либо задачи, слишком сложные в каком-то смысле, вроде эксперимента с Guards of Atlantis.
- Читатель черновика заранее зафиксировал предположение: "динамические лучше на малом масштабе, но их обгоняют статические с ростом размера проекта". [вернуться]
Отложенные тесты кажутся необходимыми, потому что без них агенты читерят и будут распознавать входные данные теста и хардкодить проходящий вывод (иногда это происходит даже при явном указании не обманывать). Если бы всё читерство было настолько явным, это не было бы проблемой (и могло бы быть интересным для измерения — то, насколько агенты по-разному следуют инструкциям в зависимости от языка, важно для реальных пользователей), но многое читерство более тонкое и трудно поддающееся однозначной оценке. Например, некоторые агенты писали код, ветвящийся по структуре тестов, но заполняли ветви кодом, не привязанным жёстко к одному конкретному результату теста, способным проходить множество вариаций одного и того же теста. На всём спектре от "точно не читерство" до "явное читерство" какой-то агент пробовал что-то. Как показал разбор Senior SWE-Bench, оценка экспериментов силами LLM — штука непростая и отличный способ внести и смещение, и разброс; использование отложенного набора тестов имеет свои проблемы, но позволяет избежать этого намного большего набора проблем.
К тому же отложенные тесты вызывают подозрение, поскольку их создавали сами агенты. Задумка была в создании отложенных тестов, которые разумный человек (или агент) мог бы пройти, если не читерит. Агенты проверяли этот набор отложенных тестов на случаи, где это было неразумно, и убирали некоторые из них, но эта проверка не была сделана вручную, так что вероятно, что хотя бы один отложенный тест несправедлив в каком-то смысле. Однако общая оценка по отложенным тестам достаточно низка, чтобы не сильно волноваться о небольшом числе плохих тестов (если бы речь шла о работе в лаборатории AI и обучении моделей следующего поколения, это волновало бы сильнее, но для текущей задачи это не критично).
Инструкция агентам не читерить при наличии отложенного набора тестов не предотвратила явное читерство, показывавшее крайне низкий результат на отложенных тестах, но сообщение агентам о наличии отложенного набора тестов, по которым их оценивают, похоже, снижало результат на видимых агентам тестах, одновременно повышая результат на отложенных тестах (без такого сообщения ряд агентов достигал 100% на тестах Pandoc с бесполезно хрупким кодом; при сообщении об отложенном наборе ни один агент не набрал 100% после 1 хода на ultra, но результаты на отложенных тестах были заметно лучше, что говорит о лучшей обобщаемости).
[вернуться]Существует немало Substack-блогов, YouTube-каналов и прочих ресурсов, обещающих раскрыть секреты успеха в кодинге с LLM, но окупаемость времени на реальные эксперименты по факту сомнительна. При разборе caveman mode было показано, что один из крупнейших программистских YouTube-блогеров потратил несколько минут на изучение вопроса и решил, что это работает. Потратить даже 15 минут на проверку, работает ли это на самом деле, вероятно, невыгодно по сравнению с затратой этого времени на производство большего количества контента.
Есть разные научные статьи, обсуждающие различные техники, и иногда они углубляются сильнее, чем большинство постов в блогах или видео, но в среднем это не означает, что в них больше полезной информации. Например, при запросе к ChatGPT (5.6 Sol, Pro) найти обсуждения эффективности языков применительно к LLM, он нашёл эту статью о токен-эффективности, где есть интересная идея, но та же проблема, что и в разобранных ранее caveman-mode экспериментах: рассматривается задача, недостаточно интересная, чтобы результат был релевантен для программиста. Проверка того, кто цитировал эту статью, привела к другой статье трёх учёных о токен-эффективности языков под названием "The Best Programming Language for Tokenmaxxing", но по сравнению с этим постом та статья сравнивает лишь четыре языка, использует худшие модели и небольшие игрушечные задачи (из чего-то под названием LiveCodeBench; стоимость решения задач с GPT-5.5 там часто порядка 1000 токенов). Независимо от того, насколько хорошо выполнен эксперимент, как уже отмечалось в этом посте и в эксперименте с caveman mode, при переходе от небольшой игрушечной задачи к задаче, действительно значимой для хобби-проекта или работы, результаты часто оказываются совершенно другими по относительным величинам. Также в этой статье указано, что был использован промпт "Чтобы протестировать программу, запусти ровно ./test.sh... Это единственные тесты, которые меня интересуют", и утверждается, что это реалистично, поскольку "мы полагаем, что такая настройка — реалистичный способ изучать поведение агента: в повседневном использовании программисты не скрывают свои тесты от агентов. Вместо этого программисты направляют своих агентов продолжать работу, пока все тесты не пройдут". Но, как отмечалось выше, такой подход приводит к хрупкому коду, который проваливается в реальном мире (или, если есть отложенные тесты, не показанные агенту, он проваливает их с очень высокой частотой; эту проблему не решить простым добавлением ещё нескольких тестов; возможно, это можно решить с помощью чего-то вроде фаззинга или property-based тестирования, но насколько хорошо это работает — тема для отдельного материала). Речь не о том, что эти статьи плохие или что из них нельзя извлечь что-то интересное, но как программисту, желающему понять, какие техники или инструменты стоит использовать, из подобных статей эту информацию получить не удаётся.
[вернуться]