Семантическое кэширование для агентов LLM с использованием Redis: сокращение затрат и задержек
Как семантическое кэширование снижает затраты и задержки при работе с большими языковыми моделями: эмбеддинги, пороговые значения, Redis и RedisVL, настройка точности и кэширование подвопросов внутри рабочих процессов агентов.
Источники: Semantic Caching for AI Agents: How to Make Your LLM Apps Faster and Cheaper
Запросы к мощным большим языковым моделям стоят дорого и выполняются медленно. Каждый, кто видел, как растут счета за API после использования агентов между реальными пользователями, понимает эту дилемму: модели, которые дают наилучшие ответы, являются одновременно самыми дорогими и медленными. Модели уровня GPT-4 могут стоить несколько долларов за тысячу запросов, а агентные рабочие процессы усугубляют ситуацию, поскольку один запрос пользователя может запустить от 8 до 20 вызовов модели.
Семантическое кэширование направлено непосредственно на снижение этих затрат, но большинство команд используют его недостаточно активно. Его легко реализовать с помощью Redis.
Почему обычные кэши не справляются
Обычный кэш считает два запроса одинаковыми только в том случае, если их размеры совпадают. Это работает для запросов к базам данных и HTTP-запросов, но не срабатывает при работе с естественным языком. Представьте себе сотрудника службы поддержки, который получает следующие запросы от трех разных пользователей:
- Клиент спрашивает, как запросить возврат средств
- Ещё кто-то требует вернуть свои деньги
- Третий хочет узнать, что гласит политика возврата средств
Кэш на уровне байтов видит три не связанных между собой входных данных и оплачивает три вызова больших языковых моделей. Человек мог бы сказать, что все трое хотят один и тот же ответ. Семантический кэш работает на основе смысла, а не символов.
Механизм в четыре шага
- Встраиваем поступивший запрос, преобразуя его в высокомерную вектор с помощью модели встраивания.
- Сравниваем этот вектор со всеми сохранёнными записями, используя косинусовое расстояние или аналогичный показатель.
- Если ближайшая запись находится в пределах заданного порога расстояния, считаем это совпадением и сразу возвращаем сохранённый ответ.
- Если ничего достаточно близкого нет, отправляем запрос через вашу систему RAG или большую языковую модель, затем сохраняем вопрос и ответ на будущее использование.
Вопросы, сформулированные по-другому, в итоге получают одинаковые ответы. По мере заполнения кэша к модели поступает всё меньше трафика.
Минимальная версия с нуля
Прежде чем использовать готовую библиотеку, полезно разобраться в её внутренних механизмах. Начните с импортов:
from sentence_transformers import SentenceTransformer
import numpy as np
Затем встроите небольшой набор FAQ и напишите алгоритм поиска по ближайшему соседу:
model = SentenceTransformer("all-mpnet-base-v2")# Embed your FAQ dataset
faq_questions = ["How do I get a refund?", "Where is my order?", ...]
faq_embeddings = model.encode(faq_questions)def cosine_distance(a, b):
return 1 - np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))def check_cache(query: str, threshold: float = 0.3):
query_embedding = model.encode(query)
distances = [cosine_distance(query_embedding, e) for e in faq_embeddings]
best_idx = np.argmin(distances)
best_distance = distances[best_idx]
if best_distance < threshold:
return faq_answers[best_idx] # Cache hit
return None # Cache miss
Попробуйте тестовый вопрос о том, сколько дней занимает поступление возврата средств, и система возвращает сохранённый FAQ о получении возврата как наиболее близкий результат с косинусным расстоянием примерно 0,33. Не было найдено совпадений только на основе общих слов; векторы просто находятся рядом друг с другом.
Работа с Redis
В упрощённой версии отсутствует функция сохранения данных, срок действия, разделение между арендаторами и оптимизированный поиск векторов. Для любых сервисов, ориентированных на клиентов, требуется настоящая база данных. Redis вместе с open-source SDK RedisVL предоставляет всё необходимое сразу.
from redisvl.extensions.cache.llm import SemanticCache
from redisvl.utils.vectorize import HFTextVectorizer
Выберите модель вживления, разработанную специально для этой задачи, затем создайте кэш:
# Load a cache-optimized embedding model
vectorizer = HFTextVectorizer("redis/langcache-embed-v1")# Create the cache
cache = SemanticCache(
name="customer_support_cache",
vectorizer=vectorizer,
redis_client=redis_client,
distance_threshold=0.3
)# Set TTL (time to live) — keeps cache fresh
cache.set_ttl(86400) # 24 hours
Модель langcache-embed-v1 — это модель векторных эмбеддингов с открытыми весами, отрегулированная для семантического кэширования, поэтому она обычно превосходит универсальные эмбеддеры в этой задаче.
С маршрутом Redis поставляются три функции для производственного использования:
- TTL. Записи самостоятельно истекают сроком действия, поэтому устаревшие ответы не остаются актуальными по мере изменения продукта.
- Namespacing. Команды, пользователи или арендаторы могут иметь изолированный кэш внутри одного экземпляра Redis.
- Оптимизированный поиск векторов. Индексация HNSW обеспечивает поиск ближайших соседей с низкой задержкой даже при больших объемах данных.
Четыре показателя, на которые стоит обратить внимание
Создание кэша — это простая часть. Также необходимо убедиться, что он работает корректно.
Коэффициент совпадений — это доля запросов, находящих соответствие в заданном диапазоне, и именно он определяет размер сэкономленных средств. При коэффициенте совпадений 30% в системе, выполняющей 10 000 вызовов LLM в день, 3 000 вызовов остаются без результата.
Точность показывает, какая доля найденных совпадений содержит правильный ответ. Кэш, который часто возвращает неверные ответы, хуже того, когда ответов вообще нет.
Покрытие указывает, какая доля запросов, которые должны были найти совпадение, действительно его нашла, при условии наличия соответствующей записи. Низкое покрытие приводит к тому, что сэкономленные средства не могут быть использованы.
Улучшение задержки сравнивает два варианта обработки запросов. По данным тестов, время ответа кэша составляет примерно 2–5 мс против 300–500 мс для типичного вызова GPT-4, то есть примерно в 160 раз быстрее. То, что вы на самом деле ощущаете, зависит от частоты совпадений, как показывает следующая формула:
With-Cache Latency = (avg_llm_latency × (1 - hit_rate)) + (avg_cache_latency × hit_rate)
При уровне успешных поисков 30% ожидается снижение общей задержки примерно на 25–30%. Как только уровень загрузки кэша достигнет 60–75%, преимущества становятся значительными.
Использование кэша в качестве классификатора
Каждый поиск заканчивается одним из четырех результатов:
| Кэш возвращает результат | Кэш не находит результат | |
|---|---|---|
| Результат должен был быть найден в кэше | Положительный результат | Отрицательный результат |
| Результат должен был отсутствовать в кэше | Ложноположительный результат | Положительный результат |
Опасными являются ложноположительные результаты: два вопроса, похожих друг на друга, но имеющих разное значение, из-за чего кэш возвращает неверный ответ. Вопрос о возврате средств после изменения решения и вопрос о общей процедуре возврата могут находиться рядом друг от друга в пространстве закодирования, однако каждый из них требует отдельного ответа. Именно в таких случаях кэшу необходимо проявлять максимальную осторожность, поскольку его ответ кажется совершенно правдоподобным.
Повышение точности
Регулировка порога. Порог расстояния — это наиболее влиятельная настройка. Меньшее значение повышает точность, но снижает полноту, тогда как большее значение действует наоборот. Попробуйте различные значения на репрезентативном наборе тестов и выберите тот, который максимизирует показатель F1 — гармоническое среднее точности и полноты. Благодаря этому подходу точность выросла с 79% до 90%, при этом уровень точных ответов остался на уровне 25%.
Переранжирование с использованием кросс-энкодера. Стандартные модели эмбеддингов кодируют каждое предложение по отдельности. Кросс-энкодер считывает запрос и кандидатские предложения вместе, что позволяет уловить нюансы, ускользающие от бай-энкодеров. Возьмем предложение о том, что банк повышает процентные ставки, и другое о наводнении реки рядом с банком после сильных дождей: общее слово «банк» приводит их эмбеддинги к довольно близким значениям, но кросс-энкодер видит, что они не связаны. В экспериментах переранжирование позволило довести точность до 94%. Кросс-энкодеры не могут генерировать векторы для поиска, поэтому они используются в качестве второго этапа над кандидатами top-K, которые получает модель эмбеддингов.
Попросите небольшую нейросеть совершить оценку. Для максимальной точности используйте недорогую модель, такую как GPT-4o-mini, чтобы проверить сомнительные случаи с помощью простого запроса: являются ли эти два вопроса семантически эквивалентными? Она отвечает за несколько миллисекунд за часть стоимости полноценного запроса. Тестирование показало 100% точность, но более низкую воспроизводимость, поскольку нейросеть иногда отклоняет корректные совпадения. Будет ли это приемлемо, зависит от сферы применения. В обслуживании клиентов немного неверный ответ из кэша может оказаться хуже, чем новый звонок.
Сначала выполните поиск с использованием алгоритма fuzzy-match. Прежде чем вычислять любые векторные представления, проведите сравнение запроса с сохраненными записями с учетом расстояния редактирования. Если запрос отличается от одной из записей на два или три замененных символа, это почти наверняка ошибка ввода того же вопроса, поэтому следует сразу соотнести их. Это позволяет полностью пропустить этап генерации векторных представлений, сокращая время обработки почти до нуля. При высоких порогах схожести это обеспечивало 100% точность и 94% воспроизводимость на тестовых наборах, содержащих множество ошибок ввода.
Более значительный преимущество: кэширование внутри агентов
Кэширование только окончательного ответа на вопрос пользователя — это неполная модель понимания ситуации. Агенты работают иначе. Типичный алгоритм обработки сложного вопроса поддержки выглядит следующим образом:
- Разделите запрос на 3–5 подвопросов.
- Получите соответствующие документы из базы знаний.
- Попросите большую языковую модель сформулировать ответ.
- Оцените качество ответа и, при необходимости, повторите процесс.
Каждый подвопрос — это возможность воспользоваться кэшем. Если агент уже ответил на вопрос «Каковы лимиты использования API?», ему не нужно снова обращаться к модели, когда этот подвопрос появляется в сложном запросе другого пользователя.
Агент для глубоких исследований на основе LangGraph был протестирован в трех сценариях:
| Сценарий | Доля успешных ответов | Количество вызовов LLM | Задержка |
|---|---|---|---|
| Новый пользователь, холодный кэш | 25% | 8 | примерно 20 сек |
| Возвращающийся пользователь, теплый кэш | 75% | 4 | примерно 13 сек |
| Предпродажный анализ | 75% | 6 | примерно 18 сек |
В третьем сценарии три из каждых четырех подвопросов поступали из кэша. Количество вызовов модели сократилось с 8 до отметки от 4 до 6, а задержка от начала до конца обработки снизилась примерно на треть. Качество ответов осталось прежним — в обоих случаях получались персонализированные и точные ответы.
Эффект накапливается. Каждая успешная обработка пополняет кэш, и последующие пользователи с похожими вопросами пользуются всеми ранее собранными данными, поэтому экономия растет по мере того, как система обрабатывает сотни или тысячи запросов.