Классическое кэширование по точному совпадению (exact match) ключей не работает с языковыми моделями, так как разработчики формулируют одни и те же запросы по-разному: "Как отсортировать массив?" и "Напиши код сортировки массива" — это разные строки, но семантически идентичные задачи. В масштабах Enterprise-разработки повторные генерации типового бойлерплейта, запросы к документации или проверки линтера сжигают миллионы токенов и перегружают GPU-кластеры.
Semantic Cache (Семантический кэш) решает эту проблему на архитектурном уровне. Вместо сравнения строк, система преобразует текст запроса в вектор (embedding) и ищет в базе данных (Vector Database, например, Redis с RediSearch или Upstash Vector) ранее сохраненные ответы с векторами, расстояние между которыми меньше заданного порога. Это позволяет возвращать ответ за миллисекунды, обходя ресурсоемкий этап генерации (инференса) в LLM.
Пайплайн семантического кэша работает как прослойка (middleware) между клиентом и LLM.
[ User Prompt ] ---> [ Embedding Model (e.g., text-embedding-3-small) ] ---> (Vector [0.1, -0.4, ...])
|
v
+------------------------------------------------------------------+
| Redis Vector Search (Cosine Similarity) |
+------------------------------------------------------------------+
| |
(Score >= Threshold) (Score < Threshold)
[ Cache HIT ] [ Cache MISS ]
| |
| v
| [ Inference Engine (LLM) ]
| |
v v
[ User Client ] <---------------- (Cached Response) (Generated Response) + [ Store in Cache ]Почему семантический кэш использует векторное расстояние (Cosine Similarity) вместо традиционного строкового хэширования (MD5 / SHA-256)?
Развертывание Redis с модулем RediSearch и создание индекса для HASH-структур с полем типа VECTOR.
Настройка быстрой локальной модели для векторизации (например, nomic-embed-text) для минимизации задержек на этапе формирования ключа.
Анализ логов запросов для подбора оптимального SIMILARITY_THRESHOLD, балансируя между Cache Hit Rate и отсутствием ложных срабатываний.
Установка времени жизни (Time-To-Live) для записей в кэше, чтобы устаревшие ответы автоматически удалялись через 7-14 дней.
docker-compose.yml или package.json), агент часто генерирует одни и те же рекомендации. Кэширование на основе семантики диффа радикально ускоряет пайплайн.Для реализации семантического кэша на TypeScript используем Upstash Redis (или локальный Redis с модулем RediSearch) и OpenAI SDK (маршрутизируемый на локальную модель эмбеддингов или API).
import { Redis } from "@upstash/redis";
import OpenAI from "openai";
const redis = Redis.fromEnv();
const openai = new OpenAI(); // Подключен к локальному AI Gateway
const SIMILARITY_THRESHOLD = 0.95; // Высокий порог для точных ответов в коде
export async function getCachedCompletion(prompt: string): Promise<string> {
// 1. Получаем эмбеддинг пользовательского запроса
const embeddingResponse = await openai.embeddings.create({
model: "text-embedding-3-small", // или локальная nomic-embed-text
input: prompt,
});
const vector = embeddingResponse.data[0]?.embedding;
if (!vector) throw new Error("Не удалось сгенерировать вектор");
// 2. Ищем семантически похожие запросы в Redis (используя Vector Search)
// Предполагается, что индекс 'idx:prompts' настроен на поиск по косинусному сходству
const searchResult = await redis.call(
"FT.SEARCH",
"idx:prompts",
`*=>[KNN 1 @vector $query_vec AS score]`,
"PARAMS", "2", "query_vec", Buffer.from(new Float32Array(vector).buffer),
"RETURN", "2", "score", "response",
"DIALECT", "2"
) as any[];
// Парсинг ответа Redis RediSearch
if (searchResult && searchResult.length > 2) {
const score = parseFloat(searchResult[2][1]);
const cachedResponse = searchResult[2][3];
// Оценка Redis (distance). Косинусное сходство = 1 - distance
const similarity = 1 - score;
if (similarity >= SIMILARITY_THRESHOLD) {
console.log(`[CACHE HIT] Similarity: ${similarity.toFixed(3)}`);
return cachedResponse;
}
}
console.log("[CACHE MISS] Отправка запроса к LLM...");
// 3. Если кэш промахнулся, генерируем ответ
const llmResponse = await openai.chat.completions.create({
model: "qwen2.5-coder-32b",
messages: [{ role: "user", content: prompt }]
});
const responseText = llmResponse.choices[0]?.message.content ?? "";
// 4. Сохраняем новый результат в кэш
const cacheKey = `prompt:${Date.now()}`;
await redis.hset(cacheKey, {
prompt: prompt,
response: responseText,
// Redis RediSearch требует бинарного формата для векторов
vector: vector
});
return responseText;
}Инженер установил заниженный порог SIMILARITY_THRESHOLD = 0.82 для семантического кэша code-completion сервиса. К какому критическому производственному инциденту это приведет?
Установка SIMILARITY_THRESHOLD = 0.8. Запросы "Как обновить пользователя в БД" и "Как удалить пользователя в БД" могут иметь высокое семантическое сходство (оба про БД и пользователя), и система отдаст код обновления в ответ на запрос удаления. Для задач генерации кода порог должен быть строгим (0.95+).
Использование только текста последнего сообщения пользователя для эмбеддинга. Если пользователь пишет "Исправь ошибку", эмбеддинг будет построен только для этой фразы, игнорируя предыдущий код в контексте. В кэш должен попадать вектор всего значимого контекста или саммари диалога.
Библиотека корпоративного UI-кита обновилась, но Semantic Cache продолжает отдавать устаревшие примеры кода для генерации компонентов. При обновлении базы знаний (или системных промптов) необходимо очищать или перестраивать кэш.