Перейти к содержимому
26 авг. 2024 г.·7 мин чтения

Лимит по токенам: когда он важнее лимита запросов

Лимит по токенам часто упирается раньше, чем лимит запросов. Разберём длинный контекст, большие ответы и вызовы инструментов с объёмными данными.

Лимит по токенам: когда он важнее лимита запросов

Почему лимит запросов не спасает

Счётчик запросов кажется понятным: один вызов равен одному вызову. Но для LLM это слишком грубая метрика. Короткий вопрос на 200 токенов и тяжёлый запрос на 80 000 токенов занимают в таком счётчике одинаковое место, хотя нагрузка и стоимость у них совсем разные.

Из-за этого команда смотрит на низкий RPS и думает, что запас большой. На деле минутный бюджет по токенам уже почти пуст. Хватает пары тяжёлых вызовов с длинной историей чата, большим системным промптом и длинным ответом модели, чтобы уткнуться в лимит по токенам, даже если запросов было всего два или три.

Простой пример. У вас есть лимит 150 000 токенов в минуту. Первый запрос занял 8 000 токенов. Второй ушёл с большим контекстом, таблицей из документа и длинным ожидаемым ответом на 110 000 токенов. Формально это всего два запроса. По токенам минутный бюджет уже почти исчерпан. Третий вызов может получить rate limit, хотя график по запросам выглядит спокойно.

В продакшене такой сбой особенно неприятен. Мониторинг показывает обычную частоту вызовов, а пользователи видят задержки и ретраи. Инженеры ищут всплеск трафика, хотя причина проще: каждый отдельный вызов стал слишком тяжёлым.

Чаще всего это происходит в трёх сценариях: в чатах с длинной перепиской, в задачах с развёрнутым ответом и при вызовах инструментов, которые тянут большой JSON или куски документов.

Через единый API-шлюз вроде RU LLM это видно ещё лучше. Снаружи у команды один endpoint и тот же SDK, но фактический расход зависит от модели, провайдера и размера каждого вызова. Поэтому смотреть только на RPS мало. Если не считать токены заранее, лимит сработает в самый обычный момент, без всякого пика нагрузки.

Что съедает токены

Лимит по токенам обычно заканчивается не на вопросе пользователя. Основной объём даёт весь служебный груз вокруг запроса. Человек пишет одну короткую фразу, а модель получает пакет в разы больше.

Первым идёт system prompt. Пользователь его не видит, но модель читает этот текст при каждом вызове. Если туда добавили роли, правила ответа, формат JSON, требования по безопасности и длинный список исключений, всё это каждый раз уходит в бюджет.

Потом растёт история чата. Один ответ редко живёт сам по себе: в запрос уходят прошлые реплики, уточнения, ошибки валидации, промежуточные результаты. После 15-20 сообщений история часто весит больше, чем текущий вопрос.

Следом идут данные из поиска. В RAG-сценарии вопрос пользователя может занимать 20 слов, а найденные фрагменты - несколько страниц текста. Если поиск вернул шесть больших кусков с повторами, модель тратит токены не на сам вопрос, а на чтение контекста, часть которого ей вообще не нужна.

Инструменты тоже стоят недёшево. В бюджет входят названия функций, схемы, описания полей и сами аргументы. Если tool calling работает с объёмным JSON, таблицей тарифов, карточкой клиента или длинным ответом внутреннего сервиса, запрос быстро раздувается.

И есть ещё один пункт, который часто недооценивают: ответ модели. Если вы просите подробное объяснение, сводку по 50 документам или большой JSON для следующего шага, нужен запас не только на вход, но и на выход.

Хорошо это видно на простом расчёте. Вопрос клиента занимает 30 токенов. System prompt - 900. История чата - 2200. Два фрагмента из поиска - 3000. Схема двух инструментов с аргументами - ещё 1200. Если вы ждёте ответ на 1500 токенов, общий объём уже близок к 9 000. Сам вопрос здесь почти ничего не решает.

Поэтому считать только число запросов почти бесполезно. Один короткий вызов и один вызов с длинным контекстом выглядят одинаково по rate limit, но по цене, задержке и риску обрезанного ответа это совсем разные операции.

Где длинный контекст бьёт сильнее всего

Сильнее всего лимит по токенам бьёт там, где команда отправляет в модель слишком много текста "на всякий случай". Для модели нет разницы между полезным фрагментом и лишним дублем. Она считает всё: инструкции, историю чата, найденные куски документов, ответ инструмента и сам вопрос.

