3,583 papers
arXiv:2512.15000 80 16 дек. 2025 г. FREE

Chain-of-Function: модульная генерация кода через функции как шаги рассуждений

КЛЮЧЕВАЯ СУТЬ
LLM генерирует код потоком сознания: всё в одной функции, переменные на куче, логика перемешана. Попросишь разбить постфактум — получишь искусственные границы: модель не знает где «естественный шаг» алгоритма. Chain-of-Function решает это ДО генерации: задаёшь правила организации кода прямо в промпте. Высокоуровневая функция первой (main с выбором подхода), потом вспомогательные (dijkstra(), build_graph()). В каждой функции docstring — объяснение логики. Получаешь модульный код, где каждая функция изолирована и понятна.
Адаптировать под запрос

TL;DR

Chain-of-Function — промпт-стратегия, которая просит LLM организовывать код в независимые функции вместо монолитного скрипта. Сначала высокоуровневая функция (например, main()) с выбором подхода, затем вспомогательные функции для конкретных подзадач. Каждая функция получает docstring с объяснением логики — это Chain-of-Thought для кода.

LLM часто генерирует код "потоком сознания": всё в одном месте, без четкой структуры, сложно отлаживать и понимать логику. Если попросить разбить на функции постфактум — модель не всегда видит естественные границы, функции получаются искусственными. В математике есть Chain-of-Thought (шаг за шагом), в коде такой явной структуры нет — LLM не знает где "шаг".

Chain-of-Function задаёт структуру до генерации: модель сначала решает какой подход использовать (main с выбором алгоритма), потом реализует части (dijkstra(), build_graph()). Получается читаемый модульный код, где каждая функция — изолированный блок логики с объяснением в docstring.


🔬

Схема метода

ПРОМПТ задаёт правила организации кода:

1. Высокоуровневые функции первыми (main, основной алгоритм)
2. Docstring в каждой функции — объяснение подхода и логики
3. Вспомогательные функции следом
4. Избегать вложенных функций — все на верхнем уровне
5. Выносить повторяющуюся логику в отдельные методы

→ LLM генерирует код следуя этой структуре

Выполняется в одном запросе — это инструкции для генерации кода.


🚀

Пример применения

Задача: Ты делаешь Telegram-бота для трекинга привычек. Нужна функция, которая проверяет серии выполнения: если пользователь отметил привычку N дней подряд — бот отправляет поздравление. Входные данные — список дат выполнения привычки, нужно найти текущую серию и максимальную в истории.

Промпт:

Напиши Python-функцию для трекинга серий выполнения привычки.

Следуй правилам организации кода:

1. Высокоуровневую функцию размести первой (основная логика). 
   Вспомогательные функции — после неё.

2. Добавь docstring в каждую функцию (используй тройные кавычки ''').
   В основной функции объясни подход, выбор алгоритма, ключевые шаги.
   Во вспомогательных — конкретную логику и назначение.

3. Выноси логику в отдельные методы, если операции повторяются 
   или вычисления можно изолировать.

4. Избегай вложенных функций — держи все функции на верхнем уровне.

Входные данные: список datetime объектов (даты выполнения).
Вывод: текущая серия и максимальная серия за всё время.

Результат:

Модель выдаст код с чёткой структурой: 1. Функция calculate_streaks(dates) — основная логика с docstring, объясняющим подход (сортировка дат, поиск последовательных серий) 2. Вспомогательные функции типа find_consecutive_days(sorted_dates) и get_current_streak(sorted_dates) с docstrings о конкретной логике 3. Каждая функция изолирована — можно тестировать и отлаживать отдельно 4. Код читается как план решения: сначала видишь "что делаем" (main функция), потом "как делаем" (helpers)


🧠

Почему это работает

LLM обучена на огромном количестве кода из GitHub — там есть и хорошо структурированный код, и спагетти. Без явных инструкций модель может выбрать любой стиль. "Напиши функцию" часто даёт монолитный блок: всё в одной функции, переменные на одном уровне, логика перемешана. Потом сложно понять где ошибка, какая часть за что отвечает.

Chain-of-Function использует сильную сторону LLM — следование структурированным инструкциям. Явно описываешь архитектуру: сначала high-level, потом детали. Модель видит иерархию задачи и переносит её в иерархию функций. Docstrings усиливают эффект: модель объясняет логику перед написанием кода → генерирует более осознанно.

Рычаги управления промптом:

  • Порядок функций — "high-level first" даёт нисходящее проектирование. Если убрать → модель может начать с деталей, потеряв общую картину.

  • Docstrings — если убрать требование docstrings, модель генерирует быстрее, но теряется объяснение логики. Оставь для сложных алгоритмов, убери для простых скриптов.

  • "Избегать вложенных функций" — модель иногда создаёт closures (функция внутри функции). Для коротких скриптов это ОК, для сложной логики — хуже отладка и повторное использование. Замени на "допустимы вложенные функции для локального контекста" если нужны замыкания.

  • Правило "выносить повторяющуюся логику" — увеличивает модульность, но больше функций. Для простых задач можно упростить: "выноси только значительные повторения (3+ раза)".

Меняя эти элементы, контролируешь баланс между структурированностью (больше функций, больше docstrings) и простотой (компактный код, меньше накладных расходов).


📋

Шаблон промпта

Напиши {описание_задачи} на Python.

Следуй правилам организации кода:

1. Функции-решатели размести первыми (основной алгоритм, main логика).
   Вспомогательные функции идут следом.

2. Каждая функция должна иметь docstring в тройных кавычках (''').
   - В основных функциях объясни подход, выбор алгоритма, ключевые шаги.
   - Во вспомогательных функциях опиши конкретную логику и назначение.

