Скрипт делает, модель думает
Каждые тридцать минут приходило одно и то же сообщение об ошибке оплаты. Не раз в день и не «после релиза» - ровно по таймеру, круглые сутки. Задача, которая должна была просто заглядывать в очередь готовых материалов и присылать короткое уведомление, к тому моменту уже ничего не проверяла - но продолжала исправно стучаться и жечь деньги.
Это была моя задача. И неправильно собрала ее тоже я.
Если вы сейчас делаете первую автоматизацию и по умолчанию ставите в центр языковую модель (LLM) - программу, которая по текстовому запросу генерирует текстовый ответ, - этот текст про то, как не повторить мой заход. К концу вы сможете за минуту решить, нужна ли модель в конкретной задаче, и почему в половине случаев достаточно обычного скрипта: небольшой программы, выполняющей фиксированную последовательность действий.
flowchart TB
subgraph M["С моделью"]
M1["Запуск по расписанию,<br/>48 раз в сутки"] --> M2["Вызов модели<br/>на каждую проверку"] --> M3["Платно за каждый запуск,<br/>поведение непредсказуемо"]
end
subgraph S["Скриптом"]
S1["Тот же запуск<br/>по расписанию"] --> S2["Прочитать, сравнить,<br/>подставить в шаблон"] --> S3["Стоимость запуска - ноль,<br/>поведение одинаково"]
end
M ==> SИстория: как рутинная проверка съела бюджет
Механика была простая:
- Планировщик раз в полчаса запускал задачу.
- Задача смотрела очередь готовых материалов.
- Если там что-то появилось - отправляла уведомление.
И все.
Но собрана она была как полноценный агент: программа, которая обращается к языковой модели и дает ей набор инструментов (прочитать данные, сходить в хранилище, отправить сообщение), а модель сама решает, какой инструмент вызвать и в каком порядке.
Звучит основательно. На практике вся «интеллектуальность» сводилась к двум действиям: прочитать данные и подставить их в фиксированный шаблон текста. Ни одного места, где надо было что-то решить по смыслу.
А дальше включилась экономика.
Работу модели считают в токенах - и по ним же считают деньги. При каждом обращении к модели вы задаете потолок: сколько токенов она максимум может выдать в ответе. Я запросила потолок «по умолчанию, по максимуму», хотя реально мне нужно было три строки уведомления.
И вот момент, который я узнала уже на счете: провайдер проверяет платежеспособность по запрошенному потолку, а не по тому, сколько токенов вы реально потратите. То есть каждые полчаса задача приходила и просила «зарезервировать» максимальный ответ - даже если отвечать было нечем.
Пока на балансе был запас, проблема не проявлялась. Когда баланс просел, тривиальная проверка начала падать с ошибкой оплаты - не потому что денег не хватало на фактическую работу (там копейки), а потому что денег не хватало на резерв под потолок.
И падала она каждые тридцать минут. Бесконечно. Пока я не выключила все руками.
Починка заняла меньше времени, чем диагностика: я переписала задачу обычным скриптом - «прочитать, сравнить, подставить в шаблон, отправить». Без модели, без «инструментов», без агента. Стоимость запуска стала нулевой, поведение - предсказуемым.
flowchart TB
T["Задача"] --> Q1{"Чтение данных<br/>из известного места?"}
Q1 -->|"нет"| LLM["Нужна модель:<br/>смысл, язык, суждение"]
Q1 -->|"да"| Q2{"Сравнение по<br/>известным правилам?"}
Q2 -->|"нет"| LLM
Q2 -->|"да"| Q3{"Фиксированное<br/>форматирование?"}
Q3 -->|"нет"| LLM
Q3 -->|"да"| SCRIPT["Это скрипт.<br/>Без исключений"]Почему это случается: чем платишь за удобство
LLM в таких задачах появляется не потому, что кто-то хотел вас разорить. Она появляется потому, что с ней вроде как не надо думать заранее:
- «Не хочу описывать все ветки - модель разберется».
- «Формат данных поменяется - модель поймет».
Это иногда правда удобнее. Но за удобство платишь.
Вот список того, что в моем случае оказалось «скрытой ценой»:
- Цена не связана со сложностью. Проверка «есть ли что-то в очереди» по стоимости запроса к модели может быть почти такой же, как реальная полезная работа.
- Резервирование идет по потолку. Даже если ответ короткий, платежная проверка может считаться по максимально возможному ответу.
- Добавляется внешняя точка отказа. Скрипт зависит от ваших данных. Агент с моделью зависит еще и от чужого сервиса, вашего баланса и его правил.
- Поведение перестает быть воспроизводимым. На одинаковом входе можно получить чуть разный текст. Для уведомления это терпимо. Для проверки, которой доверяют, - уже нет.
- Сложнее тестировать. Трудно писать проверки формата «этот вход дает ровно этот выход», если выход каждый раз слегка плавает.
- Промпт становится частью логики. И ведет себя не как код: переписали формулировку - поведение поехало, компилятор не предупредит.
И еще один усилитель проблемы: регулярная задача масштабирует любую ошибку. Разовый скрипт упадет один раз, вы заметите. Задача по расписанию будет падать 48 раз в сутки - и ровно так же 48 раз в сутки напоминать о себе (и иногда - тратить деньги).
Правило, которое я теперь применяю до написания кода
Я теперь прогоняю любую автоматизацию через очень простой фильтр:
Если задача сводится к чтению данных, сравнению и фиксированному форматированию - это скрипт. Без исключений.
Три глагола тут и есть тест:
- Чтение - взять данные оттуда, где они лежат.
- Сравнение - проверить условие: пусто/не пусто, больше/меньше, изменилось/не изменилось.
- Фиксированное форматирование - подставить значения в заранее написанный шаблон.
Если вся работа раскладывается на эти действия - модели там делать нечего. Она не добавит ценности, зато добавит стоимость и непредсказуемость.
Самопроверка, которой я пользуюсь:
Есть ли здесь шаг, который я не могу записать обычным if?
Если каждый шаг можно записать - пишите скрипт. Если есть шаг, где нужна оценка «подходит или нет по смыслу» - вот там (и только там) появляется модель.
Отсюда моя памятка на будущее: скрипт делает, модель думает.
Второй прием: пустой вывод - это молчание
Еще одна привычка, которая резко повышает шанс, что автоматизация не превратится в фон:
Если регулярная задача в большинстве запусков ничего не находит - ее вывод должен быть пустым.
Не «ничего не найдено», не «проверка завершена» - тишина.
Если задача бегает каждые полчаса, то разница между «тишиной» и «48 сообщений в день» - это разница между системой, за которой следят, и системой, поток которой перестают читать через неделю.
Где модель действительно нужна
Это не манифест «LLM не нужна». Она нужна - но там, где без нее правда нельзя.
Например:
- Отбор по смыслу. Не «взять все записи за сутки» (это условие и скрипт), а «выбрать из потока то, что относится к теме», где признак нельзя выразить набором правил.
- Генерация текста. Не подстановка значений в шаблон, а связный текст, которого до этого не существовало: пересказ, черновик, объяснение.
- Разбор неструктурированного ввода. Когда данные приходят «как попало» - письмом, сообщением, разнородными кусками - и из них надо извлечь смысл.
Чаще всего лучший вариант - не «агент» и не «чистый скрипт», а скрипт с одним модельным шагом внутри. Скрипт делает всю механику: забирает данные, фильтрует, считает, форматирует, отправляет. А модель вызывается один раз - именно там, где требуется смысловое суждение.
Применить у себя
Что проверить у себя прямо сейчас
Если у вас уже крутится автоматизация, вот быстрый чеклист:
- Выпишите все регулярные задачи и рядом с каждой - что она делает по шагам, глаголами: «читает, сравнивает, отправляет».
- Отметьте задачи, где нет ни одного шага с «суждением по смыслу». Это кандидаты на переписывание скриптом.
- Если модель все-таки нужна - проверьте, какой потолок ответа вы запрашиваете. Если стоит максимум по умолчанию, а ответ короткий - уменьшите.
- Посмотрите, что происходит при ошибке. Задача, падающая по расписанию, должна либо замолкать после нескольких неудач, либо сообщать один раз, а не спамить.
- Уберите сообщения «ничего не найдено». Пусто - значит пусто.
Как понять, что вы все сделали правильно
Самый честный критерий:
Запустите переписанную задачу дважды на одних и тех же данных и сравните вывод посимвольно.
Если совпало - у вас скрипт и его можно покрыть тестом. Если не совпало - где-то все еще «думает» модель, и надо честно ответить, зачем она там.
А самый приятный признак вы заметите позже: когда у провайдера что-то изменится, а ваша очередь просто продолжит работать - и не устроит вам новый сюрприз в 3:30 ночи.