Перегрузка обычно начинается в знакомых местах. RAG-поиск приносит слишком много фрагментов из базы знаний. Вместо трёх подходящих кусков в запрос попадают десять, причём часть из них почти одинаковая. PDF, договоры и отчёты тоже быстро раздувают ввод: после OCR один файл легко превращается в очень длинный текст с таблицами, сносками и повторяющимися заголовками. В длинной переписке копятся старые уточнения, приветствия и уже закрытые ветки. А инструменты часто отдают целый payload, хотя пользователю нужен один статус или одна сумма.

Особенно плохо работают похожие куски текста. Допустим, поиск по базе знаний нашёл шесть фрагментов из разных версий одного договора. В каждом повторяется блок про штрафы, сроки и порядок согласования. Модель читает это заново, а качество ответа почти не растёт. Зато стоимость и риск упереться в лимит растут сразу.

С PDF и отчётами проблема ещё заметнее. Люди часто думают, что "один документ" равен "один источник". Для модели это не так. Если в отчёте 40 страниц, а вопрос касается двух абзацев, всё остальное просто занимает окно контекста.

В длинных чатах потери копятся тихо. Каждый новый ответ тянет за собой старые сообщения, и через некоторое время половина контекста нужна только для того, чтобы хранить следы прошлых шагов. Это особенно часто видно в поддержке, внутренних помощниках и аналитических чатах.

Если качество ответа не растёт вместе с длиной контекста, лишний текст стоит убрать. В таких сценариях лимит запросов почти не важен: один тяжёлый вызов может стоить дороже, чем десять коротких.

Когда большой ответ ломает расчёт

Бюджет часто утекает не на количестве вызовов, а на одном слишком щедром ответе. Если у вас уже длинный контекст, большой вывод LLM быстро добивает общий объём и делает один запрос дорогим как несколько обычных.

Распространённая ошибка - ставить max_tokens с большим запасом. Команда думает: лучше дать модели свободу, чем получить обрезанный ответ. На практике широкий лимит часто провоцирует лишний текст, особенно если в промпте есть формулировки вроде "объясни подробно", "покажи все варианты" или "распиши шаг за шагом". Пользователю часто нужен короткий итог, а модель пишет мини-отчёт.

Сильнее всего объём растёт там, где ответ должен быть структурированным. JSON и таблицы выглядят аккуратно, но в токенах стоят дорого. Каждый ключ, список, кавычка, перенос строки и повтор поля добавляют вес. Если инструмент ждёт большой payload, модель может выдать не просто ответ, а почти готовый документ.

Разница быстро становится заметной. При одинаковом входе в 900 токенов короткая справка может занять 250 токенов на выходе. Подробное объяснение - уже 1600. JSON с массивом объектов - около 2800. Таблица с пояснениями - больше 2000. Число запросов то же самое, а расходы меняются в разы.

Проблема не только в цене. Длинный ответ модель генерирует дольше, клиентское приложение дольше ждёт, а следующий сервис дольше разбирает результат. Если система ещё и гоняет такой вывод между инструментами, задержка растёт на каждом шаге.

Это хорошо видно в прикладных сценариях. Допустим, бот должен вернуть оператору банка итог по обращению и приложить JSON для CRM. Если попросить "полный разбор ситуации" и оставить высокий max_tokens, ответ раздуется сразу в двух местах: в тексте и в структуре данных.

Рабочее правило простое: задавайте длину ответа под задачу, а не "на всякий случай". Краткий вывод, отдельный JSON и жёсткий потолок на генерацию почти всегда дают более предсказуемый счёт.

Как инструменты раздувают вызов

Тестируйте длинный контекст
Проверьте RAG, большие PDF и вызовы инструментов на одном OpenAI-совместимом эндпоинте.

Проблема часто начинается не с вопроса пользователя, а с того, что вы отправляете вместе с ним. Когда приложение использует tools или function calling, модель получает описание каждой функции, список параметров и правила вызова. Если функций много, их схемы попадают в запрос снова и снова. В этот момент лимит по токенам начинает давить сильнее, чем лимит по числу запросов.

Особенно дорого обходятся аргументы со сложной структурой. Один массив из сотни объектов, вложенный JSON с адресами, товарами, статусами и служебными полями легко весит больше, чем весь диалог до него. Команды часто смотрят только на текст промпта и забывают, что payload инструментов считается теми же токенами.

Хуже всего то, что ответ инструмента нередко длиннее самого вопроса. Пользователь пишет: "Где мой платёж?" А backend возвращает полный объект операции: идентификаторы, историю статусов, технические коды, валютные поля, следы ретраев и внутренние метки. Модели не нужен весь этот пакет, чтобы дать короткий ответ человеку.

