Semantisk caching med embeddings
Opbyg en semantisk cache, der henter gemte svar til semantisk lignende, men ikke identiske forespørgsler, ved at sammenligne forespørgsels-embeddings med en cache af tidligere forespørgsels-embeddings.
Semantisk caching med embeddings er en gratis AI Engineering Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i AI Engineering Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. AI Engineering Academy-kurset indeholder 4 lektioner i alt.
Begrænsningen ved præcis caching
Præcis caching hjælper kun, når brugere sender anmodninger, der er identiske byte for byte. I virkeligheden formulerer brugere det samme spørgsmål på forskellige måder: 'Hvordan opsiger jeg mit abonnement?', 'Hvad er proceduren for at afmelde mig?' og 'Kan jeg stoppe min plan?' har alle til hensigt at stille det samme spørgsmål, men giver forskellige cache-nøgler. Præcis caching overser alle disse varianter. Semantisk caching løser dette ved at matche lignende forespørgsler i stedet for identiske, hvilket øger cachetræfprocenten markant.
Sådan fungerer semantisk caching
En semantisk cache gemmer indlejringen af hver cachet forespørgsel sammen med det cachede svar. Når en ny forespørgsel ankommer, indlejrer du den og søger i cachen efter en tidligere forespørgsel med høj cosinuslighed. Hvis den nærmeste cachede forespørgsel ligger over en lighedstærskel (typisk 0.95+), returneres det cachede svar. Hvis der ikke findes noget match, kalder du LLM'en, gemmer det nye svar og føjer den nye forespørgsels indlejring til cacheindekset til fremtidige opslag.
# 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 indexSemantisk cache i hukommelsen med NumPy
Til små programmer eller prototyper kan du implementere semantisk caching i hukommelsen ved hjælp af NumPy til beregning af cosinuslighed. Gem cachede forespørgselsindlejringer i et todimensionelt array og svarene i en parallel liste. Ved hver ny forespørgsel skal du beregne cosinusligheden mellem den nye indlejring og alle cachede indlejringer og returnere det nærmeste match, hvis det overskrider tærsklen.
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)Semantisk cache med Redis og Pinecone
Til semantisk caching i produktion skal du gemme forespørgslernes embeddings i en vektordatabase for at kunne søge hurtigt efter omtrentlige nærmeste naboer og gemme svarene i Redis med et entydigt ID som nøgle. Når der kommer en ny forespørgsel, skal du søge i vektordatabasen efter den nærmeste cachede forespørgsel, hente svaret fra Redis ved hjælp af ID'et i vektormetadataene og returnere det — alt sammen uden at kalde LLM'en.
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: En færdig semantisk cache
GPTCache er et open source-bibliotek, der implementerer semantisk caching til LLM-applikationer. Det indpakker OpenAI-klienten, embedder automatisk forespørgsler, kontrollerer en cache med vektorlighed og bruger den rigtige API som fallback ved cache-mis. Det understøtter flere vektorlagre (FAISS, Milvus, Redis) og flere embedding-modeller direkte fra start, hvilket gør det til en hurtig måde at tilføje semantisk caching til en eksisterende applikation.
# 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?'}],
)Valg af lighedstærskel
Lighedstærsklen er den vigtigste hyperparameter i semantisk caching. Hvis den er for høj (0,99+), overser du de fleste parafrasevarianter. Hvis den er for lav (0,85-), returnerer du forkerte cachede svar på forskellige, men overfladisk lignende forespørgsler. Valider din tærskel empirisk ved at udtage stikprøver af forespørgselspar og kontrollere, om forespørgsler over tærsklen faktisk har det samme tilsigtede svar. Typiske værdier er 0,92-0,97 til faktuelle spørgsmål og svar samt 0,98+ til kodegenerering, hvor små forskelle har stor betydning.
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}')Omfanget af en semantisk cache: Systemprompten er vigtig
En vigtig detalje er, at semantisk caching skal tage højde for systemprompten. To identiske brugerforespørgsler giver forskellige svar, hvis systemprompten er forskellig (forskellige personaer, vidensbaser eller svarformater). Inkluder altid systemprompten i embedding-inputtet, eller opret separate cachenavneområder for hver systemprompt. Et rent mønster er at hashe systemprompten og bruge den som præfiks for cachenavnet.
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 NoneAnalyse af hitraten for semantisk cache
Efter implementering af semantisk caching skal du analysere hitrater opdelt efter forespørgselsgruppe. Brug selve de cachede forespørgsels-embeddings — gruppér dem med K-means, og beregn hitraten for hver gruppe. Grupper med høj hitrate repræsenterer almindelige spørgsmålsområder, hvor caching giver størst gevinst. Grupper med lav hitrate og mange forskellige, unikke forespørgsler har måske slet ingen fordel af caching og kan udelades fra cachen for at reducere indeksstørrelse og embedding-omkostninger.
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}')Sikkerhedsovervejelser ved semantisk cache
Semantisk caching medfører en privatlivsrisiko: Hvis bruger A stiller et følsomt spørgsmål, kan svaret blive returneret til bruger B, som stiller et lignende spørgsmål. Det er acceptabelt for offentlige vidensbaser, men ikke for applikationer med brugerspecifikke data eller følsomt indhold. Anvend streng isolation af navneområder pr. bruger eller organisation, og overvej helt at udelukke forespørgsler, der matcher mønstre som personlige oplysninger, fra cachen.
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 resultKombination af eksakt og semantisk caching
Den mest effektive cachingstrategi bruger både eksakt og semantisk caching i et hierarki med to niveauer. Kontrollér først den eksakte cache (hurtigst og uden embedding-omkostning), og returnér straks ved et hit. Hvis der ikke er et hit i den eksakte cache, skal du kontrollere den semantiske cache (det kræver ét kald til embedding-API'et). Hvis der heller ikke er et hit dér, skal du kalde LLM'en. Denne rækkefølge minimerer både svartid og omkostninger pr. forespørgsel.
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 resultOpvarmning af cache ved kold start
En ny semantisk cache giver ingen fordel, før den er blevet fyldt. For applikationer med forudsigelige trafikmønstre skal du forvarme cachen ved opstart ved at embedde og cache svar på de hyppigst stillede spørgsmål fra dine historiske forespørgselslogge. Det eliminerer perioden med kold start, hvor alle brugere i de første driftstimer får et cache-mis og medfører de fulde API-omkostninger.
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')Hurtigt tjek
Test din forståelse af semantisk caching fra denne lektion.
Opsummering af lektionen
I denne lektion har du lært, at semantisk caching matcher lignende, men ikke-identiske forespørgsler ved at sammenligne forespørgslernes embeddings med et vektorlager af cachede forespørgsels-embeddings, at lighedstærsklen styrer afvejningen mellem hitrate og korrekthed af svar, og at navneområder for systemprompten forhindrer forkerte cache-hits på tværs af kontekster. En arkitektur med to niveauer, der kontrollerer den eksakte cache før den semantiske cache, minimerer både svartid og embedding-omkostninger. Som det næste udnytter vi OpenAI's indbyggede caching af promptpræfikser.
Lær Python med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 30
- Lektioner
- 120
Ofte stillede spørgsmål
Er lektionen “Semantisk caching med embeddings” gratis?
Ja — hele teksten til “Semantisk caching med embeddings” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af AI Engineering Academy-kurset, skal du opgradere til CoddyKit PRO. AI Engineering Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Semantisk caching med embeddings”?
Opbyg en semantisk cache, der henter gemte svar til semantisk lignende, men ikke identiske forespørgsler, ved at sammenligne forespørgsels-embeddings med en cache af tidligere forespørgsels-embedding… Du øver dig i AI Engineering Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på AI Engineering Academy?
Der kræves ingen tidligere erfaring. AI Engineering Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Semantisk caching med embeddings”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne AI Engineering Academy-lektion?
Ja. Alle AI Engineering Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Eksakt caching med Redis
- Semantisk caching med embeddings
- OpenAI-prompt-præfikscaching
- Batching, modelrouting og omkostningsdashboards