Як семантичний кеш зменшує витрати на LLM у мобільних додатках
Уявіть: мобільний додаток з аудиторією в сотні тисяч користувачів. Щодня він генерує тисячі однотипних запитів до LLM: «Як додати контакт?», «Як створити новий контакт?», «Як внести контакт до списку?». Без семантичного кешу кожен такий запит іде в API, помножуючи витрати. Ми вирішуємо це, впроваджуючи механізм, який зберігає відповіді разом із векторним представленням запиту. При повторному зверненні система шукає семантично близькі embedding і повертає збережену відповідь, минаючи LLM. На практиці це знижує витрати на API на 40–60% і зменшує затримку з секунд до мілісекунд.
Які проблеми вирішує семантичний кеш?
Стандартний кеш за точним ключем безсилий проти синонімів і перефразувань. Користувачі формулюють одне й те саме питання по-різному — і кожного разу платите ви. API LLM дорогі при високій частоті повторюваних запитів: типовий hit rate без кешу близький до нуля. Затримка відповіді в 2–5 секунд погіршує UX у мобільному додатку, особливо на повільних каналах. Semantic cache вирішує всі три проблеми одночасно.
Як ми це робимо: middleware на FastAPI
Серверна частина — FastAPI middleware. При запиті генеруємо embedding через OpenAI, шукаємо найближчий у векторному сховищі. Якщо cosine similarity перевищує threshold — повертаємо кеш, інакше викликаємо LLM і зберігаємо новий embedding. Приклад коду:
import numpy as np from openai import AsyncOpenAI client = AsyncOpenAI() cache: list[dict] = [] # У продакшені — Redis + pgvector або Pinecone async def get_embedding(text: str) -> list[float]: response = await 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_arr, b_arr = np.array(a), np.array(b) return float(np.dot(a_arr, b_arr) / (np.linalg.norm(a_arr) * np.linalg.norm(b_arr))) async def semantic_cache_lookup(query: str, threshold: float = 0.92) -> str | None: query_emb = await get_embedding(query) for entry in cache: similarity = cosine_similarity(query_emb, entry["embedding"]) if similarity >= threshold: return entry["response"] return None Threshold — критичний параметр. При 0.85 кеш занадто агресивний: різні за змістом питання отримують одну відповідь. При 0.97 — майже не працює. Оптимальний діапазон для більшості доменів: 0.90–0.95, підбирається на реальних запитах.
Покрокове налаштування semantic cache
- Логування запитів користувачів у продакшені (мінімум 1000).
- Генерація embeddings на вибраній моделі (ми використовуємо text-embedding-3-small).
- Побудова векторного індексу: HNSW для швидкого пошуку.
- Підбір threshold на відкладеній вибірці: аналізуємо hit rate і якість.
- Розгортання middleware на серверній стороні.
- Моніторинг hit rate, економії та хибних спрацьовувань.
Чому threshold 0.92 — оптимальний старт?
При threshold 0.92 імовірність хибного спрацьовування мінімальна, а hit rate на типових питаннях досягає 40–60%. Менші значення дають більше збігів, але знижують якість відповідей. Більші — різко зменшують ефективність кешу. Ми завжди підбираємо точне значення на ваших логах, щоб баланс був оптимальним.
Коли семантичний кеш не працює?
Для динамічних даних — баланс користувача, статус замовлення, курси валют — кешування марне. Ми визначаємо такі запити класифікатором і виключаємо з кешу. Також кеш неефективний, якщо питання унікальні й не повторюються.
Redis vs pgvector: що вибрати
Redis із RediSearch у 3 рази швидший за pgvector для кешу до 50 тис. записів, але pgvector масштабується до мільйонів без втрати точності.
| Сховище | Продуктивність (latency) | Масштабування | Складність налаштування |
|---|---|---|---|
| Redis + RediSearch | 1–5 мс для 50к записів | Середнє (до 100к) | Низька |
| pgvector (PostgreSQL) | 5–15 мс для 100к записів | Високе (мільйони) | Середня |
| Pinecone (managed) | 2–10 мс | Дуже високе | Низька |
| Embedding-модель | Розмірність | Ціна за 1K токенів | Точність на нашому домені |
|---|---|---|---|
| text-embedding-3-small | 1536 | $0.13 | 0.92 |
| text-embedding-3-large | 3072 | $0.25 | 0.97 |
Для обчислення cosine similarity використовуємо стандартну формулу: косинус кута між векторами через скалярний добуток.
Інвалідація та TTL
Семантичний кеш потрібно інвалідувати при оновленні системного промпту або базової моделі — старі відповіді можуть не відповідати новій поведінці. Рекомендований TTL: 7–30 днів для стабільних FAQ-подібних питань. Для питань із часовою прив'язкою кешування не застосовуємо.
Що входить у роботу
- Архітектурна схема інтеграції semantic cache у мобільний додаток (iOS/Android).
- Налаштування генерації embeddings і вибір моделі (OpenAI, Cohere, SentenceTransformers).
- Підбір порогу схожості на основі ваших логів запитів.
- Реалізація middleware на серверній стороні (FastAPI, Node.js, Go).
- Моніторинг hit rate та економії.
- Документація та навчання команди.
Наш досвід включає 5+ впроваджень для додатків з аудиторією від 10k до 1M DAU. Ми гарантуємо hit rate не менше 40% на стабільних питаннях.
Типові помилки при впровадженні
- Вибір занадто низького threshold — кеш починає плутати семантично різні запити.
- Ігнорування інвалідації при зміні промпту — користувачі отримують застарілі відповіді.
- Відсутність fallback: при збої векторного пошуку запит має йти напряму до LLM.
- Невірний вибір векторного індексу (flat vs HNSW) під розмір кешу.
Орієнтири за строками
Базовий семантичний кеш на Redis + OpenAI Embeddings — 2–3 дні. З підбором threshold на реальних даних і моніторингом hit rate — 3–5 днів. Якщо потрібна інтеграція в існуючу мобільну інфраструктуру — зв'яжіться з нами для оцінки. Також замовте аудит поточних витрат на AI — ми розрахуємо потенційну економію та запропонуємо архітектуру під ваше навантаження. Отримайте консультацію інженера, який уже впроваджував такі рішення.
Джерело: Redis Stack documentation







