Скільки насправді коштує ваша AI-фіча — і чому це варто рахувати заздалегідь

💡 Усі статті, обговорення, новини про AI — в одному місці. Приєднуйтесь до AI спільноти!

Нинішню актуальність та користь AI важко заперечити. Швидкість впроваждення цих технологій в програмні продукти та галузі є досить виликою: чат-боти підтримки, автогенерація описів товарів, SEO-контент, класифікація і тегування звернень, генерація зображень для магазинів, іншого контенту, використання у блогах та соціальних мереж, як допоміжні інструменти в програмниз засобах.

Технічно додати виклик до OpenAI чи Anthropic справа кількох рядків коду, тим більше, що попри новизну технології, API та документація практично в усіх доволі проста і зрозуміло (REST API наше все:)). А от питання «скільки нам це коштуватиме в масштабі» зазвичай зʼявляється вже після того, як фукнкціонал уже на проді.

Проблема в самій моделі оплати. Це не хостинг, де ти платиш фіксовану суму за сервер і більш-менш знаєш свій бюджет наперед. Це pay-per-token: кожен виклик, кожен ретрай, кожен зайвий абзац у системному промпті — окрема сума в рахунку. І цю сума ніде не видно, доки не прийде інвойс від провайдера в кінці місяця чи, в кращому випадку, сервіс не нагадає завчасно, що токени закінчуються.

Чому AI-виклик — це інша економіка, ніж звичайний сервер

У традиційній інфраструктурі витрати переважно фіксовані й передбачувані: орендував сервер — платиш за нього незалежно від того, скільки запитів він обробив цього місяця. Масштабування коштів відбувається сходинками (додав ще один інстанс) і зазвичай контрольовано, бо рішення приймає людина.

AI API працює за іншою моделлю — маржинальна вартість кожного окремого виклику ненульова і додається лінійно (а часто й гірше за лінійно) до загальної суми:

  • Вартість рахується за токенами, а не за запитом. Токен — це фрагмент тексту (приблизно 3-4 символи для англійської мови), на які модель розбиває вхід і вихід. Вхідні та вихідні токени коштують по-різному — вихід зазвичай у 3-5 разів дорожчий за вхід, бо генерація нового тексту «важча» обчислювально, ніж читання наявного.
  • Контекст накопичується. Якщо задача передає історію діалогу чи довгий системний промпт при кожному виклику, кожен наступний запит платить за весь цей контекст повторно. Чат на 20 повідомлень коштує не як 20 окремих коротких запитів, а як 20 запитів із дедалі довшим «хвостом» попередньої історії.
  • Провайдери оптимізують по-своєму. Є механізми на кшталт prompt caching (повторне використання незмінної частини промпту за нижчою ціною), різні тарифи для різних моделей однієї лінійки, батчинг для неterm-critical задач. Кожен із цих механізмів — це реальна економія, але тільки якщо архітектура коду взагалі дозволяє ними скористатись.

Іншими словами: вартість AI-фічі — це не одна цифра «ціна за виклик», а функція від довжини контексту, вибраної моделі, кількості ретраїв і того, наскільки добре код використовує механізми оптимізації провайдера. Порахувати це «на око» наперед майже неможливо — тому й потрібен облік по факту, а не оцінка на етапі планування.

Чому це стає бізнес-ризиком, а не тільки технічним питанням

Уявімо типову картину, яка складається непомітно, крок за кроком:

  • Один клієнт чи відділ використовує фічу в рази активніше за інших — і саме через нього рахунок за місяць зростає вдвічі.
  • Запити ретраяться при збоях провайдера, і кожен ретрай — це ще одна оплачена спроба.
  • Нічна пакетна обробка (генерація описів, аналіз накопичених звернень) працює без будь-якого контролю обсягу.
  • Системний промпт поступово «обростає» інструкціями під нові edge case-и, і кожен виклик стає дорожчим, непомітно для команди.

Жодна з цих подій сама по собі не критична. Але без контролю вони накопичуються тихо, і перша точка, де хтось це помічає — рахунок від провайдера. На цьому етапі вже пізно щось «зупинити вчасно» — можна тільки розбиратися постфактум, хто і що витратив.