3. Выноси логику в отдельные методы, если:
   - Операции повторяются
   - Сложные вычисления можно изолировать
   - Блок логики имеет четкую обязанность

4. Избегай вложенных функций — держи все функции на верхнем уровне.

{дополнительные_требования}

Плейсхолдеры: - {описание_задачи} — что должен делать код (например, "функцию для поиска кратчайшего пути в графе") - {дополнительные_требования} — опционально: формат входных данных, ограничения, требования к производительности


🚀 Быстрый старт — вставь в чат:

Вот шаблон Chain-of-Function для структурированной генерации кода. 
Адаптируй под мою задачу: [опиши свою задачу].

Задай вопросы, чтобы заполнить требования к коду.

[вставить шаблон выше]

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


⚠️

Ограничения

⚠️ Простые скрипты: Для задач на 5-10 строк кода избыточная структура. LLM создаст функции там, где достаточно линейного скрипта — больше букв, не больше ясности.

⚠️ Исследовательский код: Jupyter notebooks, быстрые эксперименты — там модульность часто мешает. Нужна скорость итераций, а не архитектура.

⚠️ Специфичные паттерны: Если задача требует конкретный стиль (например, functional programming с композицией, или ООП с классами) — Chain-of-Function даст процедурный стиль с функциями. Нужно явно указывать парадигму.


🔍

Как исследовали

Команда из UC San Diego решала проблему: Process Reward Models (PRM) — модели, которые оценивают промежуточные шаги решения, а не только финальный ответ — отлично работают в математике (Chain-of-Thought даёт явные шаги), но буксуют в коде. Почему? Нет чёткого определения "шага" в коде. Некоторые используют строку кода как шаг — слишком мелко, дорого считать. Другие оценивают natural language план до кода — игнорируют логику самого кода.

Исследователи предложили функцию = шаг рассуждения. Написали Chain-of-Function промпт, натренировали Qwen-2.5-Coder-3B как PRM модель на 601 задаче с LiveCodeBench (проблемы до августа 2024). Для обучения PRM использовали Qwen3-Coder-30B для генерации кода по Chain-of-Function и Monte-Carlo сэмплирование для меток промежуточных шагов. Добавили мета-обучение для коррекции шумных меток: нижний уровень оптимизации — обучение PRM на промежуточных шагах, верхний уровень — проверка на чистых метках финальных решений (unit tests). Если PRM после обучения на "зашумлённых" промежуточных метках плохо работает на финале — метки корректируются.

Тестировали на 131 задаче после февраля 2025 (строгое временное разделение, никакого пересечения). Результат: DreamPRM-Code достиг 80.9% pass@1 на LiveCodeBench — обогнал OpenAI o4-mini (77.1%) и другие топовые модели. Применяли test-time scaling: o4-mini генерирует 4 кандидата, PRM выбирает лучший по средней оценке шагов.

Почему сработало: Chain-of-Function даёт PRM понятные единицы для оценки (функции), а мета-обучение очищает шумные метки через обратную связь от реальных тестов. Абляция показала: без коррекции меток (PRM-CoF) — 80.2%, с коррекцией (DreamPRM-Code) — 80.9%. Небольшой прирост, но стабильный, особенно на сложных задачах (Hard: 62.3% → 63.9%).


💡

Адаптации и экстраполяции

📌

🔧 Техника: Явный вывод шагов → прозрачность рассуждений

Оригинальный Chain-of-Function фокусируется на структуре кода. Добавь явное требование "объясни промежуточные состояния":

Напиши {описание_задачи} на Python.

Следуй правилам организации кода:

1. Основная функция первой, вспомогательные — после.

