Ваш Agentic RAG сжигает 30% бюджета впустую. Как это остановить?
Если вы запустили Agentic RAG в продакшн, вы уже знаете: каждый запрос - это деньги. Токены для LLM, вызовы инструментов, поиск по векторам. А что если 30% этих запросов - дубли? Повторяющиеся вопросы от разных пользователей, одни и те же команды агентам. Вы платите за одно и то же снова и снова. И латентность растет.
В 2026 году модели вроде GPT-4.5 Turbo или Claude 3.7 Sonnet стали умнее, но и дороже. Один запрос к агенту с цепочкой инструментов легко сжигает 10-20 тысяч токенов. Умножьте на тысячу пользователей. Теперь вы видите проблему.
Проверьте свои логи за последнюю неделю. Скорее всего, вы найдете десятки вариантов "Как настроить уведомления?" или "Покажи последние отчеты". Это и есть те самые 30%.
Почему обычный кэш ломается об Agentic RAG
Вы думаете: "Поставлю Redis, буду кэшировать по хэшу промпта". Звучит логично. А потом получаете гневные письма от пользователей, которые видят вчерашние данные по акциям или старые версии документов. Потому что:
- Запросы семантически одинаковые, но текстуально разные. "Цена на нефть" и "Сколько стоит баррель?" - для LLM это одно, для хэш-функции - разное.
- Данные под капотом меняются. Кэш должен инвалидироваться при обновлении источников - а в RAG их может быть двадцать.
- Контекст агента. Ответ на "Покажи отчеты" зависит от того, какой инструмент агент выберет и в каком состоянии система. Кэшировать просто финальный ответ - рискованно.
Вот почему в конфликте свежих SQL-данных и старых векторов кэш часто становится причиной катастрофы.
Трехуровневая архитектура: от быстрых совпадений до семантики
Нужно кэшировать не только ответ, а весь конвейер принятия решений агентом. Вот архитектура, которую мы отладили на проектах с трафиком 10k+ RPM.
| Уровень | Что кэширует | Инструмент (2026) | TTL |
|---|---|---|---|
| L1: Точные совпадения | Хэш исходного промпта + контекст пользователя | Redis 8.2 (с гибридной памятью) | 5-15 минут |
| L2: Семантический кэш | Эмбеддинг запроса + выбранные инструменты агента | Weaviate Cloud с фильтрацией по метаданным | До инвалидации данных |
| L3: Кэш планов агента | Цепочку reasoning и план выполнения (не только финальный ответ) | PostgreSQL + pgvector для хранения графов выполнения | Зависит от бизнес-логики |
Эта схема отсекает до 30% запросов еще до обращения к LLM. L1 ловит идиотов - абсолютно одинаковые запросы. L2 - умных пользователей, которые спрашивают одно и то же разными словами. L3 - самых хитрых: когда агент уже решил похожую задачу и можно переиспользовать не ответ, а ход мыслей.
1 Анализ и инструментарий: что нужно перед внедрением
Не бросайтесь писать код. Сначала ответьте на три вопроса:
- Какой процент запросов повторяется именно в вашем случае? Используйте анализатор логов (например, OpenTelemetry с семантическим группировщиком запросов).
- Какие источники данных динамические, а какие статические? Карта инвалидации кэша строится отсюда.
- Сколько стоит ложный срабатывание кэша? Показать старый баланс - это критично или нет?
Инструменты 2026 года: для L1 кэша берите Redis 8.2 с новым модулем RedisVL для векторного поиска прямо в памяти - это убивает две птицы одним выстрелом. Для L2 подойдет Weaviate Cloud с нативными фильтрами по метаданным (они стали работать в 10 раз быстрее после обновления в конце 2025).
Партнерская заметка: если нужен managed Redis с продвинутыми функциями кэширования, посмотрите Redis Cloud 2026 - там появилась автоматическая тиризация кэша между памятью и SSD, что снижает стоимость на 40% для семантических кэшей.
2 Реализация L1: быстрый и глупый кэш
Начнем с простого. Ключ для кэша: md5(prompt + user_id + json.stringify(agent_state)). Звучит примитивно, но ловит 15-20% повторений. Особенно в чат-интерфейсах, где пользователь несколько раз жмет Enter.
import redis
import hashlib
import json
# Redis 8.2 с поддержкой гибридного хранения
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
def get_l1_cache_key(prompt, user_id, agent_state):
state_str = json.dumps(agent_state, sort_keys=True)
raw_key = f"{prompt}:{user_id}:{state_str}"
return hashlib.md5(raw_key.encode()).hexdigest()
def try_l1_cache(prompt, user_id, agent_state):
key = get_l1_cache_key(prompt, user_id, agent_state)
cached = redis_client.get(f"agent:l1:{key}")
if cached:
return json.loads(cached)
return None
# TTL ставим небольшую - этот кэш для горячих повторений
redis_client.setex(f"agent:l1:{key}", 300, json.dumps(result)) # 5 минут
Ошибка номер один: не включать состояние агента в ключ. Если агент только что прочитал письмо, а вы кэшируете только промпт, то ответ для следующего пользователя будет нерелевантным.
3 L2: семантический кэш с умной инвалидацией
Вот где начинается магия. Берем эмбеддинг запроса (используем модель, которая обучена на вашем домене) и ищем похожие в векторной БД. Но с важным дополнением: фильтрацией по хешам источников данных.
Как это работает: при индексации документа в RAG вы считаете его хэш (например, SHA-256). Все хэши источников, которые влияли на ответ, сохраняются в метаданные кэша. При изменении любого из источников - инвалидируете все записи кэша, где есть этот хэш.
import weaviate
from sentence_transformers import SentenceTransformer # Используем актуальную модель 2026
# Модель для эмбеддингов, дообученная на ваших промптах
encoder = SentenceTransformer('intel/E5-large-v3-finetuned-rag-2025')
client = weaviate.Client("https://your-instance.weaviate.net")
def query_semantic_cache(query_embedding, data_source_hashes):
"""Ищем похожий запрос, но только среди записей с теми же хэшами источников"""
where_filter = {
"operator": "ContainsAll",
"path": ["sourceHashes"],
"valueText": data_source_hashes # Хэши текущих версий источников
}
result = client.query.get("SemanticCache", ["query", "response", "agent_plan"])
.with_near_vector({"vector": query_embedding})
.with_where(where_filter)
.with_limit(1)
.do()
if result["data"]["Get"]["SemanticCache"]:
cached = result["data"]["Get"]["SemanticCache"][0]
# Проверяем косинусную близость, порог 0.92
if cached["_additional"]["distance"] < 0.08: # Weaviate использует distance
return cached
return None
Эта система требует синхронизации с вашим конвейером индексации RAG. Каждый раз при обновлении документа пересчитывайте его хэш и удаляйте затронутые записи кэша.
Не используйте общие модели эмбеддингов типа text-embedding-ada-003 для семантического кэша агентов. Они не понимают разницу между "открой файл" (действие) и "что такое файл" (вопрос). Fine-tune на ваших логах или берите специализированные модели.
4 L3: кэширование планов агента - самая тонкая часть
Агент в 2026 году не просто генерирует текст. Он строит план: "1. Вызови API X, 2. Отфильтруй результат по условию Y, 3. Суммируй значения". Этот план можно кэшировать и повторно использовать для похожих запросов, даже если исходные данные немного изменились.
Сохраняем в кэш граф выполнения: какие инструменты были вызваны, в каком порядке, какие параметры. При семантически похожем запросе - проверяем, можно ли переиспользовать план с подстановкой новых значений из обновленных источников.
Это особенно эффективно для агентов, работающих с структурированными данными в бизнес-процессах, где логика обработки повторяется.
# Упрощенный пример хранения плана агента
agent_plan_cache = {
"plan_id": "agg_report_q3_2025",
"template": "SELECT SUM(amount) FROM sales WHERE quarter = {quarter} AND year = {year}",
"tool_calls": [
{"tool": "sql_executor", "params_template": {"query": "SELECT..."}},
{"tool": "format_as_table", "params_template": {}}
],
"source_dependencies": ["sales_db#sha256:abc123"], # Какие источники влияют
"output_schema": {"total_sales": "float", "currency": "string"}
}
# При похожем запросе "Отчет за Q4 2025"
# 1. Находим шаблон плана по семантическому сходству
# 2. Проверяем, что зависимости обновлены (хэши совпадают с текущими)
# 3. Подставляем новые параметры {quarter: "Q4", year: "2025"}
# 4. Выполняем только подстановку, без обращения к LLM для генерации плана
Ошибки, которые сведут на нет всю экономию
Внедрили трехуровневый кэш, а пользователи жалуются? Проверьте эти точки:
- Слишком агрессивная семантическая группировка. Порог косинусной близости 0.85 вместо 0.92 - и "удали письмо" попадает в кэш для "отправь письмо". Катастрофа.
- Забыли про контекст сессии. Запрос "а теперь перешли его" без кэширования предыдущих сообщений в ключе - получите мусор.
- Инвалидация только по TTL. В мире, где данные меняются каждую секунду, TTL-кэш бесполезен. Нужна event-driven инвалидация от источников.
- Кэшируете только final answer. Агент потратил 5к токенов на reasoning, вы сохранили только итоговый "Да". В следующий раз он снова потратит 5к токенов на тот же reasoning.
Помните историю про сжигание миллионов токенов в OpenCode? Там как раз провалились на инвалидации кэша при изменении кодовой базы.
FAQ: ответы на вопросы, которые вы постеснялись задать
| Вопрос | Короткий ответ | Что проверить |
|---|---|---|
| Кэш нарушает детерминированность агентов? | Да, и это хорошо | Логируйте хит/мисс кэша. Если миш > 50% - пересмотрите эмбеддинги. |
| Как измерять экономию? | Токены до и токены после | Метрики: cache hit rate, avg tokens saved per request, latency p95. |
| Можно ли кэшировать в полностью локальной RAG? | Да, даже проще | В локальной архитектуре используйте SQLite + векторами прямо в приложении. |
| Что делать с конфиденциальными данными в кэше? | Шифровать на уровне поля | Кэшируйте только эмбеддинги запросов, а ответы храните в зашифрованном виде с ключом сессии. |
Что будет дальше? Кэш как первый уровень интеллекта
В 2027 году, я предсказываю, семантический кэш станет не просто оптимизацией, а частью reasoning-системы агента. Он будет хранить не просто ответы, а прецеденты: "в такой ситуации я сделал X, и пользователь остался доволен".
А пока - начните с L1. Добавьте Redis, посмотрите на цифры. Потом внедрите семантический кэш на основе Weaviate или Qdrant. Через месяц вы увидите, как счет за LLM-провайдера уменьшился на те самые 30%. И латентность упала.
Последний совет: никогда не кэшируйте ответы на вопросы типа "сколько сейчас времени?". Но это уже тема для другой статьи о оптимизации поиска для агентов.