AI Engineering Academy · Lektion

Semantisches Caching mit Embeddings

Erstellen Sie einen semantischen Cache, der gespeicherte Antworten für semantisch ähnliche, aber nicht identische Anfragen abruft, indem er Anfrage-Embeddings mit einem Cache früherer Anfrage-Embeddings vergleicht.

Lektion 2 von 413 Schritte

Semantisches Caching mit Embeddings ist eine kostenlose AI Engineering Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Engineering Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Die Einschränkung des exakten Cachings

Exaktes Caching hilft nur, wenn Benutzer Byte für Byte identische Anfragen senden. In der Realität formulieren Benutzer dieselbe Frage unterschiedlich: „Wie kündige ich mein Abonnement?“, „Wie kann ich mich abmelden?“ und „Kann ich meinen Tarif beenden?“ zielen alle auf dieselbe Frage ab, erzeugen aber unterschiedliche Cache-Schlüssel. Exaktes Caching verfehlt all diese Varianten. Semantisches Caching löst dieses Problem, indem es ähnliche statt identischer Abfragen abgleicht und dadurch die Cache-Trefferquoten deutlich erhöht.

So funktioniert semantisches Caching

Ein semantischer Cache speichert das Embedding jeder gecachten Abfrage zusammen mit der gecachten Antwort. Wenn eine neue Abfrage eingeht, wird daraus ein Embedding erstellt und im Cache nach einer zuvor gesehenen Abfrage mit hoher Kosinusähnlichkeit gesucht. Liegt die ähnlichste gecachte Abfrage über einem Ähnlichkeitsschwellenwert (typischerweise über 0,95), wird ihre gecachte Antwort zurückgegeben. Wird keine Übereinstimmung gefunden, wird das LLM aufgerufen, die neue Antwort gespeichert und das Embedding der neuen Abfrage für künftige Suchen zum Cache-Index hinzugefügt.

# 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 index

Semantischer In-Memory-Cache mit NumPy

Für kleine Anwendungen oder Prototypen können Sie semantisches Caching mit NumPy zur Berechnung der Kosinusähnlichkeit im Arbeitsspeicher implementieren. Speichern Sie die Embeddings der gecachten Abfragen in einem 2D-Array und die Antworten in einer parallelen Liste. Berechnen Sie bei jeder neuen Abfrage die Kosinusähnlichkeit zwischen dem neuen Embedding und allen gecachten Embeddings und geben Sie die ähnlichste Übereinstimmung zurück, wenn sie den Schwellenwert überschreitet.

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)

Semantischer Cache mit Redis und Pinecone

Für semantisches Caching in der Produktion speichern Sie Abfrage-Embeddings in einer Vektordatenbank, um schnell nach ungefähren nächsten Nachbarn zu suchen, und Antworten in Redis unter einer eindeutigen ID. Wenn eine neue Abfrage eingeht, suchen Sie in der Vektordatenbank nach der ähnlichsten gecachten Abfrage, rufen die Antwort mithilfe der ID in den Vektormetadaten aus Redis ab und geben sie zurück — und das alles, ohne das LLM aufzurufen.

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: Ein fertiger semantischer Cache

GPTCache ist eine Open-Source-Bibliothek, die semantisches Caching für LLM-Anwendungen implementiert. Sie kapselt den OpenAI-Client, erstellt automatisch Embeddings für Abfragen, prüft einen Cache für Vektorähnlichkeiten und greift bei Cache-Misses auf die eigentliche API zurück. Sie unterstützt mehrere Vektorspeicher (FAISS, Milvus, Redis) und mehrere Embedding-Modelle direkt nach der Installation und ermöglicht dadurch eine schnelle Ergänzung des semantischen Cachings in einer bestehenden Anwendung.

# 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?'}],
)

Den Ähnlichkeitsschwellenwert auswählen

Der Ähnlichkeitsschwellenwert ist der wichtigste Hyperparameter beim semantischen Caching. Ist er zu hoch (0,99+), werden die meisten paraphrasierten Varianten verfehlt. Ist er zu niedrig (0,85-), werden falsche gecachte Antworten auf unterschiedliche, aber oberflächlich ähnliche Abfragen zurückgegeben. Validieren Sie Ihren Schwellenwert empirisch, indem Sie Abfragepaare auswählen und prüfen, ob Abfragen oberhalb des Schwellenwerts tatsächlich dieselbe beabsichtigte Antwort haben. Typische Werte sind 0,92–0,97 für faktische Fragen und Antworten sowie 0,98+ für Codegenerierung, bei der kleine Unterschiede eine große Rolle spielen.

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}')

Geltungsbereich des semantischen Caches: Der System-Prompt ist entscheidend

Ein entscheidendes Detail: Beim semantischen Caching muss der System-Prompt berücksichtigt werden. Zwei identische Benutzerabfragen erzeugen unterschiedliche Antworten, wenn sich der System-Prompt unterscheidet (andere Personas, andere Wissensdatenbanken, andere Antwortformate). Beziehen Sie den System-Prompt immer in die Eingabe für das Embedding ein oder erstellen Sie separate Cache-Namespaces für jeden System-Prompt. Ein sauberes Muster besteht darin, den System-Prompt zu hashen und ihn als Präfix für den Cache-Namespace zu verwenden.

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 None

