В сервисе разбора обращений один и тот же справочник тарифов занимал 18 тысяч входных токенов. Его отправляли заново с каждым тикетом. Ночная переиндексация шла синхронными запросами, а оператор ждал полный ответ, хотя первые слова были готовы через секунду. Команда собиралась обучать свою модель. Сначала она перестала оплачивать собственную небрежность.
Пять разных инструментов
Кэширование экономит повторный ввод. Оно работает, когда стабильный префикс запроса байт-в-байт одинаков: system prompt и справочники ставят первыми, пользовательские данные последними. Кэш ответа уместен только для детерминированных, обезличенных запросов; ключ обязан включать tenant, версии prompt, модели и корпуса.
Batch API снижает цену фоновой работы ценой задержки. Для ночной классификации каталога это хороший обмен, для чата поддержки нет. Streaming почти не меняет стоимость и полное время выполнения, зато уменьшает time to first token. Пользователь видит начало ответа, но сервер всё равно должен пережить обрыв соединения и сохранить итог.
Distillation переносит поведение дорогой модели в более дешёвую на размеченном наборе. Fine-tuning закрепляет формат, стиль или узкую классификацию. Ни один из них не добавляет свежих фактов: для этого нужен retrieval. Обучать модель потому, что prompt неудобно поддерживать, довольно дорогой способ не чинить prompt.
Практический каркас
from hashlib import sha256
import json
def cache_key(*, tenant: str, prompt_version: str, model: str, payload: dict) -> str:
body = json.dumps(payload, sort_keys=True, ensure_ascii=False)
raw = f"{tenant}:{prompt_version}:{model}:{body}"
return sha256(raw.encode()).hexdigest()
async def answer(req):
key = cache_key(tenant=req.tenant_id, prompt_version="support-v12",
model=settings.STRONG_MODEL, payload=req.model_dump())
if cached := await redis.get(key):
return cached
result = await generate(req)
await redis.set(key, result, ex=900)
return resultСхема выбора проста:
повторяется вход? -> prompt cache
ответ можно переиспользовать безопасно? -> response cache
результат терпит часы? -> batch
важно быстро показать начало? -> streaming
много размеченных примеров и стабильная задача? -> distillation/fine-tuningЧто ломается
Кэш без версии возвращает ответ старого prompt. Общий ключ показывает данные соседнего tenant. Streaming создаёт иллюзию успеха, хотя генерация оборвалась после первого абзаца. Batch-задачи теряются без собственного job id и сверки результатов. Fine-tuning деградирует после смены входных документов, а команда спорит с моделью вместо проверки drift.
Правило: сначала измерьте повтор, допустимую задержку и стоимость ошибки. Выбирайте механизм по найденному ограничению, а не по моде.
Практикум 46.1
Возьмите сто реальных вызовов. Посчитайте долю общего префикса, кандидатов на безопасный response cache и задач с допустимой задержкой больше часа. Реализуйте один механизм, сравните p50/p95 latency, стоимость успешного результата и качество на прежнем eval-наборе. Затем намеренно смените prompt version и убедитесь, что старый кэш не используется.