Penyimpanan Singgahan Semantik dengan Embedding
Bangun singgahan semantik yang mengambil respons tersimpan untuk kueri yang serupa secara semantik tetapi tidak identik, dengan membandingkan embedding kueri dengan singgahan embedding permintaan sebelumnya.
Penyimpanan Singgahan Semantik dengan Embedding adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Engineering Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Keterbatasan Cache Eksak
Cache eksak hanya membantu ketika pengguna mengirim permintaan yang identik byte demi byte. Dalam kenyataannya, pengguna menyampaikan pertanyaan yang sama dengan cara berbeda: 'Bagaimana cara membatalkan langganan saya?', 'Bagaimana proses berhenti berlangganan?', dan 'Bisakah saya menghentikan paket saya?' semuanya bermaksud menanyakan hal yang sama, tetapi menghasilkan kunci cache yang berbeda. Cache eksak gagal menangani semua variasi ini. Cache semantik mengatasinya dengan mencocokkan kueri yang mirip, bukan yang identik, sehingga meningkatkan rasio cache hit secara signifikan.
Cara Kerja Cache Semantik
Cache semantik menyimpan embedding setiap kueri yang di-cache bersama respons yang di-cache. Saat kueri baru tiba, buat embedding-nya dan cari kueri yang pernah dilihat sebelumnya di dalam cache dengan kemiripan kosinus yang tinggi. Jika kueri cache terdekat berada di atas ambang kemiripan (biasanya 0,95+), kembalikan respons yang di-cache. Jika tidak ditemukan kecocokan, panggil LLM, simpan respons baru, dan tambahkan embedding kueri baru ke indeks cache untuk pencarian berikutnya.
# Semantic cache flow
# 1. New query arrives: 'How do I cancel my subscription?'
# 2. Embed it: embed_query = embed('How do I cancel my subscription?')
# 3. Search cache index for nearest cached query embedding
# 4. Find cached: 'What is the process to unsubscribe?' (similarity=0.97)
# 5. 0.97 >= threshold (0.95) → cache HIT, return cached response
# 6. If 0.82 < threshold → cache MISS, call LLM, cache result, add embedding to indexCache Semantik dalam Memori dengan NumPy
Untuk aplikasi kecil atau prototipe, terapkan cache semantik di memori menggunakan NumPy untuk menghitung kemiripan kosinus. Simpan embedding kueri yang di-cache dalam array 2D dan responsnya dalam daftar paralel. Pada setiap kueri baru, hitung kemiripan kosinus antara embedding baru dan semua embedding yang di-cache, lalu kembalikan kecocokan terdekat jika nilainya melampaui ambang batas.
import numpy as np
from openai import OpenAI
client = OpenAI()
class InMemorySemanticCache:
def __init__(self, threshold: float = 0.95):
self.threshold = threshold
self.embeddings = [] # list of np.ndarray
self.responses = [] # list of str
self.queries = [] # list of str (for inspection)
def _embed(self, text: str) -> np.ndarray:
resp = client.embeddings.create(model='text-embedding-3-small', input=text)
return np.array(resp.data[0].embedding)
def get(self, query: str) -> str | None:
if not self.embeddings:
return None
q_emb = self._embed(query)
cache_matrix = np.array(self.embeddings)
# Cosine similarity: dot product of normalized vectors
norms = np.linalg.norm(cache_matrix, axis=1)
q_norm = np.linalg.norm(q_emb)
sims = (cache_matrix @ q_emb) / (norms * q_norm + 1e-8)
best_idx = int(np.argmax(sims))
if sims[best_idx] >= self.threshold:
print(f'[SEMANTIC HIT] sim={sims[best_idx]:.3f} matched: {self.queries[best_idx]!r}')
return self.responses[best_idx]
return None
def set(self, query: str, response: str):
emb = self._embed(query)
self.embeddings.append(emb)
self.responses.append(response)
self.queries.append(query)Cache Semantik dengan Redis dan Pinecone
Untuk caching semantik dalam lingkungan produksi, simpan embedding kueri di basis data vektor agar pencarian tetangga terdekat secara aproksimasi berlangsung cepat, lalu simpan respons di Redis dengan kunci berupa ID unik. Saat kueri baru diterima, cari kueri tersimpan yang paling dekat di basis data vektor, ambil respons dari Redis menggunakan ID dalam metadata vektor, lalu kembalikan respons tersebut—semuanya tanpa memanggil LLM.
import redis
from pinecone import Pinecone
import hashlib
r = redis.Redis(decode_responses=True)
pc = Pinecone(api_key='YOUR_KEY')
index = pc.Index('semantic-cache')
SIMILARITY_THRESHOLD = 0.95
def semantic_cache_get(query: str) -> str | None:
q_emb = embed(query) # from earlier lesson
results = index.query(vector=q_emb, top_k=1, include_metadata=True)
if not results.matches:
return None
best = results.matches[0]
if best.score >= SIMILARITY_THRESHOLD:
response_key = best.metadata.get('response_key')
return r.get(response_key)
return None
def semantic_cache_set(query: str, response: str):
q_emb = embed(query)
entry_id = hashlib.sha256(query.encode()).hexdigest()[:16]
response_key = f'sem_cache_resp:{entry_id}'
r.setex(response_key, 86400, response) # 24h TTL
index.upsert(vectors=[{
'id': entry_id,
'values': q_emb,
'metadata': {'query': query[:200], 'response_key': response_key},
}])GPTCache: Cache Semantik Siap Pakai
GPTCache adalah pustaka sumber terbuka yang mengimplementasikan caching semantik untuk aplikasi LLM. Pustaka ini membungkus klien OpenAI, menyematkan kueri secara otomatis, memeriksa cache kemiripan vektor, dan menggunakan API asli sebagai cadangan ketika cache tidak memiliki hasil. GPTCache mendukung berbagai penyimpanan vektor (FAISS, Milvus, Redis) dan berbagai model embedding secara langsung, sehingga menjadi cara cepat untuk menambahkan caching semantik ke aplikasi yang sudah ada.
# pip install gptcache
from gptcache import cache
from gptcache.adapter import openai
from gptcache.embedding import OpenAI as EmbeddingOpenAI
from gptcache.manager import CacheBase, VectorBase, get_data_manager
from gptcache.similarity_evaluation.distance import SearchDistanceEvaluation
# Configure GPTCache with FAISS vector store
cache.init(
embedding_func=EmbeddingOpenAI().to_embeddings,
data_manager=get_data_manager(
CacheBase('sqlite'),
VectorBase('faiss', dimension=1536),
),
similarity_evaluation=SearchDistanceEvaluation(),
)
# Now use the wrapped openai client — caching is transparent
response = openai.ChatCompletion.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': 'What is RAG?'}],
)Memilih Ambang Kemiripan
Ambang kemiripan adalah hyperparameter terpenting dalam caching semantik. Jika terlalu tinggi (0.99+), sebagian besar variasi parafrasa tidak akan ditemukan. Jika terlalu rendah (0.85-), sistem dapat mengembalikan jawaban tersimpan yang salah untuk kueri yang berbeda tetapi tampak serupa. Validasi ambang Anda secara empiris dengan mengambil sampel pasangan kueri dan memeriksa apakah kueri yang berada di atas ambang tersebut benar-benar memiliki jawaban yang dimaksud sama. Nilai yang umum digunakan: 0.92-0.97 untuk tanya jawab faktual, dan 0.98+ untuk pembuatan kode karena perbedaan kecil sangat berpengaruh.
def validate_threshold(cache, query_pairs_with_labels):
'''
query_pairs_with_labels: list of (query1, query2, should_match: bool)
'''
true_pos = true_neg = false_pos = false_neg = 0
for q1, q2, should_match in query_pairs_with_labels:
e1, e2 = embed(q1), embed(q2)
sim = cosine_similarity(e1, e2)
matched = sim >= cache.threshold
if should_match and matched: true_pos += 1
elif not should_match and not matched: true_neg += 1
elif not should_match and matched: false_pos += 1
else: false_neg += 1
precision = true_pos / (true_pos + false_pos) if (true_pos + false_pos) else 0
recall = true_pos / (true_pos + false_neg) if (true_pos + false_neg) else 0
print(f'Precision: {precision:.3f}, Recall: {recall:.3f}')Cakupan Cache Semantik: Prompt Sistem Itu Penting
Hal penting yang perlu diperhatikan: caching semantik harus memperhitungkan prompt sistem. Dua kueri pengguna yang identik dapat menghasilkan jawaban berbeda jika prompt sistemnya berbeda (persona, basis pengetahuan, atau format respons yang berbeda). Selalu sertakan prompt sistem dalam input embedding atau buat namespace cache terpisah untuk setiap prompt sistem. Pola yang rapi adalah melakukan hash terhadap prompt sistem dan menggunakannya sebagai awalan namespace cache.
import hashlib
def make_semantic_cache_namespace(system_prompt: str) -> str:
return 'sc:' + hashlib.md5(system_prompt.encode()).hexdigest()[:8]
def semantic_cache_get_namespaced(system_prompt: str, user_query: str) -> str | None:
namespace = make_semantic_cache_namespace(system_prompt)
q_emb = embed(user_query)
# Search only within this namespace
results = index.query(
vector=q_emb,
top_k=1,
filter={'namespace': namespace},
include_metadata=True,
)
if results.matches and results.matches[0].score >= SIMILARITY_THRESHOLD:
return r.get(results.matches[0].metadata['response_key'])
return NoneAnalisis Rasio Cache Hit Semantik
Setelah menerapkan caching semantik, analisis rasio hit berdasarkan klaster kueri. Gunakan embedding kueri tersimpan itu sendiri—kelompokkan dengan K-means dan hitung rasio hit untuk setiap klaster. Klaster dengan rasio hit tinggi menunjukkan tema pertanyaan umum yang paling banyak memperoleh manfaat dari caching. Klaster dengan rasio hit rendah yang berisi kueri unik dan beragam mungkin sama sekali tidak memperoleh manfaat dari caching, sehingga dapat dikeluarkan dari cache untuk mengurangi ukuran indeks dan biaya embedding.
from sklearn.cluster import KMeans
import numpy as np
def analyze_cache_clusters(cache, n_clusters=10):
if len(cache.embeddings) < n_clusters:
print('Not enough cache entries to cluster')
return
matrix = np.array(cache.embeddings)
kmeans = KMeans(n_clusters=n_clusters, n_init=10, random_state=42)
labels = kmeans.fit_predict(matrix)
from collections import Counter
cluster_sizes = Counter(labels)
print('Query clusters by size:')
for cluster_id, count in cluster_sizes.most_common():
representative = cache.queries[labels.tolist().index(cluster_id)]
print(f' Cluster {cluster_id}: {count} queries, e.g. {representative!r}')Pertimbangan Keamanan Cache Semantik
Caching semantik menimbulkan risiko privasi: jika pengguna A mengajukan pertanyaan sensitif, responsnya mungkin dikembalikan kepada pengguna B yang mengajukan pertanyaan serupa. Hal ini dapat diterima untuk basis pengetahuan publik, tetapi tidak untuk aplikasi yang memiliki data khusus pengguna atau konten sensitif. Terapkan isolasi namespace yang ketat untuk setiap pengguna atau organisasi, dan pertimbangkan untuk sepenuhnya mengecualikan kueri yang cocok dengan pola seperti informasi pribadi dari cache.
import re
PII_PATTERNS = [
r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b', # phone numbers
r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b', # emails
r'\b\d{9}\b', # SSN-like
]
def should_cache(query: str) -> bool:
for pattern in PII_PATTERNS:
if re.search(pattern, query, re.IGNORECASE):
return False # do not cache queries with PII
return True
def secure_semantic_completion(user_id: str, query: str) -> str:
if should_cache(query):
cached = semantic_cache_get_namespaced(f'user:{user_id}', query)
if cached:
return cached
result = call_llm_api(query) # actual API call
if should_cache(query):
semantic_cache_set_namespaced(f'user:{user_id}', query, result)
return resultMenggabungkan Caching Eksak dan Semantik
Strategi caching yang paling efisien menggunakan caching eksak dan semantik dalam hierarki dua tingkat. Periksa cache eksak terlebih dahulu (paling cepat, tanpa biaya embedding), lalu segera kembalikan hasil jika ditemukan. Jika cache eksak tidak menemukan hasil, periksa cache semantik (memerlukan satu panggilan API embedding). Jika cache semantik juga tidak menemukan hasil, panggil LLM. Urutan ini meminimalkan latensi dan biaya per permintaan.
async def two_tier_cached_completion(messages: list[dict], model: str = 'gpt-4o-mini') -> str:
user_query = messages[-1].get('content', '')
system_prompt = messages[0].get('content', '') if messages and messages[0]['role'] == 'system' else ''
# Tier 1: exact cache (instant, free)
exact_key = make_cache_key(messages, model, temperature=0.0)
exact_cached = await async_r.get(exact_key)
if exact_cached:
return json.loads(exact_cached)
# Tier 2: semantic cache (one embedding call ~5ms)
sem_result = semantic_cache_get_namespaced(system_prompt, user_query)
if sem_result:
# Backfill exact cache to avoid embedding next time
await async_r.setex(exact_key, 3600, json.dumps(sem_result))
return sem_result
# Tier 3: actual LLM call
response = await async_client.chat.completions.create(
model=model, messages=messages, temperature=0.0
)
result = response.choices[0].message.content
await async_r.setex(exact_key, 3600, json.dumps(result))
semantic_cache_set_namespaced(system_prompt, user_query, result)
return resultPemanasan Cache untuk Awal yang Dingin
Cache semantik yang baru dibuat tidak memberikan manfaat apa pun sebelum diisi. Untuk aplikasi dengan pola lalu lintas yang dapat diprediksi, lakukan pemanasan awal cache saat aplikasi dimulai dengan membuat embedding dan menyimpan respons untuk pertanyaan yang paling sering diajukan berdasarkan catatan kueri historis Anda. Dengan demikian, periode awal yang dingin dapat dihilangkan, yaitu saat setiap pengguna selama jam-jam pertama operasi mengalami cache miss dan menanggung biaya API penuh.
async def warm_semantic_cache(faq_list: list[dict], system_prompt: str):
print(f'Warming cache with {len(faq_list)} FAQ entries...')
for entry in faq_list:
cached = semantic_cache_get_namespaced(system_prompt, entry['question'])
if cached:
print(f' Already cached: {entry["question"][:50]}')
continue
# Generate and cache the response
response = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': entry['question']},
],
temperature=0.0,
)
answer = response.choices[0].message.content
semantic_cache_set_namespaced(system_prompt, entry['question'], answer)
print(f' Cached: {entry["question"][:50]}')
print('Cache warming complete')Pemeriksaan Singkat
Uji pemahaman Anda tentang caching semantik dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari bahwa: caching semantik mencocokkan kueri yang serupa tetapi tidak identik dengan membandingkan embedding kueri terhadap penyimpanan vektor yang berisi embedding kueri tersimpan; ambang kemiripan mengatur kompromi antara rasio hit dan ketepatan jawaban; dan pemberian namespace pada prompt sistem mencegah cache hit lintas konteks yang keliru. Arsitektur dua tingkat yang memeriksa cache eksak sebelum cache semantik meminimalkan latensi dan biaya embedding. Selanjutnya, kita akan memanfaatkan caching awalan prompt bawaan OpenAI.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Penyimpanan Singgahan Semantik dengan Embedding” gratis?
Ya — teks lengkap “Penyimpanan Singgahan Semantik dengan Embedding” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Engineering Academy, upgrade ke CoddyKit PRO. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Penyimpanan Singgahan Semantik dengan Embedding”?
Bangun singgahan semantik yang mengambil respons tersimpan untuk kueri yang serupa secara semantik tetapi tidak identik, dengan membandingkan embedding kueri dengan singgahan embedding permintaan seb… Kamu berlatih AI Engineering Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai AI Engineering Academy?
Tidak diperlukan pengalaman sebelumnya. AI Engineering Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Penyimpanan Singgahan Semantik dengan Embedding” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Engineering Academy ini?
Ya. Setiap pelajaran AI Engineering Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Penyimpanan Singgahan Persis dengan Redis
- Penyimpanan Singgahan Semantik dengan Embedding
- Penyimpanan Singgahan Awalan Perintah OpenAI
- Pengelompokan, Perutean Model, dan Dasbor Biaya