Суть
Оптимизация стоимости обычно сводится к «сделаем токен дешевле» — через кэш, каскад, модель поменьше. Есть слой раньше: часть трафика физически лишняя. Три приёма на разных уровнях стека.
SingleFlight на шлюзе
Классический паттерн из бэкенда, перенесённый на модельный шлюз. Пять пользователей одновременно просят суммаризацию одной и той же горячей новости — выполняется один инференс-таск, его результат раздаётся всем пятерым.
async def coalesced_call(key: str, fn):
if key in inflight: # такой запрос уже считается
return await inflight[key] # ждём чужой результат, своего вызова не делаем
task = asyncio.create_task(fn())
inflight[key] = task
try:
return await task
finally:
del inflight[key]
Отличие от Semantic Cache: кэш работает с уже завершёнными запросами, coalescing — с одновременными. На всплеске трафика кэш ещё пуст, потому что первый ответ не готов, и без coalescing система делает N одинаковых вызовов сразу. Фактически это защита от самодоса на популярном контенте.
Debounce на уровне интерфейса
Поле поиска с подсказками от модели генерирует вызов на каждое нажатие клавиши. Пауза 400 мс перед отправкой срезает около 90% промежуточных вызовов автокомплита: пользователь допечатывает слово, и в API уходит один запрос вместо десяти.
Самая дешёвая оптимизация во всём FinOps-наборе — правится во фронтенде, не требует изменений в архитектуре и не влияет на качество.
Детерминированная пре-валидация
Regex и правила перед вызовом модели: пустой ввод, бессмысленный набор символов, заведомо не относящиеся к домену запросы, известные атакующие паттерны. Всё это отсекается за микросекунды и ноль токенов.
Двойная польза: экономия сходится с безопасностью — тот же слой ловит часть попыток инъекций до того, как они дойдут до модели (см. Guardrails, Injection Detection).
Порядок слоёв
Ввод → debounce (UI) → пре-валидация (regex) → coalescing (шлюз) → кэш → каскад → LLM
Каждый следующий слой дороже предыдущего, поэтому порядок именно такой. Ошибка — ставить семантический кэш первым: он требует эмбеддинга запроса, то есть уже стоит денег, тогда как regex-проверка бесплатна.
Ключ схлопывания и цена debounce
Включать ли user_id в ключ. Правило жёсткое и не подлежит оптимизации: всё, от чего зависит содержание ответа, входит в ключ. Если ответ зависит от прав доступа — в ключ идёт не сам user_id, а набор прав или его хеш: тогда два пользователя с одинаковыми правами разделят один вызов, а пользователь с другими правами не получит чужой ответ. Это и экономнее, чем ключ по user_id (схлопывание всё-таки происходит), и безопаснее, чем общий ключ. Соблазн «схлопнуть по тексту запроса» здесь — прямой путь к утечке между пользователями, того же класса, что ложное попадание семантического кэша (Semantic Cache).
Ломает ли debounce отзывчивость. Да, если применять его к финальной отправке — в чате пользователь ждёт реакции сразу, и задержка читается как «подвисло». Поэтому debounce ставят не на отправку, а на промежуточные срабатывания: автодополнение, предпросмотр, реакция на набор текста. Для явной отправки правильный приём другой — не откладывать вызов, а показывать, что он начался: стриминг первых токенов снимает вопрос отзывчивости лучше любой экономии, потому что воспринимаемая скорость определяется временем до первого токена, а не до последнего.
Связано с
- Agent CostControl — общая картина управления затратами, куда встраиваются эти слои
- Semantic Cache — соседний слой, работающий с завершёнными запросами
- Cascade Routing — следующий по порядку слой, уже с вызовом модели
- Guardrails — пре-валидация как точка контроля входа