Analyse der Trefferquote des semantischen Caches

Analysieren Sie nach der Einführung des semantischen Cachings die Trefferquoten, aufgeschlüsselt nach Abfrageclustern. Verwenden Sie dazu die Embeddings der gecachten Abfragen selbst — clustern Sie sie mit K-Means und berechnen Sie die Trefferquote pro Cluster. Cluster mit hoher Trefferquote stehen für häufige Fragethemen, bei denen sich Caching besonders lohnt. Cluster mit niedriger Trefferquote und vielfältigen, einmaligen Abfragen profitieren möglicherweise überhaupt nicht vom Caching und könnten aus dem Cache ausgeschlossen werden, um Indexgröße und Embedding-Kosten zu reduzieren.

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}')

Sicherheitsaspekte des semantischen Caches

Semantisches Caching bringt ein Datenschutzrisiko mit sich: Wenn Benutzer A eine sensible Frage stellt, könnte die Antwort an Benutzer B zurückgegeben werden, der eine ähnliche Frage stellt. Das ist für öffentliche Wissensdatenbanken akzeptabel, nicht jedoch für Anwendungen mit benutzerspezifischen Daten oder sensiblen Inhalten. Wenden Sie eine strikte Namespace-Isolierung pro Benutzer oder Organisation an und erwägen Sie, Abfragen, die beispielsweise auf personenbezogene Daten hindeuten, vollständig vom Caching auszuschließen.

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 result

Exaktes und semantisches Caching kombinieren

Die effizienteste Caching-Strategie verwendet exaktes und semantisches Caching in einer zweistufigen Hierarchie. Prüfen Sie zuerst den exakten Cache (am schnellsten, ohne Embedding-Kosten) und geben Sie das Ergebnis bei einem Treffer sofort zurück. Wenn der exakte Cache keinen Treffer liefert, prüfen Sie den semantischen Cache (dafür ist ein Embedding-API-Aufruf erforderlich). Wenn auch der semantische Cache keinen Treffer liefert, rufen Sie das LLM auf. Diese Reihenfolge minimiert sowohl die Latenz als auch die Kosten pro Anfrage.

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 result

Cache-Aufwärmen beim Kaltstart

Ein neuer semantischer Cache bringt keinen Nutzen, solange er nicht befüllt ist. Für Anwendungen mit vorhersehbaren Zugriffsmustern können Sie den Cache beim Start vorab aufwärmen, indem Sie für die am häufigsten gestellten Fragen aus Ihren historischen Abfrageprotokollen Embeddings erstellen und Antworten cachen. Dadurch entfällt die Kaltstartphase, in der jeder Benutzer während der ersten Betriebsstunden einen Cache-Miss verursacht und die vollständigen API-Kosten auslöst.

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')

Kurze Überprüfung

Testen Sie Ihr Verständnis des semantischen Cachings aus dieser Lektion.

Zusammenfassung der Lektion

In dieser Lektion haben Sie gelernt: semantisches Caching gleicht ähnliche, aber nicht identische Abfragen ab, indem es Abfrage-Embeddings mit einem Vektorspeicher gecachter Abfrage-Embeddings vergleicht, der Ähnlichkeitsschwellenwert das Verhältnis zwischen Trefferquote und Antwortgenauigkeit steuert und das Namespacing des System-Prompts falsche Cache-Treffer über verschiedene Kontexte hinweg verhindert. Eine zweistufige Architektur, die den exakten Cache vor dem semantischen Cache prüft, minimiert sowohl Latenz als auch Embedding-Kosten. Als Nächstes nutzen wir das integrierte Prompt-Präfix-Caching von OpenAI.

Kostenlos starten

Lerne Python mit einem KI-Tutor — kostenlos

Schreibe und führe echten Code in deinem Browser aus, bekomme sofortige Hilfe von einem 24/7 KI-Tutor und setze dein Lernen im Web oder in der App fort.

Kurse
30
Lektionen
120

Häufig gestellte Fragen

Ist die Lektion „Semantisches Caching mit Embeddings“ kostenlos?

Ja — der vollständige Text von „Semantisches Caching mit Embeddings“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Engineering Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Semantisches Caching mit Embeddings“?

Erstellen Sie einen semantischen Cache, der gespeicherte Antworten für semantisch ähnliche, aber nicht identische Anfragen abruft, indem er Anfrage-Embeddings mit einem Cache früherer Anfrage-Embeddi… Du übst AI Engineering Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um AI Engineering Academy zu starten?

Keine Vorkenntnisse erforderlich. AI Engineering Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.

Wie lange dauert die Lektion „Semantisches Caching mit Embeddings“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser AI Engineering Academy-Lektion Code schreiben und ausführen?

Ja. Jede AI Engineering Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Exaktes Caching mit Redis
  2. Semantisches Caching mit Embeddings
  3. Prompt-Prefix-Caching von OpenAI
  4. Batching, Model Routing und Kosten-Dashboards
← Zurück zu AI Engineering Academy