English translation is not available yet. Showing Russian content.

Как код как язык представления улучшает рассуждение LLM?

Краткий тезис

Код — это формальный язык с однозначной семантикой, исполнимостью и композициональностью. Когда LLM использует код (Python, SQL, псевдокод) как промежуточное представление для рассуждения, она получает возможность проверять свои шаги через выполнение, разбивать сложные задачи на модульные функции и комбинировать рассуждение с формальной верификацией. Это снижает галлюцинации, повышает точность и делает цепочку рассуждения прозрачной и воспроизводимой.

1. Термин: Код как язык представления (Code as Representation)

Код как язык представления — это подход, при котором LLM генерирует программный код (обычно Python) в качестве промежуточного шага перед финальным ответом. Вместо того чтобы рассуждать цепочкой на естественном языке (Chain-of-Thought), модель пишет исполняемые инструкции. Этот метод лежит в основе таких техник, как Program-of-Thought (PoT) и Code Agents.

АспектЕстественный языкКод
СемантикаНеоднозначная, зависит от контекстаСтрого определена синтаксисом и runtime
ПроверяемостьТолько экспертная оценкаАвтоматическое выполнение и тестирование
КомпозициональностьСлабая (сложно формально комбинировать)Сильная (функции, модули, импорты)
МасштабируемостьОграничена длиной контекстаМожет использовать внешние библиотеки и API

2. Исполнимость (Executability)

Главное преимущество кода — его можно запустить и проверить результат. LLM может сгенерировать код, выполнить его в изолированной среде (песочнице) и получить обратную связь: совпадает ли вывод с ожидаемым, возникает ли ошибка.

Пример: Задача: «Сколько секунд в 2.5 часах?»
LLM на естественном языке может ошибиться: «2.5 * 60 * 60 = 9000 секунд» (неверно).
LLM, генерирующая код, напишет:

hours = 2.5
seconds = hours * 3600
print(seconds)

При выполнении получит 9000.0, что неверно (правильно 9000). Но если модель использует execution feedback, она может перепроверить: «2.5 * 3600 = 9000, верно». Однако в данном случае ответ верный, но часто модель может допустить арифметическую ошибку, а выполнение кода её исправит.

Механизм

  • Генерация кода]] → выполнение в песочнице → получение stdout/stderr → повторная генерация с учётом ошибки (Self-Debugging).
  • Это превращает рассуждение в итеративный процесс с обратной связью, что значительно повышает точность на математических и логических задачах.

3. Композициональность (Compositionality)

Код позволяет разбить сложную задачу на модульные функции, которые можно вызывать и комбинировать. LLM может сначала написать вспомогательные функции, а затем использовать их в основной логике.

Пример: Агент для анализа данных получает запрос: «Найди среднюю зарплату в отделе продаж за последний квартал».
Код:

def get_sales_data():
    # вызов API или чтение БД
    pass

def filter_last_quarter(data):
    # фильтрация по дате
    pass

def average_salary(data):
    return sum(d.salary for d in data) / len(data)

sales = get_sales_data()
filtered = filter_last_quarter(sales)
result = average_salary(filtered)

Каждая функция может быть написана, отлажена и переиспользована отдельно. Это соответствует принципу разделяй и властвуй (divide and conquer), который улучшает рассуждение LLM, так как модель фокусируется на одной подзадаче за раз.

4. Четкая семантика (Unambiguous Semantics)

В естественном языке одна фраза может иметь несколько трактовок. Например, «увеличь число на 10%» — это умножить на 1.1 или прибавить 10? В коде семантика однозначна: x * 1.1 или x + 10. Это уменьшает амбигуозность (неоднозначность) и, как следствие, галлюцинации LLM.

Сравнение

  • Естественный язык: «Если число делится на 3, выведи Fizz, иначе если на 5 — Buzz».
  • Код:
if n % 3 == 0:
    print("Fizz")
elif n % 5 == 0:
    print("Buzz")

В коде нет неоднозначности: порядок условий, граничные случаи (например, делится на 3 и 5) явно определены.

5. Формальная верификация (Formal Verification)

Код можно проверять статически (типизация, линтеры) и динамически (юнит-тесты, property-based testing). LLM может генерировать не только код, но и спецификации (контракты, инварианты), которые затем проверяются формальными инструментами.

