3,583 papers
arXiv:2508.06942 78 9 авг. 2025 г. FREE

CNL-P: контролируемый естественный язык для промптов с формальной грамматикой

КЛЮЧЕВАЯ СУТЬ
Усложняешь промпт — добавляешь переменные, условия, API-вызовы — и всё катится в кашу. Модель путает где что определено, пропускает условия, неправильно передаёт данные между шагами. CNL-P решает через формализацию: промпт структурируется как код с явными секциями, типами переменных и синтаксисом. Есть PERSONA (роль), CONSTRAINTS (ограничения), TYPES (типы данных), VARIABLES (переменные), WORKER (workflow с командами). Впервые промпты можно проверять linting-инструментом — как Python или JavaScript. Ловит синтаксические ошибки, несуществующие переменные, неправильные типы. 100% точность против 76% у GPT-4o.
Адаптировать под запрос

TL;DR

CNL-P — формальный язык для промптов с точной грамматикой (BNF-нотация) и строгой семантикой. Язык структурирует промпт через явные секции: PERSONA (роль), CONSTRAINTS (ограничения), TYPES (типы данных), VARIABLES (переменные), WORKER (workflow с командами и условиями). Это как писать код, но естественным языком — есть синтаксис, типы, модули.

Исследователи обнаружили что обычные NL-промпты теряют в модульности и строгости процесса. Когда промпт усложняется — добавляются переменные, условия, API-вызовы — неструктурированный текст превращается в кашу. Непонятно где что определено, какие типы у переменных, как данные передаются между шагами. Модель может неправильно интерпретировать неявные связи.

CNL-P решает через формализацию: каждая сущность (переменная, тип, команда) явно определена с указанием типа и позиции в workflow. Модель видит чёткую структуру — какие данные входят, какие выходят, какие условия проверяются. Плюс есть linting tool — статический анализ промптов как код (проверка синтаксиса, типов, несуществующих переменных). Это первая работа, где NL-промпты проверяются программно.

🔬

Схема метода

CNL-P определяет агента через иерархию секций:

[DEFINE_AGENT: ИМЯ_АГЕНТА]
 
 [DEFINE_PERSONA:]
 ROLE: описание роли
 [Опциональные аспекты]
 [END_PERSONA]
 
 [DEFINE_CONSTRAINTS:]
 Ограничение_1: описание
 Ограничение_2: описание
 [END_CONSTRAINTS]
 
 [DEFINE_TYPES:]
 СложныйТип = {поле_1: тип, поле_2: тип}
 [END_TYPES]
 
 [DEFINE_VARIABLES:]
 "описание" переменная_1: тип
 "описание" переменная_2: тип
 [END_VARIABLES]
 
 [DEFINE_WORKER: ВОРКЕР_ИМЯ]
 [INPUTS] <REF>входная_переменная [END_INPUTS]
 [OUTPUTS] <REF>выходная_переменная [END_OUTPUTS]
 
 [MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-1 [COMMAND действие RESULT переменная: тип]
 COMMAND-2 [CALL api_имя WITH {параметр: значение}]
 [END_SEQUENTIAL_BLOCK]
 
 [IF условие]
 COMMAND-3 [COMMAND действие]
 [END_IF]
 [END_MAIN_FLOW]
 [END_WORKER]
 
[END_AGENT]

Ключевые элементы:

  • переменная — ссылка на переменную
  • RESULT переменная: тип — сохранение результата команды
  • CALL api_имя WITH {...} — вызов API
  • IF-блоки для условной логики
  • Явное указание типов: text, number, boolean, сложные структуры

Всё это в одном промпте — LLM интерпретирует как инструкции.

🚀

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

⚠️ Ограничения метода: CNL-P эффективен для сложных multi-step задач с явными данными, условиями, API-вызовами. Не подходит для простых one-shot вопросов ("напиши стих") — избыточная формализация. Требует понимания структуры для создания/проверки промпта.

Задача: Ты — предприниматель, запускаешь сервис доставки завтраков в Москве. Нужен агент, который анализирует конкурентов (парсит Яндекс.Еду через API), твои финансы (берёт из Google Sheets), и выдаёт рекомендацию: в какой район выходить первым.

Промпт (упрощённая версия CNL-P):

[DEFINE_AGENT: РынокАнализатор]

 [DEFINE_PERSONA:]
 ROLE: Ты аналитик-стратег для стартапов в food-tech. 
 Специализируешься на анализе локальных рынков доставки еды в России.
 [END_PERSONA]

 [DEFINE_CONSTRAINTS:]
 География: Только Москва, районы внутри МКАД
 Бюджет: Рекомендация должна учитывать стартовый капитал пользователя
 Данные: Используй только проверенные источники (API Яндекс.Еды, Google Sheets)
 [END_CONSTRAINTS]

 [DEFINE_TYPES:]
 Район = {
 название: text,
 конкуренты: number,
 средний_чек: number,
 плотность_населения: number
 }
 
 Финансы = {
 стартовый_капитал: number,
 месячные_расходы: number
 }
 [END_TYPES]

 [DEFINE_VARIABLES:]
 "Список районов Москвы для анализа" районы: List[Район]
 "Финансовые данные стартапа" финансы: Финансы
 "Итоговая рекомендация" рекомендация: text
 [END_VARIABLES]

 [DEFINE_WORKER: АнализРайонов]
 [INPUTS] 
 <REF>районы
 <REF>финансы
 [END_INPUTS]
 
 [OUTPUTS] 
 <REF>рекомендация
 [END_OUTPUTS]

 [MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-1 [CALL yandex_eda_api WITH {районы: <REF>районы} 
 RESPONSE конкуренты_данные: List[Район] SET]
 
 COMMAND-2 [CALL google_sheets_api WITH {sheet_id: "финансы"} 
 RESPONSE <REF>финансы SET]
 
 COMMAND-3 [COMMAND Для каждого района из <REF>конкуренты_данные 
 посчитай индекс привлекательности: 
 (плотность_населения * средний_чек) / (конкуренты + 1)
 RESULT индексы: List[number] SET]
 [END_SEQUENTIAL_BLOCK]
 
 [IF <REF>финансы.стартовый_капитал < 1000000]
 COMMAND-4 [COMMAND Выбери районы где индекс > 70 И конкуренты < 5
 RESULT топ_районы: List[Район] SET]
 [ELSE]
 COMMAND-5 [COMMAND Выбери районы где индекс > 50
 RESULT топ_районы: List[Район] SET]
 [END_IF]
 
 [SEQUENTIAL_BLOCK]
 COMMAND-6 [COMMAND Сгенерируй рекомендацию на основе <REF>топ_районы:
 - Топ-3 района
 - Обоснование выбора каждого
 - Оценка стартовых инвестиций
 - Риски и возможности
 RESULT <REF>рекомендация SET]
 
 COMMAND-7 [DISPLAY <REF>рекомендация]
 [END_SEQUENTIAL_BLOCK]
 [END_MAIN_FLOW]
 [END_WORKER]

[END_AGENT]

Результат:

Модель выполнит последовательность действий:

  1. Вызовет API Яндекс.Еды (симуляция) для получения данных по районам
  2. Загрузит финансы из Google Sheets (симуляция)
  3. Посчитает индекс привлекательности для каждого района
  4. В зависимости от бюджета (<1M или >1M рублей) отфильтрует районы по разным критериям
  5. Сгенерирует структурированную рекомендацию: топ-3 района с обоснованием, оценкой инвестиций и рисков
  6. Выведет финальную рекомендацию

Вся логика — условия, вызовы API, передача данных — явно прописана в промпте. Модель не додумывает что делать, а следует чёткому workflow.

🧠

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

Слабость LLM: Неструктурированный текст промпта допускает множественные интерпретации. Когда в промпте смешаны роль, ограничения, переменные, условия — модель может:

  • Неправильно понять какие данные нужны на входе
  • Пропустить проверку условия
  • Использовать переменную до её определения
  • Перепутать типы данных (текст вместо числа)

Чем сложнее промпт, тем выше риск. LLM "читает" текст линейно, но структура workflow не очевидна.

Сильная сторона LLM: Модели отлично следуют явным структурированным инструкциям. Если чётко сказать "сначала вызови API_A, результат сохрани в переменную X типа число, потом если X > 100 то сделай Y" — модель выполнит точно. LLM любит когда границы модулей выделены, типы указаны, порядок действий пронумерован.

Как CNL-P использует сильную сторону:

  1. Формальная структура устраняет двусмысленность[DEFINE_PERSONA:]...[END_PERSONA] явно отделяет роль от инструкций. Модель знает "это описание роли, это не команда к действию".
  2. Явные типы предотвращают ошибки — если переменная budget: number, модель не попытается записать туда текст. Это как type hints в Python — подсказка для интерпретатора.
  3. Ссылки <REF> делают зависимости видимыми — когда команда использует variable, модель понимает "эта переменная должна быть определена ранее". Как import в коде.
  4. Sequential/IF блоки задают порядок выполнения — модель не додумывает что делать дальше, а следует workflow. Как функция с чёткими шагами.

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

  • Секции PERSONA/CONSTRAINTS → меняй роль и ограничения под задачу (аналитик vs креативщик)
  • Типы в TYPES/VARIABLES → добавь сложные структуры если данные многоуровневые
  • IF-условия → настрой логику ветвления (пороги, критерии)
  • COMMAND-индексы → измени порядок шагов
  • API-вызовы → замени реальные API на симуляцию для тестирования
  • RESULT SET/APPEND → контролируй как сохраняются промежуточные результаты
📋

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

[DEFINE_AGENT: {имя_агента}]

 [DEFINE_PERSONA:]
 ROLE: {описание роли и экспертизы}
 {дополнительные_аспекты}: {описание}
 [END_PERSONA]

 [DEFINE_CONSTRAINTS:]
 {ограничение_1}: {что нельзя или обязательно}
 {ограничение_2}: {что нельзя или обязательно}
 [END_CONSTRAINTS]

 [DEFINE_TYPES:]
 {СложныйТип} = {
 {поле_1}: {тип},
 {поле_2}: {тип}
 }
 [END_TYPES]

 [DEFINE_VARIABLES:]
 "{описание назначения}" {переменная_1}: {тип}
 "{описание назначения}" {переменная_2}: {тип}
 [END_VARIABLES]

 [DEFINE_WORKER: {имя_воркера}]
 [INPUTS] 
 <REF>{входная_переменная}
 [END_INPUTS]
 
 [OUTPUTS] 
 <REF>{выходная_переменная}
 [END_OUTPUTS]

 [MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-1 [COMMAND {описание действия} RESULT {переменная}: {тип} SET]
 COMMAND-2 [CALL {api_имя} WITH {{параметр}: {значение}} RESPONSE {переменная}: {тип} SET]
 [END_SEQUENTIAL_BLOCK]
 
 [IF {условие с <REF>переменная}]
 COMMAND-3 [COMMAND {действие при истине}]
 [ELSEIF {другое условие}]
 COMMAND-4 [COMMAND {действие при другой истине}]
 [ELSE]
 COMMAND-5 [COMMAND {действие по умолчанию}]
 [END_IF]
 
 [SEQUENTIAL_BLOCK]
 COMMAND-6 [DISPLAY {что показать пользователю}]
 [END_SEQUENTIAL_BLOCK]
 [END_MAIN_FLOW]
 [END_WORKER]

[END_AGENT]

Что подставлять:

  • {имя_агента} — краткое имя без пробелов (МаркетАнализатор)
  • {описание роли} — кто этот агент, какая экспертиза
  • {ограничение} — формат, лимиты, запреты
  • {СложныйТип} — если данные структурированы (например, Клиент с полями имя/возраст/email)
  • {переменная}: {тип} — имя переменной и её тип (text, number, boolean, List[тип], или ваш СложныйТип)
  • {api_имя} — имя API для вызова (может быть фиктивным, модель симулирует)
  • переменная — ссылка на ранее определённую переменную
  • RESULT переменная: тип SET — сохранение результата команды в переменную

Упрощённые типы:

  • text — текст
  • number — число
  • boolean — true/false
  • List[тип] — список элементов типа

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

Вот шаблон CNL-P для создания формализованного агента. Адаптируй его под мою задачу: [твоя задача]. 
Задавай вопросы, чтобы заполнить все секции корректно.

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

LLM спросит какие входные данные нужны, какие API вызывать, какая логика ветвления — потому что CNL-P требует явного определения всех элементов workflow. Модель возьмёт структуру шаблона и заполнит конкретными значениями под твою задачу.

Важно: CNL-P — это не просто текстовый шаблон, это формальный язык с синтаксисом. Модель интерпретирует [DEFINE_PERSONA:] не как текст, а как инструкцию "следующий блок — определение роли". Поэтому структура критична — не меняй ключевые слова в [ ].

⚠️

Ограничения

⚠️ Сложность для новичков: Полный синтаксис CNL-P требует понимания формальных языков (BNF-грамматика). Не-программисты могут столкнуться с барьером при создании промптов с нуля. Linting tool требует технических навыков для интерпретации ошибок.

⚠️ Избыточность для простых задач: Для one-shot вопросов ("перефразируй текст", "предложи идеи") формализация CNL-P избыточна. Проще использовать обычный NL-промпт. CNL-P оправдан когда нужны условия, API-вызовы, многошаговая логика.

⚠️ Зависимость от transformer agent: Для конвертации NL → CNL-P нужен специальный агент (в исследовании использовали LLM с промптом-конвертером). Без него создание CNL-P вручную трудозатратно. При этом конвертер может не всегда корректно интерпретировать сложные требования.

⚠️ Linting tool не в чате: Статический анализ (проверка типов, синтаксиса) работает вне ChatGPT/Claude — нужен отдельный инструмент. В обычном чате LLM может пропустить ошибки в CNL-P структуре. Linting показал 100% точность vs 76% у GPT-4o, но это внешний инструмент.

⚠️ Не заменяет код для реальной интеграции: CNL-P описывает workflow, но не выполняет реальные API-вызовы. Для интеграции с Яндекс.Едой, Google Sheets нужен код на Python/JS. CNL-P — это "спецификация агента", не исполняемая программа (пока).

🔍

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

Исследователи взяли 93 популярных промпта из GitHub (репозиторий "Awesome ChatGPT Prompts" с 113K звёзд) и конвертировали каждый в три формата: CNL-P, RISEN (известный NL-шаблон), RODES (другой NL-шаблон). Затем оценили по пяти критериям: соответствие оригиналу, модульность, расширяемость, читаемость, строгость процесса.

Три группы оценивали независимо:

  1. GPT-4o — без объяснений про CNL-P, просто оценка по критериям (0-100 баллов)
  2. Техническая группа — 4 программиста с 4+ годами опыта (low/medium/high оценка на 30 промптах)
  3. Нетехническая группа — 2 человека из образования и бизнеса (тоже low/medium/high)

Результаты удивили: CNL-P намного опережал RISEN и RODES в техгруппе по модульности (+30-40%) и строгости процесса (+25-35%). Интересно, что нетехническая группа оценила CNL-P ближе к LLM — обе группы видели преимущества структурированности, но технари ценили это сильнее. GPT-4o показал среднее мнение между двумя группами.

Почему CNL-P выиграл: Технари отметили что RISEN сужает область применения агента из-за секции "Expectation" (ожидания) — она делает агента заточенным под конкретную задачу. CNL-P же определяет general-purpose агента с чётким workflow, который можно переиспользовать. Нетехнари снизили оценку CNL-P за читаемость — им потребовалось время на понимание TYPES и VARIABLES, хотя структура визуально понравилась.

Вторая часть: Проверили понимает ли LLM CNL-P без объяснений. Взяли 6 classification tasks (от простых до сложных, включая математические задачи) из датасета Natural Instructions. Прогнали через 5 моделей (GPT-4o, Gemini-1.5-Pro, GPT-4o-Mini, Llama3-70B, Claude-3-Haiku) с промптами в 4 форматах: NL, CNL-P, RISEN, RODES.

Вывод: CNL-P показал сопоставимые результаты с NL (разница <5% по accuracy почти везде). Это значит LLM понимает структуру CNL-P интуитивно — не нужны Few-Shot примеры или объяснения синтаксиса. Более того, на сложных задачах (математика, многошаговый reasoning) CNL-P иногда превосходил NL за счёт явного workflow.

Третья часть: Тестировали linting tool — статический анализатор CNL-P. Создали 47 тестовых случаев с ошибками (неправильные типы, несуществующие переменные, нарушение синтаксиса). Сравнили точность linting tool vs GPT-4o в обнаружении ошибок.

Результат шокирует: Linting tool — 100% точность, 0% false positives. GPT-4o — 76.6% точность, 19% false positives (придумывал ошибки которых не было). Причина: linting работает программно (парсинг JSON-структуры CNL-P, проверка типов через Pydantic), а LLM "додумывает" из-за вероятностной природы.

Инсайт для практики: Это первая работа о статическом анализе NL-промптов. Показывает что промпты можно проверять как код — до отправки в модель. Снижает риск ошибок в продакшене (когда промпт — часть критического workflow).

📄

Оригинал из исследования

Контекст: Исследователи дали пример CNL-P агента для фитнес-ассистента. Это полный синтаксис CNL-P — дословно из paper (Figure 15 в Appendix).

[DEFINE_AGENT: FITNESS_ADVISOR]
 [DEFINE_PERSONA:]
 ROLE: Fitness and health assistant providing workout routines and diet plans.
 EXPERTISE: Exercise science, nutrition, wellness coaching
 GUIDELINES: Share only safe and verified health tips, avoiding harmful content
 [END_PERSONA]

 [DEFINE_CONSTRAINTS:]
 SAFETY: No extreme diets or dangerous exercises
 SCOPE: Focus on general fitness, not medical diagnosis
 LANGUAGE: Clear, encouraging, non-judgmental
 [END_CONSTRAINTS]

 [DEFINE_TYPES:]
 UserAccountFitness = {
 age: number,
 weight: number,
 height: number,
 fitness_goal: [lose_weight, gain_muscle, maintain, improve_endurance],
 activity_level: [sedentary, lightly_active, moderately_active, very_active]
 }
 
 WorkoutPlan = {
 duration_weeks: number,
 exercises: List[text],
 frequency_per_week: number
 }
 
 DietPlan = {
 daily_calories: number,
 macros: {protein: number, carbs: number, fats: number},
 meal_suggestions: List[text]
 }
 [END_TYPES]

 [DEFINE_VARIABLES:]
 "User's fitness profile" _user_account_fitness: UserAccountFitness
 "Generated workout plan" _workout_plan: WorkoutPlan
 "Generated diet plan" _diet_plan: DietPlan
 [END_VARIABLES]

 [DEFINE_WORKER: FitnessPlanGenerator]
 [INPUTS]
 <REF>_user_account_fitness
 [END_INPUTS]
 
 [OUTPUTS]
 <REF>_workout_plan
 <REF>_diet_plan
 [END_OUTPUTS]

 [MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-1 [CALL get_workout_plan WITH {
 user: <REF>_user_account_fitness,
 preference: "home_workout"
 } RESPONSE <REF>_workout_plan SET]
 
 COMMAND-2 [CALL get_diet_plan WITH {
 user: <REF>_user_account_fitness,
 preference: "balanced"
 } RESPONSE <REF>_diet_plan SET]
 [END_SEQUENTIAL_BLOCK]
 
 [IF <REF>_user_account_fitness.fitness_goal == lose_weight]
 COMMAND-3 [COMMAND Adjust <REF>_diet_plan.daily_calories to 
 create 500 calorie deficit based on <REF>_user_account_fitness.weight
 RESULT <REF>_diet_plan SET]
 [ELSEIF <REF>_user_account_fitness.fitness_goal == gain_muscle]
 COMMAND-4 [COMMAND Increase <REF>_diet_plan.macros.protein to 
 2g per kg of <REF>_user_account_fitness.weight
 RESULT <REF>_diet_plan SET]
 [END_IF]
 
 [SEQUENTIAL_BLOCK]
 COMMAND-5 [DISPLAY Workout plan: <REF>_workout_plan]
 COMMAND-6 [DISPLAY Diet plan: <REF>_diet_plan]
 COMMAND-7 [DISPLAY Motivational message based on <REF>_user_account_fitness.fitness_goal]
 [END_SEQUENTIAL_BLOCK]
 [END_MAIN_FLOW]
 [END_WORKER]
[END_AGENT]

Ключевые элементы оригинала:

  • Энумы в типахfitness_goal: [lose_weight, gain_muscle, ...] задаёт allowed values
  • Вложенные структурыmacros: {protein: number, ...} внутри DietPlan
  • Условная логика по значениюIF variable == value проверяет конкретное значение
  • Математические операции — "создай дефицит 500 калорий" в COMMAND-3
  • API-симуляцияCALL get_workout_plan может быть реальным API или просто инструкцией модели сгенерировать план

Это production-ready структура — можно копировать, менять типы/переменные под свою задачу, добавлять новые COMMAND или IF-блоки.

💡

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

💡 Адаптация: CNL-P для личных финансов

Та же структура, другая задача — управление личным бюджетом. Агент анализирует траты (из банковского API), категоризует, выдаёт рекомендации.

[DEFINE_AGENT: БюджетАнализатор]
 [DEFINE_PERSONA:]
 ROLE: Финансовый советник для личных финансов
 EXPERTISE: Бюджетирование, анализ трат, финансовое планирование
 [END_PERSONA]

 [DEFINE_TYPES:]
 Транзакция = {
 дата: text,
 сумма: number,
 категория: [еда, транспорт, развлечения, жильё, другое],
 описание: text
 }
 
 Бюджет = {
 месяц: text,
 доход: number,
 траты_по_категориям: {еда: number, транспорт: number, ...},
 экономия: number
 }
 [END_TYPES]

 [DEFINE_VARIABLES:]
 "Список транзакций за месяц" транзакции: List[Транзакция]
 "Анализ бюджета" бюджет: Бюджет
 "Рекомендации" советы: text
 [END_VARIABLES]

 [DEFINE_WORKER: АнализБюджета]
 [MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-1 [CALL bank_api WITH {month: "текущий"} 
 RESPONSE <REF>транзакции SET]
 
 COMMAND-2 [COMMAND Для каждой транзакции из <REF>транзакции 
 суммируй по категориям
 RESULT <REF>бюджет.траты_по_категориям SET]
 [END_SEQUENTIAL_BLOCK]
 
 [IF <REF>бюджет.траты_по_категориям.еда > 30000]
 COMMAND-3 [COMMAND Создай совет: "Траты на еду превышают норму (30к₽). 
 Рассмотри готовку дома вместо доставки."
 RESULT <REF>советы APPEND]
 [END_IF]
 
 [SEQUENTIAL_BLOCK]
 COMMAND-4 [DISPLAY Бюджет: <REF>бюджет]
 COMMAND-5 [DISPLAY Советы: <REF>советы]
 [END_SEQUENTIAL_BLOCK]
 [END_MAIN_FLOW]
 [END_WORKER]
[END_AGENT]

Изменения: Другие типы (Транзакция, Бюджет), другой API (bank_api), другая логика условий (проверка лимитов по категориям). Структура та же — PERSONA, TYPES, VARIABLES, WORKER с IF-блоками.

🔧 Техника: Добавить DEBUG-режим → видеть промежуточные шаги

По умолчанию CNL-P показывает только финальный результат (команды с DISPLAY). Чтобы видеть каждый шаг — добавь DISPLAY после каждой команды с RESULT.

[SEQUENTIAL_BLOCK]
 COMMAND-1 [CALL yandex_eda_api WITH {...} RESPONSE данные: List[Район] SET]
 COMMAND-1.1 [DISPLAY DEBUG: Получено районов: <REF>данные]
 
 COMMAND-2 [COMMAND Посчитай индексы RESULT индексы: List[number] SET]
 COMMAND-2.1 [DISPLAY DEBUG: Индексы: <REF>индексы]
[END_SEQUENTIAL_BLOCK]

Эффект: LLM будет выводить промежуточные результаты после каждой команды. Полезно для отладки сложных workflow — видишь где именно логика ломается.

🔧 Техника: Сделать условия гибкими → параметризация порогов

Жёсткие пороги в IF (> 1000000) неудобны — каждый раз менять промпт. Вынеси в переменные:

[DEFINE_VARIABLES:]
 "Порог бюджета для разных стратегий" порог_бюджета: number
 "Минимальный индекс района" мин_индекс: number
[END_VARIABLES]

[IF <REF>финансы.стартовый_капитал < <REF>порог_бюджета]
 COMMAND-4 [COMMAND Выбери районы где индекс > <REF>мин_индекс]
[END_IF]

Эффект: Теперь достаточно изменить значения порог_бюджета и мін_индекс в INPUTS — логика IF подстраивается автоматически. Переиспользование промпта для разных сценариев без изменения workflow.

🔧 Техника: Добавить валидацию входных данных → защита от ошибок

CNL-P не проверяет корректность входных данных по умолчанию. Добавь VALIDATION_BLOCK в начало MAIN_FLOW:

[MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-0 [COMMAND Проверь что <REF>финансы.стартовый_капитал > 0 
 И <REF>районы содержит хотя бы 1 элемент
 RESULT валидация: boolean SET]
 [END_SEQUENTIAL_BLOCK]
 
 [IF <REF>валидация == false]
 COMMAND-ERR [DISPLAY Ошибка: некорректные входные данные]
 [EXIT]
 [END_IF]
 
 [SEQUENTIAL_BLOCK]
 ... # основная логика
 [END_SEQUENTIAL_BLOCK]
[END_MAIN_FLOW]

Эффект: Агент не выполнит workflow если входные данные битые. Early exit вместо краша посередине.


💡 Экстраполяция: Комбинация CNL-P + Memory для long-running агентов

CNL-P описывает stateless workflow — каждый запуск независим. Но можно добавить memory через специальную переменную _session_state:

[DEFINE_TYPES:]
 СостояниеСессии = {
 история_запросов: List[text],
 контекст: text,
 предыдущие_рекомендации: List[text]
 }
[END_TYPES]

[DEFINE_VARIABLES:]
 "Состояние между запусками" _session_state: СостояниеСессии
[END_VARIABLES]

[MAIN_FLOW]
 [SEQUENTIAL_BLOCK]
 COMMAND-1 [COMMAND Загрузи <REF>_session_state из хранилища
 RESULT <REF>_session_state SET]
 
 COMMAND-2 [COMMAND Добавь текущий запрос в <REF>_session_state.история_запросов
 RESULT <REF>_session_state SET]
 
 ... # основная логика с учётом контекста
 
 COMMAND-N [COMMAND Сохрани обновлённый <REF>_session_state в хранилище]
 [END_SEQUENTIAL_BLOCK]
[END_MAIN_FLOW]

Эффект: Агент помнит предыдущие взаимодействия. Например, фитнес-ассистент видит что пользователь уже спрашивал про диету → не повторяет информацию, а углубляет.

Комбинация с CNL-P: Memory — это просто специальная переменная типа "история". CNL-P workflow загружает её в начале, обновляет, сохраняет в конце. Stateful агент на базе stateless языка.

🔗

Ресурсы

Оригинальная работа: "When Prompt Engineering Meets Software Engineering: CNL-P as Natural and Robust "APIs" for Human-AI Interaction" — Zhenchang Xing, Yang Liu, Zhuo Cheng, Qing Huang, Dehai Zhao, Daniel Sun, Chenhua Liu (2025, ICLR)

Авторы из:

  • CSIRO's Data61 (Австралия) — Zhenchang Xing, Dehai Zhao
  • Jiangxi Normal University (Китай) — Yang Liu, Zhuo Cheng, Qing Huang, Chenhua Liu
  • Newcastle University (UK) — Daniel Sun

Ключевые отсылки из исследования:

  • RISEN framework — популярный NL-шаблон для промптов (Role, Input, Steps, Expectation, Narrowing)
  • RODES framework — другой NL-шаблон (Role, Objective, Details, Examples, Sense Check)
  • TypeChat (Microsoft) — вдохновение для type checking в CNL-P
  • Pydantic — используется в linting tool для валидации типов Python
  • DSPy, LangChain, Semantic Kernel — PL-based методы для управления промптами (CNL-P позиционируется как альтернатива)

GitHub репозиторий: github.com/Irasoo/CNL-P — содержит код linting tool, примеры промптов, датасеты экспериментов


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

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

Усложняешь промпт — добавляешь переменные, условия, API-вызовы — и всё катится в кашу. Модель путает где что определено, пропускает условия, неправильно передаёт данные между шагами. CNL-P решает через формализацию: промпт структурируется как код с явными секциями, типами переменных и синтаксисом. Есть PERSONA (роль), CONSTRAINTS (ограничения), TYPES (типы данных), VARIABLES (переменные), WORKER (workflow с командами). Впервые промпты можно проверять linting-инструментом — как Python или JavaScript. Ловит синтаксические ошибки, несуществующие переменные, неправильные типы. 100% точность против 76% у GPT-4o.

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

Каждая секция — отдельный модуль с чёткими границами. PERSONA отделена от WORKER, переменные определены до использования, типы указаны явно. Ссылки на переменные через переменная — модель видит зависимости. IF-блоки для условий, SEQUENTIAL_BLOCK для последовательности шагов. Команды пронумерованы (COMMAND-1, COMMAND-2), результаты сохраняются через RESULT переменная: тип SET. Это как писать функцию на Python — есть входы, выходы, типы, порядок выполнения.

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

Неструктурированный текст ломается на сложных промптах. LLM читает линейно, но если роль, ограничения, переменные смешаны в одну кучу — модель додумывает что где. Формальная структура устраняет двусмысленность: [DEFINE_PERSONA:]...[END_PERSONA] явно говорит "это роль, не команда к действию". Модель видит границы модулей, понимает порядок. Типы предотвращают ошибки: если переменная budget: number, модель не запишет туда текст. Linting tool проверяет промпт до запуска — находит переменные которых нет, команды без результата, неправильные типы. Это как компилятор для кода: показывает ошибки до выполнения, а не после.

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

Multi-step задачи с явной логикой → конкретно для агентов с API-вызовами, условиями, передачей данных между шагами, особенно когда промпт превышает 5-7 команд и есть ветвление. Примеры: анализ данных с парсингом API и генерацией отчёта, агент для оценки стартапов с проверкой финансовых условий, workflow с условной логикой (если X > 100 делай A, иначе B). НЕ подходит для простых one-shot вопросов — "напиши пост" или "предложи идеи". Избыточная формализация убивает скорость. CNL-P оправдан когда без структуры промпт становится нечитаемым.

Мини-рецепт

1. Определи PERSONA: Роль агента и экспертизу — ROLE: Ты аналитик-стратег для стартапов в food-tech
2. Задай CONSTRAINTS: Что нельзя или обязательно — География: Только Москва, Данные: Только проверенные источники
3. Опиши TYPES: Если данные сложные, создай структуру — Район = {название: text, конкуренты: number}
4. Определи VARIABLES: Все переменные с типами до использования — "Список районов" районы: List[Район]
5. Построй WORKER: Укажи INPUTS/OUTPUTS, затем MAIN_FLOW с блоками SEQUENTIAL_BLOCK и IF
6. Пронумеруй команды: COMMAND-1, COMMAND-2... с явным RESULT переменная: тип SET
7. Используй : Ссылки на переменные через районы — показывает зависимости
8. Проверь linting: Если доступен инструмент, прогони промпт через проверку до запуска

Примеры

[ПЛОХО] : Ты аналитик. Проанализируй конкурентов на Яндекс.Еде для районов Москвы, учти мой бюджет из Google Sheets, выдай рекомендацию куда выходить. Если бюджет меньше миллиона — фильтруй по конкурентам < 5. (Неясно какие переменные, какие типы, в каком порядке выполнять, где данные берутся)
[ХОРОШО] : [DEFINE_AGENT: РынокАнализатор] [DEFINE_VARIABLES:] "Список районов" районы: List[Район] "Финансы стартапа" финансы: Финансы [END_VARIABLES] [DEFINE_WORKER: АнализРайонов] [MAIN_FLOW] [SEQUENTIAL_BLOCK] COMMAND-1 [CALL yandex_eda_api WITH {районы: районы} RESPONSE конкуренты_данные: List[Район] SET] COMMAND-2 [CALL google_sheets_api WITH {sheet_id: "финансы"} RESPONSE финансы SET] [END_SEQUENTIAL_BLOCK] [IF финансы.стартовый_капитал < 1000000] COMMAND-3 [COMMAND Выбери районы где конкуренты < 5] [END_IF] [END_MAIN_FLOW] [END_WORKER] [END_AGENT] (Явная структура: переменные определены с типами, API-вызовы с параметрами, условие с порогом, всё пронумеровано)
Источник: When Prompt Engineering Meets Software Engineering: CNL-P as Natural and Robust "APIs" for Human-AI Interaction
ArXiv ID: 2508.06942 | Сгенерировано: 2026-01-12 02:17

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

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

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