Це той самий клас проблем, який у хмарних витратах (cloud spend) вже давно вирішує напрям, відомий як FinOps — практика фінансового управління змінними технічними витратами. У FinOps є чотири базові принципи, які прямо переносяться на AI-виклики: видимість витрат (хто, що і скільки витрачає — в розрізі команди/клієнта/фічі), розподіл витрат (можливість «приписати» вартість конкретному тенанту чи фічі, а не бачити тільки загальну суму), оптимізація (кешування, вибір дешевшої моделі там, де якість не постраждає) і, нарешті, governance — жорсткі обмеження, які не дають окремій команді чи клієнту вийти за рамки бюджету незалежно від того, помітив хтось це вручну чи ні. AI-бюджетування — це, по суті, четвертий пункт FinOps, застосований до LLM-викликів.

Чому однієї перевірки бюджету недостатньо

Найпростіша ідея контролю виглядає так: перед викликом перевірити, чи тенант ще в межах ліміту. Проблема в тому, що ця перевірка спирається на дані, яких на момент перевірки ще не існує — реальну вартість виклику ти дізнаєшся тільки після відповіді провайдера, коли стають відомі точні токени. Тенант, який на $2 нижче ліміту, може відправити запит вартістю $5 — і перевірка «на вході» сама по собі це пропустить, бо на момент перевірки все виглядало нормально.

Це прояв загальнішої інженерної проблеми — контроль стану, який змінюється асинхронно і з затримкою (схоже на те, чому «перевірити залишок і списати» без атомарності — класичне джерело race condition у будь-якій системі з лічильниками). Правильний підхід — не одна точка контролю, а дві:

  1. Pre-flight, до виклику провайдера — за витратами тенанта з початку періоду. Дешева перевірка, яка відсікає найгірший випадок: тенант, що вже давно за межею ліміту, не витрачає ні копійки на новий виклик, бо той навіть не відправляється.
  2. Post-call, одразу після відповіді — за реальною вартістю саме цього виклику. Ловить прикордонний випадок: виклик, який сам по собі «переповнює» бюджет.

Обидві перевірки прив’язані до одного й того самого механізму, тож перевищення ліміту помічається в момент, коли воно фактично відбувається, а не постфактум під час звірки рахунків.

Важлива деталь, яку легко упустити при проєктуванні такої логіки: коли спрацьовує саме post-call перевірка, провайдеру вже заплачено за цю відповідь. Якщо в цей момент просто «відкинути» виклик як помилку і не зберегти його вартість — ця сума випаде з наступного підрахунку витрат, і тенант виглядатиме так, ніби в нього залишився запас, якого насправді вже немає. Тобто сам факт відмови не повинен приховувати реальну витрату, що вже відбулась.

Не прив’язуватись до одного провайдера

Ще один момент, який часто недооцінюють на старті: різні провайдери AI суттєво відрізняються за ціною і якістю для різних типів задач, і ці умови змінюються — провайдер може підняти ціни, впасти на кілька годин, або просто виявитись гіршим за конкурента для конкретної задачі. Ринок LLM ще молодий і нестабільний порівняно, скажімо, з ринком хмарного хостингу — цінові й продуктові умови міняються набагато частіше.

Класичний спосіб убезпечитись від цього в архітектурі — шар абстракції між бізнес-логікою і конкретним постачальником (той самий adapter/strategy pattern, який десятиліттями застосовують для баз даних, платіжних систем чи файлових сховищ). Для AI це виглядає так: код фічі описує що треба зробити (summарize, класифікувати, згенерувати опис), а яка модель від якого провайдера це виконає — питання конфігурації, а не коду фічі.

Перемикання між провайдерами тоді стає зміною конфігурації, а не переписуванням бізнес-логіки. Для кожної задачі можна задати пріоритет провайдерів (спочатку спробувати дешевший, за збою — перейти на резервний), або перемкнути провайдера для конкретного виклику. Це дає бізнесу гнучкість: можна тестувати дешевшу модель на частині трафіку, миттєво реагувати на збій одного з постачальників, або торгуватися за ціну, знаючи, що технічно перехід не коштуватиме тижнів розробки. Це той самий принцип, що й уникнення vendor lock-in у будь-якій іншій частині інфраструктури — просто застосований до відносно нового класу постачальників.

Облік у токенах і грошах — автоматично, без Excel

