Всем привет! Сегодня хочу поговорить о преждевременной спецификации при работе с ИИ-агентами - ситуации, когда мы подробно объясняем, как написать код, но упускаем, какую задачу он должен решать.
Пример запроса: Добавь параметр integration.enabled. В начале метода send() проверяй его значение: если false, сразу делай return.
Такой запрос выглядит хорошей постановкой задачи. Есть название настройки, конкретный метод и понятная логика. Агенту удобно приступать к работе: найти нужный класс, добавить проверку, написать тест. Но какую проблему должен решить этот код? Возможно, нужно временно приостановить отправку данных. Возможно, запретить обращения к внешней системе на тестовом стенде. А возможно, приложение должно работать у клиентов, которые вообще не подключали интеграцию: без её адреса, учётных данных и доступности по сети. За одной инструкцией могут стоять разные требования. Пока исходная проблема остаётся только в голове разработчика, агенту трудно проверить, подходит ли предложенный механизм.
Назовём это преждевременной спецификацией: мы указываем функции, библиотеки и порядок действий раньше, чем объясняем цель задачи.
Разработчику легко попасть в эту ситуацию. Мы привыкли быстро переходить от проблемы к технической гипотезе и по итогу в запрос агенту попадает последний шаг рассуждения. Причины, предположения и ожидаемый результат остаются за его пределами.
Представим, что интеграция нужна только части клиентов. Для остальных приложение должно запускаться и выполнять основные функции без её настроек. Проверка в send() запрещает отправку, но компонент интеграции по-прежнему может создаваться при запуске приложения. Его конфигурация требует URL и учётные данные. Без них приложение падает ещё до первого вызова send().
Агент способен аккуратно реализовать проверку флага, а исходная проблема сохранится. Более того, тест тоже может оказаться привязан к выбранному механизму. Он подтвердит, что при выключенном флаге метод отправки не вызывается. Но ничего не скажет о запуске приложения без настроек интеграции. Получится корректная реализация инструкции без подтверждения, что задача решена.
Теперь сформулируем поручение иначе:
Интеграция нужна только части клиентов. При integration.enabled=false приложение должно запускаться и выполнять основные функции без адреса и учётных данных внешней системы. Обращений к ней быть не должно, в том числе из фоновых задач.
Я предполагаю, что потребуется проверка флага, но место и способ отключения нужно определить по коду. Проверь создание компонентов и точки обращения к интеграции. Подтверди результат запуском с выключенной интеграцией и без её настроек.
У агента появляется основание для оценки решения. Он знает желаемое поведение и сценарий проверки. Предложение про флаг сохраняется, но его статус понятен: это идея, которую нужно проверить. Теперь агенту можно обнаружить, что отключать требуется создание компонентов, например через @ConditionalOnProperty в Spring. Или что отдельного внимания требуют фоновые задачи. Это даёт агенту контекст, позволяющий заметить расхождение между механизмом и целью.
Практически полезно разделять в постановке три вещи:
- Цель: какое наблюдаемое поведение должно измениться.
- Ограничения: какие условия действительно обязательны и почему.
- Гипотеза реализации: какой способ решения кажется подходящим.
Конкретная библиотека или порядок вызовов вполне могут быть обязательными. Возможно, решение уже исследовали и согласовали в команде. Эти детали стоит передавать вместе с причинами. Тогда агенту проще понять, где допустимо предложить альтернативу, а где требуется соблюдать принятое решение. Для небольшой задачи достаточно нескольких предложений. Не нужно превращать каждый запрос в развёрнутое ТЗ. Нужно сохранить связь между проблемой и действиями, которые мы поручаем выполнить.
Перед отправкой запроса можно сделать простую проверку: мысленно убрать названия библиотек, функций и последовательность вызовов. Останется ли понятно, чего нужно добиться и как определить, что задача решена? Если нет, стоит дописать цель. Даже когда план уже готов.