Обычно вызов раздувают одни и те же вещи: лишние описания функций, которые не понадобятся в этом сценарии, пустые поля и debug-данные, большие массивы, которые модель не будет читать целиком, и сырые ответы инструментов без сжатия до нужных полей.

В банке это видно сразу. Чат просит проверить карту или платёж, а инструмент возвращает JSON на десятки килобайт. Даже если интеграция идёт через OpenAI-совместимый шлюз вроде RU LLM и формат вызова менять не нужно, объём данных всё равно оплачивается в токенах. Прокси не отменяет математику контекста.

Помогает простое правило: отправляйте модели только то, на чём она действительно должна принять решение. Если ей нужен статус, сумма и дата, не стоит передавать весь журнал обработки.

Начинать лучше с самого простого. Уберите пустые поля и служебные метаданные. Затем сократите массивы до нескольких последних записей или до агрегатов. После этого замените сырой ответ инструмента на короткую нормализованную структуру. Даже такая чистка часто снижает расход на 20-40% без заметной потери качества.

Как посчитать бюджет до отправки

Один запрос может укладываться в лимит по количеству вызовов и всё равно упираться в окно контекста. Поэтому бюджет считают до отправки, а не после первых ошибок в продакшене. И считать нужно не только текст пользователя.

Самая практичная формула выглядит так:

входные токены = system prompt + история диалога + найденные документы + схемы инструментов + аргументы инструментов + служебные поля
общий бюджет = входные токены + верхняя граница ответа + запас на ретраи

Сначала измерьте system prompt. Команды часто думают, что он короткий и почти ничего не меняет, но на больших потоках даже лишние 500-700 токенов на каждый вызов быстро превращаются в серьёзный расход.

Потом отдельно посчитайте историю диалога. Если у вас чат, важно понимать не только среднюю длину сессии, но и хвост распределения: сколько весят самые длинные 5-10% разговоров. Именно они обычно ломают лимиты.

Дальше оцените документы, поиск и инструменты. Если один и тот же сценарий иногда тащит два коротких фрагмента, а иногда шесть длинных кусков и полный JSON от сервиса, среднее значение мало что скажет. Нужна верхняя граница для реального тяжёлого случая.

И только после этого задавайте лимит на ответ. Не наоборот. Если вход уже занимает почти всё окно, широкий max_tokens просто создаст лишние ошибки и обрезанные ответы.

Пример: чат поддержки банка

Подберите модель под бюджет
Один и тот же запрос можно проверить на 500+ моделях через один API.

Представьте обычный кейс. Клиент пишет в чат: "Почему у меня списали деньги дважды и где возврат?" Сам вопрос короткий, но дальше система начинает собирать всё, что поможет ответить без ошибки.

В банковом сценарии к сообщению часто добавляют правила по спорным операциям, историю событий по карте, куски внутреннего FAQ и результат поиска по платёжному статусу. На глаз это выглядит как один запрос. По токенам это уже тяжёлый вызов.

Примерный расклад такой: вопрос клиента занимает 30-50 токенов, системные инструкции и правила по спорным операциям - 800-1200, история по счёту или карте за нужный период - 500-900, выдержки из FAQ и шаблонов ответа - 400-700, JSON от инструмента поиска со статусами, кодами и таймстемпами - 1000-2000, а ответ модели с пояснениями для клиента - ещё 300-700.

Даже без экзотики такой диалог легко уходит в 3500-5500 токенов. Для сравнения, короткий ответ на простой вопрос вроде "как сменить номер телефона" часто укладывается в 120-180 токенов вместе с ответом. Разница получается огромной.

Проблема обычно не в числе запросов. Команда смотрит на лимит 100 вызовов в минуту и думает, что запас есть. Но если действует потолок по токенам, 20-30 тяжёлых обращений про спорные операции могут съесть бюджет раньше, чем вы упрётесь в лимит запросов.

JSON от инструмента особенно коварен. Человек читает его быстро, а модель получает каждое поле, каждый статус и каждый служебный код. Если поиск вернул длинный payload с полной историей маршрута платежа, токены сгорают мгновенно, даже если в финальный ответ попадут две строки: операция в обработке и срок возврата до трёх дней.

В банке это чувствуется сильнее, потому что ответ редко делают совсем коротким. Модель обычно объясняет причину, срок, следующий шаг и иногда просит проверить выписку или дождаться обновления статуса. Это полезно для клиента, но дорого для бюджета.

Где команды ошибаются чаще всего