Кожен виклик до AI фіксується з кількістю токенів на вхід і вихід та порахованою вартістю в доларах — автоматично, без ручного зведення даних. Наприклад, реальний прогін через задачу класифікації звернення виглядає так:

{
    "driver": "openai",
    "tokens_in": 100,
    "tokens_out": 24,
    "cost": 0.0000294
}

Виглядає як копійки — і це справді копійки для одного виклику. Але коли таких викликів у процесі одне звернення проходить через три (наприклад: коротке резюме, ключові слова для пошуку, категорія), а звернень — тисячі на місяць, ці копійки складаються у конкретну суму в бюджеті відділу підтримки, і цю суму варто знати заздалегідь, а не дізнаватись з рахунку.

Тут вступає в дію ще один принцип із того ж FinOps-словника — різниця між showback і chargeback. Showback — це коли команда чи клієнт просто бачить, скільки витрачає (звіт, дашборд, «ось ваші цифри»). Chargeback — коли ця сума реально виставляється клієнту чи відділу як частина рахунку, зі своїм бюджетом і відповідальністю за перевищення. Якщо кожен виклик уже прив’язаний до тенанта, вибір між showback і chargeback — це вже питання бізнес-процесу, а не додаткової розробки: дані для обох моделей одні й ті самі.

Це саме той тип даних, який дозволяє відповісти на цілком бізнесове питання: «скільки нам коштує ця AI-фіча на місяць, і скільки коштуватиме, якщо обсяг зросте вдесятеро» — без окремого проєкту з фінансової аналітики, бо дані вже є в системі з першого дня.

Зрілість процесу контролю AI-витрат

Якщо дивитись на це як на еволюцію процесу, а не разову функцію, вимальовуються приблизно такі стадії зрілості:

  1. Немає нічого — виклики роблять напряму до SDK провайдера, витрати видно тільки в кабінеті постачальника раз на місяць.
  2. Логування — кожен виклик фіксується в базі (модель, токени, вартість), але це просто журнал подій, без жодних дій на основі цих даних.
  3. Видимість (showback) — з’являється звіт чи команда, яка показує витрати в розрізі тенанта/фічі/періоду, і хтось починає це регулярно переглядати.
  4. Governance (бюджети) — ліміти стають частиною самого потоку виконання: перевищення блокує виклик або хоча б негайно сигналізує про нього, а не чекає ручного перегляду звіту.

Більшість команд, які тільки почали додавати AI у продукт, знаходяться десь між першою і другою стадією. Перехід до четвертої не потребує окремого проєкту — якщо облік токенів і вартості вже вбудований у сам шар виклику AI, бюджети зверху додаються як конфігурація, а не як нова підсистема.

Як це реалізовано в laravel-ai-tasks

Усе описане вище — не абстрактна теорія, а конкретна реалізація в пакеті laravel-ai-tasks для Laravel.

Важливий архітектурний вибір — пакет не пише власний транспортний шар до кожного провайдера, а будується поверх офіційного laravel/ai SDK від команди Laravel. laravel-ai-tasks відповідає за оркестрацію (маршрутизація, черги, логування, бюджет), а сам зв’язок з конкретним провайдером — автентифікація, формат запиту, розбір відповіді — лишається на laravel/ai. Це дає кілька відчутних переваг:

  • Не треба самому наздоганяти зміни в API кожного провайдера. Коли OpenAI, Anthropic чи Gemini міняють щось у своєму API, це питання оновлення laravel/ai, а не переписування власного HTTP-клієнта під кожного з них.
  • Нові можливості SDK автоматично доступні пакету. Коли laravel/ai додає щось нове — наприклад, структурований JSON-вивід через нативну схему провайдера чи примусовий вибір інструменту (ToolChoice) — laravel-ai-tasks підхоплює це оновленням версії залежності, а не переписуванням своєї логіки з нуля.
  • Офіційний SDK — це довіра екосистеми. Розробнику на Laravel не треба вивчати чужий, специфічний для конкретного пакету спосіб опису запитів — синтаксис laravel/ai (повідомлення, інструменти, стрімінг) той самий, що й у прямому використанні SDK без цього пакету.

