Как ловят галлюцинации LLM в коде через CI-проверки
Один выдуманный вызов функции в коде чуть не прошел ревью, потому что выглядел слишком «родным» для проекта. После этого в команде собрали цепочку проверок в CI, которая помогает находить такие ошибки до попадания в сборку и на релизные платформы.
Почему нейросетевой код легко обманывает ревью
История началась с пулл-реквеста, где в цикле обновления частиц оказался вызов несуществующей функции FrameAllocator::TryReclaimTail(). Он был оформлен в правильном стиле, с нужным префиксом и даже с комментарием, поэтому на ревью выглядел вполне правдоподобно.
На ПК сборка прошла, тесты тоже не ругались: вызов прятался внутри старого профайлерного макроса, который на десктопе просто исчезал. Проблема всплыла только позже, когда ночной билд под консоль упал на линковке — функции в движке никогда не было. Автор кода честно признал, что не стал проверять наличие символа, потому что он выглядел как что-то, написанное давным-давно своим техлидом.
Что команда поменяла в сборке
Запрещать LLM в проекте не стали. Вместо этого за несколько месяцев вокруг обычной сборки выстроили набор проверок, который отлавливает большую часть выдуманных сущностей еще до того, как они попадут дальше по пайплайну.
Первое и самое важное правило — каждый PR теперь собирается сразу всеми тулчейнами проекта: MSVC для десктопа и редактора, clang для двух консолей и gcc для серверной части. Все это идет с включенными предупреждениями как ошибками. Да, это дороже по времени и ресурсам, зато закрывает дыры, которые раньше проявлялись только ночью или уже после мерджа.
Еще одна мера — переписывание макросов, которые на некоторых платформах полностью выбрасывают свои аргументы. Теперь выражение внутри видно компилятору везде, даже если оно не вычисляется. Это помогает заметить несуществующий символ раньше, чем он спрячется в платформенно-зависимом коде.
Какие проверки ловят ошибки LLM лучше всего
Отдельно в пайплайн добавили анализ новых идентификаторов из diff. Бот вытаскивает все имена из добавленных строк и проверяет, встречались ли они в репозитории или в сторонних заголовках. Если имя впервые появляется в том же PR и при этом нигде не объявлено, на запрос ставится метка и прилетает комментарий.
Эта проверка наивная только на первый взгляд: она хорошо ловит случаи, когда модель придумывает название функции, переменной или класса, особенно если ошибка спрятана за условной компиляцией, шаблонами или строками, которые компилятор не видит. Но у подхода есть цена — ложные срабатывания.
Чтобы снизить шум, индекс символов собирают по препроцессированным исходникам. Это добавляет несколько минут к ночной сборке и занимает около двух гигабайт на диске, зато делает проверку заметно полезнее. Также пришлось завести файл исключений, который, как обычно, растет быстрее, чем хотелось бы.