Ошибки начинаются не на сотом запросе, а на одном тяжёлом вызове. Команда смотрит на RPM, видит запас и думает, что всё в порядке. Потом один диалог с длинной историей, большим ответом и вызовом инструмента съедает минутный TPM почти целиком.

Самый частый промах - считать только текст пользователя и забывать про system prompt, историю, служебные сообщения и схему tool call. В счёте участвует весь пакет, а не одна последняя реплика.

Вторая типичная ошибка - оставлять длинную сессию как есть. После 20-30 сообщений история уже тянет за собой старые детали, которые модели больше не нужны, но токены продолжают уходить.

Третий промах - щедрый лимит на ответ. Если модели разрешить 4000 токенов вывода там, где хватило бы 400-600, расчёт быстро ломается.

Четвёртая проблема - передавать в инструмент целый объект из CRM или ERP. Модели часто нужен номер счёта, статус заявки и пара дат, а не весь JSON на десятки полей.

И наконец, многие следят за лимитом запросов, но не следят за TPM. Из-за этого сервис держит нормальную частоту вызовов и всё равно упирается в потолок по токенам.

Хороший пример - чат поддержки банка. Клиент пишет короткий вопрос про блокировку карты, но система добавляет правила комплаенса, историю диалога, результаты поиска по базе знаний и большой ответ от внутреннего инструмента. Снаружи это один запрос. По факту это уже тяжёлый пакет, который может стоить как десять обычных.

Отдельно часто ошибаются с инструментами. Разработчик берёт готовый ответ backend и передаёт его модели целиком, потому что так быстрее собрать прототип. На проде этот быстрый путь начинает жечь бюджет каждый час.

Нормальная практика проста: считать токены до отправки, резать историю по правилам, ограничивать размер ответа и фильтровать поля для инструмента. Если трафик идёт через RU LLM, такие перекосы удобно замечать по аудит-трейлам и биллингу, особенно когда смотришь не только на число запросов, но и на объём каждого вызова.

Быстрая проверка перед релизом

Смените только base_url
Оставьте свои SDK, код и промпты, а запросы отправляйте через RU LLM.

Один удачный прогон почти ничего не доказывает. Лимит по токенам чаще ломает релиз не на средней сессии, а на редком тяжёлом запросе: длинная история, пара документов в контексте и ответ с развёрнутым объяснением.

Перед запуском полезно проверить не обычный вызов, а самый тяжёлый сценарий, который реально может случиться в продакшене. Если провайдер держит много запросов в минуту, это не спасёт, когда один запрос уже не помещается в окно контекста или упирается в лимит на ответ.

Проверка обычно сводится к пяти вещам. Посчитайте верхнюю границу входа: сюда входят system prompt, история чата, найденные документы, схемы инструментов и служебные поля. Заранее ограничьте длину ответа, если пользователю нужен короткий итог. Решите, как вы обрабатываете историю: старые сообщения либо сжимают в резюме, либо обрезают по правилу. Проверьте payload инструментов, чтобы в вызов модели попадали только нужные поля, а не весь JSON из CRM, HTML страницы или base64-вложения. И настройте мониторинг до отказа: смотрите prompt tokens, completion tokens, размер payload, причину остановки ответа и ошибки по длине контекста.

Есть простой тест, который быстро ловит слабые места: возьмите длинный диалог, добавьте объёмный документ и вызов инструмента с реальными полями. Если после этого модель ещё отвечает в нужное время и укладывается в лимиты, конфигурация близка к рабочей.

Что делать дальше

Начните с замеров, а не с общих лимитов в настройках. Один и тот же сервис может тратить в три-четыре раза больше токенов в разных сценариях: короткий вопрос в чате, длинная переписка, вызов инструмента с большим JSON, генерация развёрнутого ответа. Если вы не видите средний и пиковый расход по каждому сценарию, лимит по токенам быстро превращается в сюрприз уже в продакшене.

Полезно собрать простую таблицу по каждому потоку: сколько уходит на вход, сколько на ответ и сколько добавляют инструменты. Смотрите не только среднее значение, но и верхние 5-10% запросов. Именно там обычно прячутся дорогие случаи, которые выбивают бюджет и режут качество.

Одна общая цифра почти всегда мешает. Намного полезнее держать отдельные пороги для контекста, ответа и payload инструментов, а также небольшой аварийный запас на системные инструкции и служебные поля. Тогда проще понять, что именно распухает. Иногда бот поддержки дорожает не из-за модели, а потому что в prompt внезапно попал весь журнал диалога за месяц и карточка клиента на несколько экранов.