Кілька моментів, як саме кожна ідея з тексту вище виглядає в коді (уже на рівні laravel-ai-tasks, поверх цього фундаменту):

  • Кожна AI-операція — окремий клас-задача (AiTask), а не розкидані виклики по сервісах. Клас описує запит (toPayload()) і що робити з відповіддю (postprocess()); фасад AI викликає його синхронно (AI::send()), у черзі (AI::queue()) чи стрімом (AI::stream()). Щось подібно до уже звичого патерну Laravel Notification чи Email.
  • Незалежність від провайдера — та сама задача виконується на OpenAI, Anthropic, Gemini, DeepSeek, Groq, Mistral, xAI чи Ollama залежно від конфігурації маршрутизації (config('ai-tasks.routing')), з ланцюжком fallback-ів, якщо перший провайдер недоступний.
  • Облік токенів і вартості — вбудований у кожен виклик: усе, що показано вище як приклад JSON, це реальні поля, які пакет пише в таблицю ai_runs (модель, токени входу/виходу, вартість, статус, тривалість) без додаткового коду в задачі.
  • Двошарова перевірка бюджету (pre-flight + post-call), описана в розділі вище, — це клас Budget, підключений до кожного з трьох шляхів виконання (send, stream, черга), з лімітами на тенанта в конфігурації.
  • php artisan ai:budget {tenant} — команда, вивід якої показано нижче, реалізована як звичайний Artisan-репорт. Скоріше для відлагодження, як для реального використання, але може бути цікавим і корисним на старті проекту.
php artisan ai:budget default

Реальний вивід з тестового середовища (ліміт виставлено умисно невеликим, щоб цифри не були нульовими):

+---------+-------------------------+-------------+-------------+------------------+
| Tenant  | Period                  | Spent (USD) | Limit (USD) | Remaining (USD)  |
+---------+-------------------------+-------------+-------------+------------------+
| default | 2026-07-01 → 2026-07-31 | 0.006698    | 1.0000      | 0.9933           |
+---------+-------------------------+-------------+-------------+------------------+

Якщо ліміт вичерпано, команда сигналізує про це кодом завершення — тобто її можна поставити в план і отримати готове попередження без окремої системи алертів.

Приклад викорисатння laravel-ai-tasks на практиці

Як виглядає такий клас-задача насправді — реальний приклад із проєкту, де я тестую пакет, задача отримання підсумку по наданому тексту:

class SummarizeTextTask extends AiTask
{
    public function __construct(
        private readonly string $text,
        private readonly int    $maxWords = 50,
    ) {}

    public function toPayload(): AiPayload
    {
        return new AiPayload(
            modality: 'text',
            messages: [new UserMessage($this->text)],
            systemPrompt: "Summarize the following text in at most {$this->maxWords} words.",
            options: ['temperature' => 0.2],
        );
    }

    public function postprocess(AiResponse $resp): array|AiResponse
    {
        return ['ok' => $resp->ok, 'summary' => trim($resp->content ?? ''), 'usage' => $resp->usage];
    }
}

Якщо до прикладу у вас магазин, є задача генерації SEO — створюєте відповідний клас-task, описуєте (чи дістаєте з БД, request, конфігу, і т.д. промпт), запускаєте, коли і звідки це необхідно, і все. Підібний підхід праціює фактично на задачах будь-якого типу і складності.

Це звичайний PHP-клас, без спадкування від якогось важкого абстрактного шару. toPayload() формує запит — текст, системний промпт, температуру. postprocess() каже, що робити з відповіддю моделі, коли вона прийде (тут просто повертає структурований масив, у реальній задачі це може бути запис у БД чи виклик іншого сервісу чи API). Викликається це одним рядком — AI::send(new SummarizeTextTask($text, 20)) — а вся інфраструктура навколо (вибір провайдера, ретраї, логування в ai_runs, перевірка бюджету) відбувається прозоро, поза кодом самої задачі.