2. В docstring основной функции распиши пошаговый план:
   """
   Шаг 1: [что делаем]
   Шаг 2: [что делаем]
   ...
   """

3. В конце каждой вспомогательной функции добавь print-statement 
   с описанием результата:
   print(f"build_graph completed: {num_nodes} nodes, {num_edges} edges")

4. Выноси логику в отдельные методы для сложных вычислений.

Эффект: Видишь не только финальный код, но и промежуточные результаты при выполнении. Удобно для отладки и понимания потока данных.


📌

🔧 Техника: Персонажи-агенты → критика кода

Chain-of-Function создаёт структуру. Добавь мультиагентную критику для проверки:

Напиши код для {задача} следуя Chain-of-Function структуре 
(высокоуровневые функции первыми, docstrings, модульность).

После генерации кода:

Агент "Оптимизатор":
- Проверь сложность алгоритма каждой функции
- Найди узкие места (вложенные циклы, повторные вычисления)
- Предложи оптимизации с оценкой прироста скорости

Агент "Тестировщик":
- Найди edge cases для каждой функции
- Предложи unit-тесты с pytest
- Проверь обработку ошибок (пустые входные данные, None, некорректные типы)

Выведи финальную версию кода с учётом замечаний.

Эффект: Модель симулирует peer review — генерирует код, потом критикует с двух ракурсов, потом улучшает. Получаешь не просто структурированный код, но и протестированный с покрытием edge cases.


🔗

Ресурсы

DreamPRM-Code: Function-as-Step Process Reward Model with Label Correction for LLM Coding | Project Page | Ruiyi Zhang, Peijia Qin, Qi Cao, Pengtao Xie | University of California, San Diego

Related: LiveCodeBench (бенчмарк для code generation), Chain-of-Thought prompting (Wei et al., 2022), Process Reward Models (PRMs)


📋 Дайджест исследования

Ключевая суть

LLM генерирует код потоком сознания: всё в одной функции, переменные на куче, логика перемешана. Попросишь разбить постфактум — получишь искусственные границы: модель не знает где «естественный шаг» алгоритма. Chain-of-Function решает это ДО генерации: задаёшь правила организации кода прямо в промпте. Высокоуровневая функция первой (main с выбором подхода), потом вспомогательные (dijkstra(), build_graph()). В каждой функции docstring — объяснение логики. Получаешь модульный код, где каждая функция изолирована и понятна.

Принцип работы

Не генерируй код, потом разбивай → задавай структуру до генерации. Правила для LLM: 1) Главная функция первой, 2) Docstring в каждой — объяснение подхода, 3) Вспомогательные следом, 4) Без вложенности, 5) Повторяющееся — в отдельный метод. Модель видит иерархию задачи → переносит в иерархию функций. Фишка: docstrings заставляют объяснить логику ДО кода — генерация становится осознанной, как Chain-of-Thought для математики.

Почему работает

LLM обучена на GitHub — там и хорошо структурированный код, и месиво. Без инструкций модель выбирает случайно. «Напиши функцию» часто даёт монолитный блок: всё внутри, непонятно где логические границы. Chain-of-Function использует сильную сторону LLM — следование чётким инструкциям по структуре. Явно описываешь архитектуру: сначала high-level, потом детали. Модель переносит это в код. Docstrings усиливают эффект: объясняет подход → пишет код под объяснение, а не наоборот.

Когда применять

Для задач где нужен читаемый и отлаживаемый код — API эндпоинты, обработка данных, алгоритмы для продакшн. Особенно полезно когда планируешь дорабатывать код вручную или передавать команде. НЕ подходит для быстрых скриптов на 5-10 строк (избыточная структура) и исследовательского кода в Jupyter (модульность замедляет эксперименты).

Мини-рецепт

1. Задай правила организации в промпте: Высокоуровневые функции первыми, docstring в каждой функции (подход и логика), вспомогательные следом, без вложенности, повторяющееся выносить.

2. Укажи требования к docstrings: В основных функциях — выбор алгоритма и ключевые шаги. Во вспомогательных — конкретное назначение.

3. Опиши задачу и данные: Что должен делать код, формат входа/выхода, ограничения.

4. Настрой баланс структура/простота: Для сложных алгоритмов оставь все правила. Для средних задач смягчи: «выноси повторения если встречаются 3+ раза».

Примеры