Пример:

  • LLM пишет функцию сортировки и добавляет assert для проверки:
def sort_list(arr):
    # реализация
    return sorted_arr

# Проверка
assert sort_list([3,1,2]) == [1,2,3]
assert all(sort_list(arr)[i] <= sort_list(arr)[i+1] for i in range(len(arr)-1))
  • Можно использовать формальные верификаторы (например, Z3, Dafny) для доказательства корректности. Это особенно ценно в Agentic RAG, где агенты выполняют код с побочными эффектами (доступ к БД, API).

6. Примеры техник, использующих код

  • Program-of-Thought (PoT) — генерация кода вместо текстовой цепочки рассуждений. Показывает SOTA на математических бенчмарках (GSM8K, MATH).
  • Self-Debugging — LLM выполняет свой код, получает ошибку и исправляет её без внешних сигналов.
  • Code Agents (например, ReAct с кодом) — агент пишет код для вызова инструментов, обрабатывает результаты и планирует следующие шаги.
  • Tool-using LLM — код используется для вызова API, парсинга ответов, агрегации данных.

7. Сравнение с рассуждением на естественном языке

КритерийЕстественный язык (CoT)Код (PoT / Code Agent)
ПроверяемостьТолько человекомАвтоматическая (выполнение)
Точность вычисленийНизкая (ошибки в арифметике)Высокая (использует интерпретатор)
МодульностьСлабаяСильная (функции, классы)
Обработка ошибокНет встроеннойTry/except, отладка
ЗатратыМеньше токеновБольше токенов (код + вывод)
БезопасностьНизкая (нет риска выполнения)Требует песочницы

8. Ограничения и риски

  • Синтаксическая точность: LLM должна генерировать синтаксически корректный код. Ошибки в синтаксисе приводят к сбоям выполнения.
  • Безопасность: Выполнение кода, сгенерированного LLM, может быть опасным (инъекции, бесконечные циклы, доступ к файловой системе). Требуется изолированная среда (Docker, gVisor, Pyodide).
  • Неэффективность для некоторых задач: Для задач, требующих качественного анализа (например, реферирование текста), код не даёт преимуществ.
  • Затраты токенов: Генерация кода и его вывода требует больше токенов, чем короткий текстовый ответ.

9. Интеграция в Agentic RAG

В архитектуре Agentic RAG код используется как универсальный язык для взаимодействия с инструментами:

  • Retrieval: агент пишет код для запроса к векторной БД (например, через API).
  • Reasoning: агент генерирует код для анализа извлечённых документов (фильтрация, агрегация, вычисления).
  • Execution: агент выполняет код, получает результаты и передаёт их в следующий шаг.
  • Verification: агент пишет тесты для проверки корректности своих действий.

Пример пайплайна:

  1. Запрос пользователя → LLM генерирует код для поиска документов.
  2. Выполнение кода → получение чанков.
  3. LLM генерирует код для анализа чанков (например, подсчёт статистики).
  4. Выполнение → финальный ответ.

10. Пет-проект для закрепления

Задача Создать агента, который решает математические задачи из датасета GSM8K, используя код как промежуточное представление.

Инструменты Python, библиотека openai, exec в изолированной среде (например, subprocess с ограничением ресурсов), датасет GSM8K (Hugging Face).

Шаги:

  1. Загрузить датасет и выбрать 10 задач.
  2. Для каждой задачи отправить промпт LLM с инструкцией: «Напиши Python-код для решения задачи. Выведи ответ в конце.»
  3. Выполнить сгенерированный код в песочнице (ограничить время выполнения, запретить опасные модули).
  4. Сравнить вывод с правильным ответом.
  5. Если код упал с ошибкой, передать сообщение об ошибке обратно LLM и попросить исправить (Self-Debugging).
  6. Посчитать accuracy и среднее количество итераций.

Ожидаемый результат Accuracy > 80% на выбранных задачах, понимание, как execution feedback улучшает рассуждение.

11. Связь с другими вопросами

ВопросТема
180Архитектура Agentic RAG
182Использование инструментов (tool use)
186Исполнение кода в агентах
187Формальная верификация в агентах
188Обработка ошибок в агентах
189Оценка агентов и рассуждений

12. Навигация


Навигация