Ще кілька речей, які стосуються того самого класу без жодних змін у ньому:

  • Синхронно чи в черзі — вибір одного слова у виклику. AI::send() виконує задачу одразу і чекає відповіді. AI::queue() кладе той самий клас у чергу (Redis/Horizon) і повертає ID запуску, не блокуючи запит користувача, — зручно для довгих генерацій чи масової фонової обробки. AI::stream() — третій варіант, коли відповідь віддається шматками по мірі генерації, без обмеження за часом очікування.
  • Тип контенту — теж просто параметр (modality), не інша задача. Той самий підхід із toPayload()/postprocess() працює не тільки для тексту, а й для генерації зображень, ембедінгів (векторів для пошуку), синтезу мовлення чи транскрипції аудіо в текст — міняється модальність у пейлоаді, структура коду задачі лишається тою самою.
  • Події на кожному етапі виконання. Пакет кидає Laravel-події (AiTaskStarted, AiTaskCompleted, AiTaskFailed, AiTaskQueued), на які можна підписатись окремо від самої задачі — наприклад, писати в лог, слати сповіщення в Slack при падінні всіх провайдерів, чи оновлювати власну статистику, не чіпаючи код задачі взагалі.

Тобто пакет — не не тільки окремо «бюджетування», а спроба з першого дня закласти в інфраструктуру AI-викликів усе те, що інакше довелось би добудовувати вручну після першого неприємного рахунку.

Схема роботи пакету ai-tasks

Інструменти ззовні через MCP

Окремо варто згадати підтримку MCP (Model Context Protocol) — стандарту, який дозволяє AI-моделі самій викликати зовнішні інструменти й читати дані з інших систем під час відповіді, а не тільки генерувати текст. У laravel-ai-tasks це підключається через tools() у тій самій задачі — або нативно через laravel/mcp (якщо він встановлений), або через вбудований zero-dependency HTTP-клієнт, якщо ні.

Практично це виглядає так само, як задача з прикладу вище, тільки замість фіксованого промпту модель сама вирішує, коли і який інструмент викликати. Кілька реальних кейсів, де це стає в пригоді:

  • Асистент над власною CRM/таск-трекером. Задача підключає інструменти CRM-сервера через MCP, і на запит на кшталт «покажи завантаженість усіх користувачів» чи «створи задачу „Fix login bug“ у проєкті CRM з пріоритетом 3» модель сама викликає потрібні MCP-методи й повертає результат — без написання окремого парсера команд.
  • Технічний асистент з актуальною документацією. Підключення до сервера на кшталт Context7 дає моделі змогу підтягнути свіжу документацію бібліотеки перед відповіддю (наприклад, «як користуватись simplePaginate в Laravel») — замість відповіді з застарілих знань з тренування моделі.
  • Доступ до файлової системи чи внутрішніх сервісів компанії — той самий механізм, просто інший MCP-сервер: підключення до внутрішнього API моніторингу, пошуку по документах компанії тощо.

Головне тут — це той самий клас AiTask і той самий виклик AI::send()/AI::queue(): інструменти з MCP просто додаються в масив поруч зі звичайними локальними, і вся інфраструктура — бюджет, логування, вибір провайдера — працює над ними так само, як над будь-яким іншим викликом.

Підсумок

Контроль AI-бюджету — це не технічна дрібниця для інженерів, а питання фінансової передбачуваності продукту: скільки коштує фіча зараз, скільки коштуватиме при зростанні, і що станеться, якщо хтось випадково або навмисно почне зловживати нею. Коли ця логіка вбудована в процес з самого початку — рахунок наприкінці місяця перестає бути сюрпризом.

Код пакету: github.com/...​fomvasss/laravel-ai-tasks

👍ПодобаєтьсяСподобалось8
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

корисне чтиво, дякую

Золоті слова. Комбінаторний вибух кількості токенів — цифровий «героїн» щоб підсадити і розорити існуючі бізнеси та прибрати ринки собі.

Комбінаторний вибух кількості токенів — цифровий «героїн» щоб підсадити і розорити існуючі бізнеси

попутно знищивши розробників, які здатні писати код головою і дебажити руками.

прибрати ринки собі.

зараз відбуваєтья масований злив ноухау в ллм.
безкоштовний сир в обмін на внутрішні дані.

в мене питання виникло
dou.ua/...​rums/topic/60935/#3104806
а є сенс в окремій ролі типу ai-ops, для випадків, коли ціна запитів йде вже не на сотні доларів, а на тисячі?

тоді як окрема фукнція, це дійсно не важливо. не звертати увагу на показник ціни )

використовувати як оркестратор, основу для організації кодової бази, технічного контролю виконання, черг, постобробки.. якщо звичайно стек, потреби і підходи співпадають

врахуйте, що зараз ціна на токени ще формується, і ймовірно буде зростати.

Підписатись на коментарі