Что такое PAL (Program-Aided Language Models)? PAL (Program-Aided Language Models) — подход к рассуждению, в котором языковая модель читает задачу на естественном языке и генерирует программу как промежуточные шаги, а вычисление ответа берёт на себя отдельный интерпретатор Python. Авторы описывают это в статье Gao et al. на PMLR: модель декомпозирует условие в программные шаги, а решение делегируется среде исполнения. Генерация кода и его запуск — две разные операции, и это ядро техники.
Определение PAL по первичке: генерация и исполнение разделены
В первичной статье (Gao et al., PMLR 202) PAL описан как подход, где языковая модель читает задачу на естественном языке и генерирует программы в роли промежуточных шагов рассуждения, а шаг решения перекладывается на интерпретатор Python. Разложение условия на исполняемые шаги остаётся единственной задачей модели, вычисление уходит внешнему решателю. Авторы подчёркивают: цепочки рассуждений могут быть логически верными, но приводить к неверному ответу, и перенос вычисления закрывает этот разрыв.
Официальный README проекта reasoning-machines/pal формулирует то же: модель решает задачи со сложной арифметикой, генерируя цепочки из текста и кода, а исполнение кода передаётся программному runtime — в их случае интерпретатору Python. Реализация построена на few-shot prompting.
Для PAL нужны два отдельных шага: модель генерирует программу, затем интерпретатор исполняет её. В README показан интерфейс ProgramInterface, который связывает backend модели, backend Python и пользовательский промпт; параметр get_answer_expr задаёт выражение, вычисляемое после сгенерированного кода, чтобы получить ответ.
Разделение ролей объясняет, почему PAL устойчивее к большим числам. В статье на GSM-HARD, где числа в вопросах заменены на крупные, точность прямого ответа падает с 19.7% до 5.0%, а PAL держится на 61.5%. Причина в том, что арифметику выполняет интерпретатор.
| Звено | Что делает | Где происходит |
|---|---|---|
| Языковая модель | читает условие, пишет программу и промежуточные шаги | генерация текста и кода |
| Runtime | исполняет сгенерированный код, возвращает результат | интерпретатор Python |
| Читатель | проверяет код до запуска | своя среда |
Полный few-shot промпт с def solution и запретом операций
Ниже — готовый промпт для задачи на подсчёт остатка. Он требует от модели только определения функции solution() с полным телом и return, запрещает сетевые и файловые операции и не просит исполнять код. Обучающий пример и новая задача даны целиком.
Ответь только определением Python-функции solution() без сетевых и файловых операций. Не исполняй код. Ниже пример и новая задача.
Пример: В ящике 3 набора по 8 болтов, израсходовано 5. Сколько осталось?
def solution():
batches = 3
per_batch = 8
used = 5
return batches * per_batch - used
Новая задача: Мастерская заказала 7 наборов по 18 заготовок, использовала 43, получила возврат 6. Сколько осталось?
Верни def solution(): с полным телом и return, не словесный ответ и не отдельный return вне функции.
Что менять под себя: числа и формулировку новой задачи, а также обучающий пример, если ваша задача другого типа. Требование верни def solution() с полным телом и return оставляйте — оно отсекает ответ в виде отдельного выражения или словесного числа.
Почему промпт устроен именно так. В статье PAL каждый пример в промпте — это пара «вход и программные шаги», финальные ответы в примерах не приводятся: решение делегируется интерпретатору. Авторы также отмечают, что осмысленные имена переменных важны для качества: в их абляции замена имён на однобуквенные снижала средний результат с 71.8% до 59%. Поэтому в промпте переменные названы по смыслу — batches, per_batch, used, returned.
Ограничение «без сетевых и файловых операций» — это инструкция модели. Условие задаёт желаемый вид кода; реальные ограничения при запуске определяются средой исполнения. Об этом — отдельный раздел ниже.
Корректная solution и print(solution()): эталон 89
Ожидаемый код для новой задачи выглядит так. Это ручной учебный эталон, составленный для проверки.
def solution():
batches = 7
per_batch = 18
used = 43
returned = 6
return batches * per_batch - used + returned
print(solution())
Разберём по строкам. batches = 7 — число наборов, per_batch = 18 — заготовок в наборе, used = 43 — израсходовано, returned = 6 — возврат. Выражение batches * per_batch - used + returned считает остаток: расход вычитается, возврат прибавляется. Функция объявлена через def solution():, return находится внутри неё, а print(solution()) вызывает функцию после определения и печатает результат.
Арифметика: 7 × 18 = 126, затем 126 − 43 = 83, затем 83 + 6 = 89. Ожидаемый stdout — 89. Обучающий пример из промпта даёт 3 × 8 − 5 = 19; новая задача даёт 89. Оба значения — учебные, они показывают, как проверить знаки и порядок операций.
Обратите внимание на форму. Если модель вернёт return вне функции или отдельное выражение без def, код не выполнится как задумано: print(solution()) не найдёт функцию. Требование полного тела с return внутри def — это то, что делает результат запускаемым.
| Проверка | Что смотрим | Ожидание |
|---|---|---|
| Входы | 7, 18, 43, 6 на месте | все четыре значения сохранены |
| Знаки | расход вычитается, возврат прибавляется | - used + returned |
| Синтаксис | return внутри def solution() |
функция объявлена целиком |
| Вывод | print(solution()) после определения |
stdout 89 |
Проверка входов и знаков: контроль used 44 → 88
Перед запуском стоит сверить числа и знаки с условием. Четыре входа — 7, 18, 43, 6 — должны совпадать с текстом задачи. Знак перед used — минус, перед returned — плюс. Если перепутать, ответ изменится предсказуемым образом, и это удобный контроль.
Возьмём ту же задачу и заменим used на 44 при остальных равных. Тогда 7 × 18 = 126, 126 − 44 = 82, 82 + 6 = 88. Ручной эталон даёт 88. Разница с 89 ровно на единицу — это и есть след забытого или лишнего расхода. Такой контрольный прогон показывает, что формула чувствительна именно к тому входу, который вы меняете, и что знак вычитания стоит на нужном месте.
Этот эталон — учебный. Он не является измеренным ответом языковой модели и не доказывает, что модель выдаст именно его. Его роль — дать вам точку сравнения: вы знаете, какой результат должен получиться при данных входах, и можете сверить с ним то, что вернёт интерпретатор.
Порядок проверки для читателя:
- Получите текст кода от модели.
- Прочитайте каждую строку и сопоставьте четыре входа со значениями в условии.
- Проверьте знаки: расход вычитается, возврат прибавляется.
- Убедитесь, что в коде нет файловых, сетевых и процессных операций.
- Выполните именно проверенный малый код в своей отдельно подготовленной среде Python.
Если на шаге 4 обнаружился неожиданный импорт или операция, остановитесь и разберите код заново. Запускать непроверенный сгенерированный код не стоит.
Промпт не равен sandbox: сначала просмотр, потом запуск
Инструкция в промпте — «без сетевых и файловых операций» — управляет тем, что модель напишет, и не является изолированной средой. Указание в промпте не обезвреживает произвольный выход: если модель всё же вставит вызов, он окажется в тексте кода, и его запуск будет зависеть от вашей среды. Разграничение простое: промпт влияет на генерацию, изоляция — на исполнение.
В README проекта показано, что ProgramInterface запускает сгенерированный фрагмент и затем вычисляет get_answer_expr, чтобы получить финальный ответ. То есть исполнение кода — штатная часть маршрута, и оно происходит в среде, которую вы контролируете. В статье авторы используют стандартный интерпретатор Python и отмечают, что решателем может быть любой интерпретатор или компилятор.
Практический порядок: сначала просмотр кода, затем запуск малого проверенного фрагмента. Перед запуском определите реальные ограничения выбранной среды на файлы, сеть и процессы. Приведённый пример не содержит готовой настройки таких ограничений.
Отдельно про ошибки исполнения. В статье авторы сообщают, что весь сгенерированный Python-код был синтаксически корректным, менее 1% примеров вызвали исключение во время выполнения, а самая частая ошибка — обращение к переменной, которая не была определена раньше (NameError). Это наблюдение из их экспериментов, и оно полезно как ориентир: чаще всего проблема не в синтаксисе, а в неопределённой переменной.
Границы учебного примера
В LearnPrompting и PromptingGuide уже есть полные учебные маршруты few-shot, генерации и исполнения кода. Здесь проверяется собственный набор: 7 наборов по 18 заготовок, расход 43 и возврат 6, с контрольной заменой расхода на 44.
Качество PAL зависит от того, насколько верно модель составила программу. В первичной статье при слабой способности модели к коду результат PAL мог уступать chain-of-thought; применимость выбранного генератора требуется проверять отдельно.
Частые вопросы
Что такое PAL простыми словами?
PAL — это способ решения задач, где языковая модель пишет программу, а отдельный интерпретатор Python выполняет её и выдаёт ответ. Модель занимается разбором условия и составлением кода, а вычисление уходит среде исполнения. Так арифметика выполняется точно, если программа составлена верно.
Чем PAL отличается от chain-of-thought?
В цепочке рассуждений модель ведёт шаги в виде текста и сама называет ответ. В PAL шаги записаны как программные конструкции, а финальный результат получается запуском кода в интерпретаторе. Авторы статьи связывают устойчивость PAL именно с этим переносом вычисления.
Почему в промпте нужен def solution и return?
Требование полного тела функции с return внутри def делает результат запускаемым: print(solution()) вызовет объявленную функцию. Отдельный return вне функции или словесное число не дадут исполнить код. Это же требование отсекает ответы в неудобной форме.
Можно ли запускать сгенерированный код сразу?
Сначала стоит прочитать код построчно, сверить входы и знаки и убедиться в отсутствии файловых, сетевых и процессных операций. Инструкция в промпте не является изолированной средой. Только после проверки запускайте малый фрагмент в своей отдельно подготовленной среде Python.
Что даёт контрольная замена used на 44?
При остальных равных входах ручной эталон даёт 88 вместо 89. Разница в единицу показывает, что формула чувствительна именно к расходу и что знак вычитания стоит верно. Это учебная проверка знаков и порядка операций.