[ПЛОХО] : Напиши функцию для проверки серий выполнения привычки (Получишь монолитный блок на 50 строк, где логика подсчёта текущей серии, максимальной серии и обработка дат перемешаны)
[ХОРОШО] : Напиши функцию для трекинга серий выполнения привычки. Правила: 1) Главная функция calculate_streaks() первой с docstring о подходе, 2) Вспомогательные функции find_consecutive_days() и get_current_streak() с docstrings о логике, 3) Без вложенности, все на верхнем уровне. Вход: список datetime. Выход: текущая серия и максимальная. (Получишь чистый модульный код: основная функция с планом решения → изолированные helpers → каждую часть можно тестировать отдельно)
Источник: DreamPRM-Code: Function-as-Step Process Reward Model with Label Correction for LLM Coding
ArXiv ID: 2512.15000 | Сгенерировано: 2026-01-08 22:16

Методы

МетодСуть
Правила структуры кода — модульность по инструкцииДай модели явные правила организации кода ДО генерации. Правила: 1) Главную функцию размести первой (high-level логика), вспомогательные — после. 2) Каждая функция получает docstring с объяснением подхода и логики. 3) Повторяющиеся операции выноси в отдельные функции. 4) Избегай вложенных функций — всё на верхнем уровне. Почему работает: LLM обучена на коде разного качества. Без инструкций может выдать монолит или спагетти-код. Явные правила активируют паттерн чистого кода. Docstrings работают как "подумай вслух" — модель объясняет логику перед генерацией, пишет осознаннее. Когда да: сложные алгоритмы (50+ строк), нужна читаемость и отладка, код будут поддерживать. Когда нет: простые скрипты 5-10 строк, исследовательский код в Jupyter, нужны специфичные паттерны (ООП, функциональщина)

Тезисы

ТезисКомментарий
Структура ДО генерации эффективнее рефакторинга ПОСЛЕЕсли попросить модель сначала написать код, потом разбить на функции — она не всегда видит естественные границы. Функции получаются искусственными, логика размазана. Если задать структуру сразу ("главная функция первая, вспомогательные следом") — модель генерирует код уже модульным. Разбивка происходит на этапе планирования, а не постобработки. Применяй: Вместо "напиши код, потом разбей на функции" пиши "напиши код где главная функция идёт первой, вспомогательные после неё, каждая с docstring"
📖 Простыми словами

Chain-of-Function: модульная генерация кода через функции как шаги рассуждений

arXiv: 2512.15000

Суть метода DreamPRM-Code в том, что нейронки пишут код гораздо лучше, когда их заставляют дробить задачу на мелкие части. Вместо того чтобы вываливать на тебя огромный монолитный кусок текста, модель использует стратегию Chain-of-Function. Это как если бы ты попросил построить дом, и строитель сначала нарисовал общую схему, а потом по отдельности спроектировал фундамент, стены и крышу. Каждая функция становится независимым шагом, где логика прописана в комментариях, что по сути является Chain-of-Thought для кода.

Это как пытаться собрать сложный шкаф из Икеи без инструкции, имея на руках только гору досок и шурупов. Если ты начнешь крутить всё подряд, в конце обязательно останутся лишние детали, а конструкция развалится. Chain-of-Function — это та самая инструкция, где каждый этап сборки изолирован. Ты не переходишь к дверцам, пока не убедился, что каркас стоит ровно. Формально код и так бы работал, но без структуры он превращается в хрупкое спагетти, которое ломается от любого чиха.

В основе лежат три вещи: высокоуровневое планирование (сначала пишем main, где выбираем алгоритм), вспомогательные подзадачи (каждая мелкая проблема — отдельная функция) и документирование логики (docstrings). Если тебе нужно написать бота для трекинга привычек, модель не вывалит сто строк каши. Она создаст функцию для расчета дат, отдельную функцию для поздравлений и главную логику, которая ими управляет. Такой подход заставляет LLM «думать» об интерфейсах и связях, а не просто гадать следующее слово в строке.

Хотя метод тестировали на кодинге, принцип универсален для любой сложной работы с данными или текстами. Это работает везде, где нужно превратить хаос в систему: от написания сложных юридических договоров до создания многошаговых маркетинговых воронок. Модульность побеждает монолит, потому что нейронке проще контролировать качество маленького куска, чем удерживать в памяти контекст огромного проекта. Если заставить AI работать по кусочкам, количество ошибок падает в разы.

Короче: хватит просить нейронку «просто написать код» — заставляй её использовать Chain-of-Function. Когда модель сначала планирует структуру, а потом реализует её через независимые функции, она перестает лажать на сложных поворотах. Структурированный код — это предсказуемый результат. Кто продолжает кормить AI запросами на монолитные скрипты, тот обречен бесконечно дебажить мусор на выходе.

Работа с исследованием

Адаптируйте исследование под ваши задачи или создайте готовый промпт на основе техник из исследования.

0 / 2000
~0.5-2 N-токенов ~10-30с
~0.3-1 N-токенов ~5-15с