Модель тоже не стоит выбирать только по цене за миллион токенов или по размеру окна контекста. Проверяйте её на своих кейсах: длинный контекст, длинный ответ, вызов одного инструмента, цепочка инструментов. Иногда более дешёвый токен проигрывает, потому что модель пишет длиннее, хуже ужимает контекст или чаще просит повторный вызов.

Если вы сравниваете провайдеров и модели в российском контуре, удобно гонять одинаковые сценарии через один OpenAI-совместимый endpoint. В RU LLM для этого достаточно сменить base_url на api.rullm.com и прогонять те же SDK, код и промпты без переделки интеграции.

Последний шаг самый практичный: ставьте бюджет до отправки запроса, а не после счёта. Если вызов не помещается, режьте историю, сжимайте payload инструмента и ограничивайте длину ответа. Так команда контролирует расход заранее, а не разбирается потом, почему счёт вырос вдвое.

Часто задаваемые вопросы

Когда лимит по токенам важнее лимита запросов?

Когда запросы сильно отличаются по размеру. Один короткий вопрос и один вызов с длинной историей, документами и большим ответом считаются как два запроса, но по токенам это может быть разница в десятки раз.

Если сервис упирается в TPM, счётчик RPM уже мало помогает. Тогда смотреть нужно на объём каждого вызова, а не только на их число.

Могут ли два-три запроса выбить минутный лимит?

Да, и это обычная ситуация. Пара тяжёлых вызовов с длинным контекстом легко съедает почти весь минутный бюджет, даже если график по запросам выглядит спокойно.

Из-за этого команда видит низкий RPS, а пользователи уже ловят задержки и ретраи.

Что обычно сильнее всего раздувает запрос?

Чаще всего бюджет съедают не слова пользователя, а обвязка вокруг них. Обычно больше всего весят system prompt, история чата, фрагменты из поиска, схемы инструментов, аргументы и сам ответ модели.

Если вы не считаете эти части отдельно, реальный объём почти всегда кажется меньше, чем есть на самом деле.

Почему длинная история чата так быстро жжёт токены?

Длинная переписка быстро тянет бюджет вниз, потому что каждый новый вызов тащит старые реплики за собой. Через 15–20 сообщений история часто весит больше, чем текущий вопрос.

Помогает простое правило: старые шаги сжимайте в короткое резюме или отрезайте по лимиту, если они уже не влияют на ответ.

Почему RAG с документами часто ломает расчёт?

Поиск часто приносит слишком много похожих кусков. Модель читает повторы, а качество ответа почти не растёт, зато цена и риск упереться в лимит растут сразу.

Лучше отдавать 2–3 точных фрагмента, чем 8–10 кусков на всякий случай. Для длинных PDF и отчётов это правило особенно полезно.

Как большой ответ модели ломает бюджет?

Потому что длинный вывод стоит дорого сам по себе. Если вход уже большой, подробный ответ, таблица или крупный JSON быстро добивают общий объём.

Не ставьте max_tokens с большим запасом без причины. Если пользователю нужен короткий итог, просите короткий итог и держите жёсткий потолок на генерацию.

Почему function calling и tools так дорого обходятся?

Инструменты добавляют в запрос описания функций, параметры и сами данные. Если вы передаёте сырой JSON из backend, он легко оказывается тяжелее всего диалога.

Модели редко нужен полный объект. Если для решения хватает статуса, суммы и даты, отправляйте только эти поля.

Как посчитать токены до вызова модели?

Считайте бюджет до отправки. Сложите system prompt, историю, найденные документы, схемы инструментов, аргументы и служебные поля, потом добавьте верхнюю границу ответа и небольшой запас на ретраи.

Ориентируйтесь не только на среднее значение. Самые длинные 5–10% вызовов обычно и ломают лимиты.

Что лучше сократить в первую очередь?

Сначала режьте то, что не влияет на решение модели. Обычно это старые сообщения, дубли из поиска, пустые поля, debug-данные и слишком подробные ответы инструментов.

После этого уменьшайте длину ответа. Во многих сценариях одна эта чистка заметно снижает расход без потери качества.

Что проверять перед релизом и в продакшене?

Перед релизом гоняйте не средний кейс, а самый тяжёлый: длинный чат, объёмный документ и реальный payload инструмента. Смотрите prompt tokens, completion tokens, размер payload, причину остановки и ошибки по длине контекста.

Если вы работаете через RU LLM, такие перекосы удобно ловить по аудит-трейлам и биллингу. Так команда видит, какой именно сценарий раздувает вызов.