# AI Cost Optimization: как сократить расходы на LLM на 60% без потери качества

> Пять техник снижения затрат на LLM API: prompt caching, smart routing, Batch API, semantic caching, model downsizing. Таблицы стоимости до/после и конфиги.
> Author: Roman Belov · Published: 2026-06-09 · Source: https://futurecraft.pro/ru/blog/ai-cost-optimization/

Средний 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` в сообщениях:

```json
{
  "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 2.5 Flash-Lite выдают результат не хуже GPT-5.4 при разнице в цене 10-30x.

### Таблица стоимости моделей (апрель 2026)

> Цены указаны приблизительно на момент публикации и могут измениться. Актуальные цены — на сайтах провайдеров.

| Модель | Вход ($/1M) | Выход ($/1M) | Относительно Claude Sonnet 4.6 |
|--------|------------|-------------|-------------------------------|
| GPT-5.4-mini | ~$0.15 | ~$0.60 | ~20x дешевле |
| Gemini 2.5 Flash-Lite | ~$0.10 | ~$0.40 | ~30x дешевле |
| 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.8 | ~$5 | ~$25 | ~2.5x дороже |

Разница между Gemini Flash-Lite и Claude Opus — 150x на входе, почти 190x на выходе. Это не проценты, это порядки.

### Классификатор задач

Smart routing начинается с классификации. Два подхода: rule-based (по типу задачи) и LLM-based (LLM-классификатор определяет сложность).

**Rule-based routing** - проще, предсказуемее, достаточно для большинства случаев:

```python
ROUTING_TABLE = {
    # task_type: model
    "chat_title_generation": "gemini/gemini-2.5-flash-lite",
    "ticket_classification": "gpt-5.4-mini",
    "email_extraction": "gpt-5.4-mini",
    "sentiment_analysis": "gemini/gemini-2.5-flash-lite",
    "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** - для случаев, когда сложность задачи непредсказуема. Дешёвая модель оценивает входящий запрос и направляет на подходящую:

```python
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 архитектуре](/ru/blog/multi-provider-llm-architecture/).

## Batch API: отложенные задачи за полцены

OpenAI, Anthropic и Google предлагают Batch API - отправка пакета запросов с получением результатов в течение 24 часов. Скидка: 50% от обычной цены.

Не каждая задача требует ответа за 200 мс. Генерация еженедельных отчётов, массовая классификация контента, обогащение данных, обработка загруженных документов - всё это может подождать.

### Какие задачи подходят для Batch API

| Подходит | Не подходит |
|----------|------------|
| Ежедневная/еженедельная аналитика | Чат в реальном времени |
| Массовая классификация (100+ элементов) | Автокомплит |
| Генерация эмбеддингов для поиска | Streaming-ответы |
| Обработка загруженных файлов | Любой интерактив |
| Периодическое обогащение данных | Триггерные уведомления |

### OpenAI Batch API

```python
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 часов.

```python
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

```python
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+ записей в кеше линейный поиск становится узким местом.

### Готовые решения: Redis LangCache

Долгое время стандартным open-source вариантом был [GPTCache](https://github.com/zilliztech/GPTCache) от Zilliz, но проект фактически перестал развиваться: поддержка новых моделей и API не добавляется, и для нового кода его брать не стоит.

Живая альтернатива — [Redis LangCache](https://redis.io/langcache/): managed-сервис semantic caching поверх Redis. Работает через REST API, генерация эмбеддингов и векторный поиск происходят на стороне сервиса — в приложении остаётся один HTTP-вызов перед обращением к LLM. Для self-hosted варианта достаточно связки Redis + embeddings из примера выше, а у LiteLLM-прокси есть встроенный semantic cache на базе Redis — отдельная библиотека не понадобится.

### Расчёт экономии

Предположения: 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

1. **Собрать eval-датасет.** 50-100 реальных примеров из production-логов для каждой задачи. Входные данные + ожидаемые ответы (или критерии качества).

2. **Прогнать baseline.** Текущая модель на eval-датасете. Записать метрики: accuracy, format compliance, latency, cost.

3. **Прогнать кандидата.** Более дешёвая модель на том же датасете. Те же метрики.

4. **Сравнить.** Если дешёвая модель выдаёт >95% от качества дорогой - безопасно переключать.

5. **A/B тест в production.** 10% трафика на новую модель, мониторинг через [Langfuse](/ru/blog/llm-observability-langfuse/) или аналог.

### Eval-фреймворк

```python
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 2.5 Flash-Lite | -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 000 запросов/день, средний запрос — 2 000 токенов системного промпта + 500 входных + 200 выходных, базовая модель Claude Sonnet 4.6 (~$105/день, ~$3 150/месяц):

| Шаг | Допущение | Расход/месяц после шага | Сложность | Время внедрения |
|-----|-----------|------------------------|-----------|-----------------|
| Baseline | всё на Claude Sonnet 4.6, без оптимизаций | ~$3 150 | — | — |
| + Prompt Caching | ~80% кеш-хитов на системный промпт | ~$1 940 | Низкая | 1 день |
| + Smart Routing | 40% запросов уходят на GPT-5.4-mini | ~$1 230 | Средняя | 3-5 дней |
| + Batch API | 15% трафика — отложенные задачи со скидкой 50% | ~$1 130 | Низкая | 1-2 дня |
| + Semantic Caching | 10% кеш-хитов | ~$1 020 | Средняя | 2-3 дня |

Model downsizing отдельной строкой в водопад не включён: в этом сценарии он по большей части перекрывается smart routing — перевод задач на дешёвые модели после eval и есть его финальная стадия. При этом он остаётся самой трудозатратной техникой из пяти (1-2 недели вместе с постройкой eval). Долю routing мы взяли консервативно: 60-70% запросов классифицируются как простые, но не каждый из них можно вынести из интерактивного флоу без последствий для UX.

**Итоговая экономия: ~67%** (~$3 150 → ~$1 020/месяц). Порядок внедрения: 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](/ru/blog/llm-observability-langfuse/) хранит каждый trace: модель, токены, стоимость, latency. Дашборд стоимости по моделям строится за минуты. LiteLLM интегрируется с Langfuse нативно - каждый вызов через прокси автоматически логируется.

```yaml
# 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](https://belov.works).*
