AI Cost Optimization: как сократить расходы на LLM на 60% без потери качества
Что такое пять техник оптимизации стоимости LLM API без деградации качества?
Оптимизация расходов на LLM охватывает пять техник, каждая из которых бьёт по отдельной категории затрат: prompt caching (устраняет повторную обработку системного промпта — скидка 50–90% в зависимости от провайдера), smart routing (направляет каждый запрос к наиболее дешёвой модели, способной его обработать — экономия 10–30x на простых задачах), Batch API (откладывает неинтерактивные запросы со скидкой 50% от стандартной цены), semantic caching (возвращает кешированные ответы на семантически схожие запросы без вызова LLM) и model downsizing (заменяет дорогие flagship-модели на более дешёвые, прошедшие валидацию на задаче-специфичном eval-датасете). Применённые последовательно, эти пять техник типично снижают общие расходы на LLM на 59–67% без измеримой потери качества пользовательских функций.
TL;DR
- -40–60% расходов на LLM API в типичном production-приложении уходят в три категории потерь: повторяющийся системный промпт, тарифицируемый на каждый запрос; дорогие flagship-модели для тривиальных задач; дублирующиеся запросы от разных пользователей.
- -Prompt caching сам по себе экономит ~$1 620/месяц для приложения с 10 000 запросов/день на системном промпте в 2 000 токенов — один день внедрения, нулевой риск для качества.
- -Smart routing экономит ~$2 145/месяц при том же трафике, направляя 60–70% запросов (классификация, извлечение, форматирование) к моделям, в 20–30 раз дешевле flagship — самый значимый единичный рычаг экономии.
- -Model downsizing — самая рискованная техника, всегда требующая предварительного eval-фреймворка: на задачах сложного reasoning замена Claude Sonnet 4.6 на GPT-5.4-mini даёт падение accuracy на 18.5% — невидимое без измерений.
- -Порядок внедрения для максимального ROI при минимальном риске: prompt caching → smart routing → Batch API → semantic caching → model downsizing.
Средний LLM-стартап тратит ~$8 000-15 000 в месяц на API-вызовы при 50 000 запросов в день. При этом 40-60% этих расходов уходят на повторяющиеся запросы, избыточные токены и использование дорогих моделей для тривиальных задач.
Снижение расходов на 60% достижимо за 2-3 недели работы. Без деградации качества, без отказа от flagship-моделей на сложных задачах. Пять техник, каждая из которых даёт измеримый результат.
Анатомия LLM-расходов: где теряются деньги
Прежде чем оптимизировать, полезно понять структуру расходов. Типичное распределение для production-приложения с AI-чатом и несколькими пайплайнами:
| Категория расходов | Доля бюджета | Причина |
|---|---|---|
| Повторяющиеся системные промпты | 25-35% | Один и тот же system prompt на каждый запрос, тысячи раз в день |
| Избыточная мощность модели | 20-30% | GPT-5.4 на задачах, где справляется GPT-5.4-mini |
| Дублирование запросов | 15-20% | Одинаковые вопросы от разных пользователей |
| Синхронные вызовы без приоритезации | 10-15% | Batch-задачи по цене реалтайма |
| Полезная нагрузка | 15-25% | Запросы, которые действительно требуют полной мощности |
Каждая техника ниже бьёт по конкретной категории. Порядок подобран по ROI: первая техника даёт максимальный эффект при минимальных усилиях.
Prompt Caching: экономия на повторяющихся префиксах
Prompt caching позволяет провайдеру кешировать начало промпта (system prompt, контекст, инструкции) и не пересчитывать токены при следующих запросах с тем же префиксом.
Anthropic предоставляет prompt caching с августа 2024. OpenAI запустил автоматическое кеширование в октябре 2024. Google Gemini поддерживает context caching с середины 2024.
Как это работает. Провайдер хеширует первые N токенов промпта. Если следующий запрос начинается с того же блока, кешированные токены не тарифицируются по полной цене. У Anthropic скидка составляет 90% на кешированные токены. У OpenAI - 50%.
Anthropic: явное кеширование
У Anthropic кеширование управляется явно через параметр cache_control в сообщениях:
{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "Ты AI-ассистент для планирования путешествий. Отвечай структурированно, используй markdown. Всегда указывай бюджет, время в пути, рейтинг безопасности локации...",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{"role": "user", "content": "Спланируй маршрут по Бали на 7 дней"}
]
}
Минимальный размер кешируемого блока - 1024 токена для Claude Sonnet/Opus, 2048 для Haiku. Кеш живёт 5 минут и продлевается при каждом обращении.
OpenAI: автоматическое кеширование
У OpenAI prompt caching работает автоматически для промптов длиннее 1024 токенов. Ничего менять в коде не нужно. Кешируется самый длинный общий префикс между запросами.
Расчёт экономии
Системный промпт AI-чата - 2000 токенов. 10 000 запросов в день. Модель Claude Sonnet 4.6.
Без кеширования:
- Входные токены system prompt: 2 000 * 10 000 = 20M токенов/день
- Стоимость: 20M * ~$3/1M = ~$60/день
С кешированием (Anthropic, 90% скидка на кешированные):
- Первый запрос: ~$3/1M (полная цена)
- Кешированные: ~$0.30/1M
- Запись в кеш: ~$3.75/1M (на 25% дороже обычного, только при первой записи)
- Стоимость: 20M * ~$0.30/1M = ~$6/день
Экономия: ~$54/день, ~$1 620/месяц. Это только system prompt. Если кешировать ещё few-shot примеры и контекст, экономия растёт пропорционально.
Паттерн: максимизация кеш-хитов
Структура промпта критична для эффективности кеширования. Кешируемый блок должен быть в начале, переменные части - в конце.
┌─────────────────────────────┐
│ System prompt (кешируется) │ ← 2000+ токенов, одинаковые для всех
├─────────────────────────────┤
│ Few-shot примеры │ ← 500-1000 токенов, одинаковые для всех
│ (кешируется) │
├─────────────────────────────┤
│ Контекст пользователя │ ← Уникальный для каждого запроса
├─────────────────────────────┤
│ Сообщение пользователя │ ← Уникальный для каждого запроса
└─────────────────────────────┘
Плохой паттерн: имя пользователя в начале system prompt. Каждый запрос от нового пользователя ломает кеш. Правильно: поставить имя после кешируемого блока или вынести в user сообщение.
Smart Routing: правильная модель для каждой задачи
Не каждый запрос требует flagship-модели. Классификация тикета, генерация заголовка, извлечение email из текста - задачи, где GPT-5.4-mini или Gemini 3.1 Flash выдают результат не хуже GPT-5.4 при разнице в цене 10-30x.
Таблица стоимости моделей (апрель 2026)
Цены указаны приблизительно на момент публикации и могут измениться. Актуальные цены — на сайтах провайдеров.
| Модель | Вход ($/1M) | Выход ($/1M) | Относительно Claude Sonnet 4.6 |
|---|---|---|---|
| GPT-5.4-mini | ~$0.15 | ~$0.60 | ~20x дешевле |
| Gemini 3.1 Flash | ~$0.075 | ~$0.30 | ~40x дешевле |
| DeepSeek-V3 | ~$0.14 | ~$0.28 | ~21x дешевле |
| Claude Haiku 3.5 | ~$0.80 | ~$4.00 | ~4x дешевле |
| GPT-5.4 | ~$2 | ~$8 | ~1x (сопоставимо) |
| Claude Sonnet 4.6 | ~$3 | ~$15 | baseline |
| Claude Opus 4.6 | ~$15 | ~$75 | ~5x дороже |
Разница между Gemini Flash и Claude Opus - 200x на входе, 250x на выходе. Это не проценты, это порядки.
Классификатор задач
Smart routing начинается с классификации. Два подхода: rule-based (по типу задачи) и LLM-based (LLM-классификатор определяет сложность).
Rule-based routing - проще, предсказуемее, достаточно для большинства случаев:
ROUTING_TABLE = {
# task_type: model
"chat_title_generation": "gemini/gemini-2.0-flash",
"ticket_classification": "gpt-5.4-mini",
"email_extraction": "gpt-5.4-mini",
"sentiment_analysis": "gemini/gemini-2.0-flash",
"translation": "deepseek/deepseek-chat",
"code_review": "anthropic/claude-sonnet-4-6",
"complex_reasoning": "anthropic/claude-sonnet-4-6",
"trip_planning": "anthropic/claude-sonnet-4-6",
}
def route_request(task_type: str, fallback: str = "gpt-5.4-mini") -> str:
return ROUTING_TABLE.get(task_type, fallback)
LLM-based router - для случаев, когда сложность задачи непредсказуема. Дешёвая модель оценивает входящий запрос и направляет на подходящую:
ROUTER_PROMPT = """Оцени сложность задачи пользователя по шкале 1-3:
1 - простая (классификация, извлечение, форматирование)
2 - средняя (суммаризация, перевод, ответ на вопрос)
3 - сложная (рассуждение, анализ, генерация плана)
Ответь ТОЛЬКО числом: 1, 2 или 3."""
COMPLEXITY_TO_MODEL = {
1: "gpt-5.4-mini", # ~$0.15/$0.60
2: "deepseek/deepseek-chat", # ~$0.14/$0.28
3: "anthropic/claude-sonnet-4-6", # ~$3/$15
}
Стоимость самого роутера: ~50 входных токенов + ~1 выходной токен через GPT-5.4-mini. Это ~$0.000008 за классификацию. При 10 000 запросов в день — ~$0.08/день. Экономия от маршрутизации — десятки долларов в день.
Реальное распределение
Практика показывает, что в типичном AI-приложении 60-70% запросов попадают в категорию “простые”, 20-25% - “средние”, и только 10-15% требуют flagship-модели.
| Сценарий | Модель | Запросов/день | Стоимость/день |
|---|---|---|---|
| Без routing | Claude Sonnet 4.6 на всё | 10 000 | ~$90 |
| С routing | Mix (70/20/10) | 10 000 | ~$18.50 |
Экономия: ~$71.50/день, ~$2 145/месяц.
Подробнее о настройке multi-provider routing через LiteLLM - в статье о multi-provider архитектуре.
Batch API: отложенные задачи за полцены
OpenAI, Anthropic и Google предлагают Batch API - отправка пакета запросов с получением результатов в течение 24 часов. Скидка: 50% от обычной цены.
Не каждая задача требует ответа за 200 мс. Генерация еженедельных отчётов, массовая классификация контента, обогащение данных, обработка загруженных документов - всё это может подождать.
Какие задачи подходят для Batch API
| Подходит | Не подходит |
|---|---|
| Ежедневная/еженедельная аналитика | Чат в реальном времени |
| Массовая классификация (100+ элементов) | Автокомплит |
| Генерация эмбеддингов для поиска | Streaming-ответы |
| Обработка загруженных файлов | Любой интерактив |
| Периодическое обогащение данных | Триггерные уведомления |
OpenAI Batch API
import json
from openai import OpenAI
client = OpenAI()
# 1. Подготовка JSONL-файла с запросами
requests = []
for i, item in enumerate(items_to_classify):
requests.append({
"custom_id": f"item-{i}",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "gpt-5.4-mini",
"messages": [
{"role": "system", "content": "Классифицируй товар по категориям: electronics, clothing, food, other. Ответь одним словом."},
{"role": "user", "content": item["description"]}
],
"max_tokens": 10
}
})
# Записать в файл
with open("batch_input.jsonl", "w") as f:
for req in requests:
f.write(json.dumps(req) + "\n")
# 2. Загрузить файл
batch_file = client.files.create(
file=open("batch_input.jsonl", "rb"),
purpose="batch"
)
# 3. Создать batch
batch = client.batches.create(
input_file_id=batch_file.id,
endpoint="/v1/chat/completions",
completion_window="24h"
)
# 4. Проверить статус (позже)
status = client.batches.retrieve(batch.id)
# status.status: "validating" → "in_progress" → "completed"
Anthropic Message Batches
У Anthropic аналогичный механизм - Message Batches API. Скидка 50%, результаты в течение 24 часов.
import anthropic
client = anthropic.Anthropic()
batch = client.messages.batches.create(
requests=[
{
"custom_id": f"item-{i}",
"params": {
"model": "claude-sonnet-4-6",
"max_tokens": 100,
"messages": [
{"role": "user", "content": f"Классифицируй: {item['description']}"}
]
}
}
for i, item in enumerate(items)
]
)
Расчёт экономии
1 000 документов в день на классификацию. Средний запрос: 500 входных + 20 выходных токенов. Модель GPT-5.4-mini.
Синхронно:
- Входные: 500K * ~$0.15/1M = ~$0.075
- Выходные: 20K * ~$0.60/1M = ~$0.012
- Итого: ~$0.087/день
Batch API (50% скидка):
- Итого: ~$0.044/день
На GPT-5.4-mini экономия скромная в абсолютных числах. Но для Claude Sonnet 4.6 на 10 000 запросов:
Синхронно: ~$45/день → Batch: ~$22.50/день. Экономия: ~$675/месяц.
Semantic Caching: одинаковые вопросы не нужно обрабатывать дважды
Prompt caching экономит на уровне провайдера (повторяющийся префикс). Semantic caching экономит на уровне приложения: если пользователь задаёт вопрос, похожий на уже обработанный, ответ берётся из локального кеша без вызова LLM.
“Какая погода в Бали в декабре?” и “Погода на Бали в декабре” - семантически идентичные запросы. Платить за второй не нужно.
Как устроен semantic cache
Запрос пользователя
│
▼
Генерация embedding (text-embedding-3-small: $0.02/1M токенов)
│
▼
Поиск в vector store (Qdrant / Redis / pgvector)
│
├── similarity > 0.95 → вернуть кешированный ответ (0 LLM-токенов)
│
└── similarity < 0.95 → вызвать LLM → сохранить ответ + embedding в кеш
Реализация на Redis + OpenAI embeddings
import hashlib
import json
import numpy as np
import redis
from openai import OpenAI
client = OpenAI()
cache = redis.Redis(host="localhost", port=6379, db=0)
SIMILARITY_THRESHOLD = 0.95
CACHE_TTL = 3600 # 1 час
def get_embedding(text: str) -> list[float]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def cosine_similarity(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def cached_completion(messages: list[dict], model: str = "gpt-5.4") -> str:
user_message = messages[-1]["content"]
query_embedding = get_embedding(user_message)
# Поиск в кеше
cached_keys = cache.keys("sem_cache:*")
for key in cached_keys:
cached_data = json.loads(cache.get(key))
similarity = cosine_similarity(query_embedding, cached_data["embedding"])
if similarity >= SIMILARITY_THRESHOLD:
return cached_data["response"]
# Кеш-промах: вызвать LLM
response = client.chat.completions.create(
model=model,
messages=messages
)
result = response.choices[0].message.content
# Сохранить в кеш
cache_key = f"sem_cache:{hashlib.md5(user_message.encode()).hexdigest()}"
cache.setex(
cache_key,
CACHE_TTL,
json.dumps({"embedding": query_embedding, "response": result})
)
return result
Для production лучше использовать vector database (Qdrant, Pinecone, pgvector) вместо линейного поиска по ключам Redis. С 10 000+ записей в кеше линейный поиск становится узким местом.
GPTCache: готовое решение
GPTCache от Zilliz - open-source библиотека для semantic caching. Поддерживает несколько embedding-моделей, vector stores и стратегий эвикции.
from gptcache import cache
from gptcache.adapter import openai
from gptcache.embedding import OpenAI as OpenAIEmbedding
from gptcache.manager import CacheBase, VectorBase, get_data_manager
from gptcache.similarity_evaluation import SearchDistanceEvaluation
# Инициализация
embedding = OpenAIEmbedding()
cache_base = CacheBase("sqlite")
vector_base = VectorBase("faiss", dimension=embedding.dimension)
cache.init(
embedding_func=embedding.to_embeddings,
data_manager=get_data_manager(cache_base, vector_base),
similarity_evaluation=SearchDistanceEvaluation()
)
# Использование — интерфейс идентичен openai
response = openai.ChatCompletion.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Погода в Бали в декабре"}]
)
Расчёт экономии
Предположения: 10 000 запросов в день, 30% кеш-хитов (консервативно для FAQ-like сценариев), модель GPT-5.4.
Без semantic cache:
- 10 000 * (500 input + 200 output tokens)
- Стоимость: 5M * ~$2/1M + 2M * ~$8/1M = ~$26/день
С semantic cache (30% хитов):
- 7 000 запросов к LLM: ~$18.20
- 10 000 embeddings: 5M * ~$0.02/1M = ~$0.10
- Итого: ~$18.30/день
Экономия: ~$7.70/день, ~$230/месяц. При 50% кеш-хитов (реалистично для support-ботов) экономия вырастает до ~$390/месяц.
Model Downsizing: системная замена дорогих моделей
Самая интуитивная техника и при этом самая рискованная при неправильном подходе. Заменить Claude Sonnet 4.6 на GPT-5.4-mini для конкретной задачи - это не переключение тумблера. Это процесс с eval-фреймворком.
Процесс downsizing
-
Собрать eval-датасет. 50-100 реальных примеров из production-логов для каждой задачи. Входные данные + ожидаемые ответы (или критерии качества).
-
Прогнать baseline. Текущая модель на eval-датасете. Записать метрики: accuracy, format compliance, latency, cost.
-
Прогнать кандидата. Более дешёвая модель на том же датасете. Те же метрики.
-
Сравнить. Если дешёвая модель выдаёт >95% от качества дорогой - безопасно переключать.
-
A/B тест в production. 10% трафика на новую модель, мониторинг через Langfuse или аналог.
Eval-фреймворк
import json
from dataclasses import dataclass
@dataclass
class EvalResult:
model: str
task: str
accuracy: float
avg_latency_ms: float
cost_per_1k_requests: float
format_compliance: float # % ответов в нужном формате
def run_eval(model: str, task: str, dataset: list[dict]) -> EvalResult:
results = []
total_cost = 0
total_latency = 0
correct = 0
format_ok = 0
for example in dataset:
start = time.time()
response = call_llm(model=model, messages=example["messages"])
latency = (time.time() - start) * 1000
total_latency += latency
total_cost += response.usage.total_cost
if evaluate_correctness(response.content, example["expected"]):
correct += 1
if validate_format(response.content, example.get("format_schema")):
format_ok += 1
results.append(response)
n = len(dataset)
return EvalResult(
model=model,
task=task,
accuracy=correct / n,
avg_latency_ms=total_latency / n,
cost_per_1k_requests=(total_cost / n) * 1000,
format_compliance=format_ok / n,
)
# Сравнение
baseline = run_eval("anthropic/claude-sonnet-4-6", "ticket_classification", dataset)
candidate = run_eval("gpt-5.4-mini", "ticket_classification", dataset)
print(f"Accuracy: {baseline.accuracy:.1%} → {candidate.accuracy:.1%}")
print(f"Cost/1K: ${baseline.cost_per_1k_requests:.2f} → ${candidate.cost_per_1k_requests:.2f}")
print(f"Latency: {baseline.avg_latency_ms:.0f}ms → {candidate.avg_latency_ms:.0f}ms")
Типичные результаты downsizing
| Задача | Было | Стало | Accuracy delta | Cost delta |
|---|---|---|---|---|
| Классификация тикетов | Claude Sonnet 4.6 | GPT-5.4-mini | -1.2% | -95% |
| Извлечение email | GPT-5.4 | GPT-5.4-mini | -0.3% | -94% |
| Генерация заголовков | Claude Sonnet 4.6 | Gemini 3.1 Flash | -0.8% | -97% |
| Суммаризация | GPT-5.4 | DeepSeek-V3 | -2.1% | -94% |
| Сложный reasoning | Claude Sonnet 4.6 | GPT-5.4-mini | -18.5% | -95% |
Последняя строка - пример того, почему eval обязателен. На сложных задачах с reasoning дешёвые модели проседают значительно. Замена вслепую приведёт к деградации продукта.
Комбинированная стратегия: сводная таблица
Каждая техника в изоляции даёт 10-50% экономии на своём участке. В комбинации эффекты складываются.
Расчёт для приложения с 10 000 запросов/день, средний запрос 500 input + 200 output токенов, базовая модель Claude Sonnet 4.6:
| Техника | Экономия/месяц | Сложность внедрения | Время внедрения |
|---|---|---|---|
| Prompt Caching | ~$1 620 | Низкая | 1 день |
| Smart Routing | ~$2 145 | Средняя | 3-5 дней |
| Batch API | ~$675 | Низкая | 1-2 дня |
| Semantic Caching | ~$230-390 | Средняя | 2-3 дня |
| Model Downsizing | ~$500-1 500 | Высокая | 1-2 недели (с eval) |
Baseline: ~$2 700/месяц (всё на Claude Sonnet 4.6, без оптимизаций).
После оптимизации: ~$900-1 100/месяц.
Итоговая экономия: 59-67%. Порядок внедрения: prompt caching (мгновенный эффект, ноль рисков) → smart routing (большой эффект, требует классификации задач) → batch API (перевод отложенных задач) → semantic caching → model downsizing (последний, так как требует eval).
Мониторинг расходов: без observability оптимизация слепая
Оптимизация без мониторинга - угадывание. Три метрики, которые стоит отслеживать ежедневно:
Cost per request - средняя стоимость одного LLM-вызова. Разбивка по задачам и моделям. Резкий рост означает регрессию: кеш перестал работать, routing отправляет на дорогую модель, промпт раздулся.
Cache hit rate - для prompt caching и semantic caching. Падение hit rate ниже порога (например, <20% для semantic cache) сигнализирует о проблемах: изменился характер запросов, TTL слишком короткий, threshold слишком высокий.
Quality score - accuracy/relevance на eval-датасете. После каждого downsizing или смены модели прогон eval покажет, не просела ли точность.
Langfuse хранит каждый trace: модель, токены, стоимость, latency. Дашборд стоимости по моделям строится за минуты. LiteLLM интегрируется с Langfuse нативно - каждый вызов через прокси автоматически логируется.
# litellm_config.yaml
litellm_settings:
success_callback: ["langfuse"]
environment_variables:
LANGFUSE_PUBLIC_KEY: "pk-..."
LANGFUSE_SECRET_KEY: "sk-..."
LANGFUSE_HOST: "https://cloud.langfuse.com"
Чего не стоит делать
Оптимизировать до запуска. Преждевременная оптимизация вредит и здесь. Сначала запустить, получить реальные данные по usage, потом оптимизировать по фактам.
Жертвовать качеством ради экономии. Если downsizing роняет accuracy с 95% до 80% - это не экономия, это деградация продукта. Пользователи уйдут, и сэкономленные $500/месяц не компенсируют потерю.
Игнорировать скрытые расходы. Semantic cache требует vector database. Eval-фреймворк требует времени на разметку датасета. Эти расходы включать в ROI.
Забывать про цены провайдеров. Модели дешевеют. Проверять pricing каждые 2-3 месяца — цены меняются быстро и в сторону снижения.
Итог
Пять техник, применённых последовательно, снижают LLM-расходы на 60% и более. Prompt caching и batch API не требуют архитектурных изменений и дают эффект в первый день. Smart routing и model downsizing требуют больше работы, но дают наибольшую абсолютную экономию.
Ключевой принцип: каждая оптимизация должна быть измеримой. Eval перед downsizing. Мониторинг после любого изменения. Дашборд с cost per request в Langfuse или аналоге. Без метрик оптимизация превращается в угадывание, а экономия - в деградацию.
Нужна помощь с оптимизацией AI-расходов? Я помогаю стартапам внедрять AI-решения и строить продукты — belov.works.
Часто задаваемые вопросы
Что представляет собой минимальная настройка prompt caching — и есть ли случаи, когда она увеличивает расходы?
cache_control: {type: "ephemeral"} в блок системного промпта, одна строка конфигурации. Для OpenAI кеширование работает автоматически для промптов длиннее 1024 токенов без изменений в коде. Кеширование увеличивает расходы в одном сценарии: системный промпт чуть меньше минимального порога (1024 токена для Claude Sonnet/Opus), но cache_control добавлен — это запускает запись в кеш по цене 125% от стандартного входного тарифа без последующих кеш-хитов. Проверьте количество токенов системного промпта перед включением кеширования. Если меньше минимального порога — либо расширьте промпт контекстом, улучшающим качество вывода, либо оставьте кеширование отключённым.