vegman.dev
Все статьи

Когда языковая модель не нужна: правило «скрипт делает, модель думает»

2026-03-05статья

Скрипт делает, модель думает

Каждые тридцать минут приходило одно и то же сообщение об ошибке оплаты. Не раз в день и не «после релиза» - ровно по таймеру, круглые сутки. Задача, которая должна была просто заглядывать в очередь готовых материалов и присылать короткое уведомление, к тому моменту уже ничего не проверяла - но продолжала исправно стучаться и жечь деньги.

Это была моя задача. И неправильно собрала ее тоже я.

Если вы сейчас делаете первую автоматизацию и по умолчанию ставите в центр языковую модель (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 ночи.