Einstellungen

Sprache

Warum Ihr Semantic Cache falsche Antworten liefert

L
LemonData
·5. März 2026·585 Aufrufe
Warum Ihr Semantic Cache falsche Antworten liefert

Ein Benutzer berichtete, dass unser Übersetzungs-Plugin für jede Anfrage das gleiche gecachte Ergebnis zurückgab, unabhängig von der Eingabe. Wir untersuchten den Fall und fanden etwas Schlimmeres: 95 % aller semantischen Cache-Treffer auf unserer gesamten Plattform waren False Positives. 199 verschiedene Übersetzungsanfragen, 198 eindeutige Request-Bodies, eine einzige gecachte Antwort für alle.

Wenn Ihnen langlebige Agent-States und die Verarbeitung von Production-Requests wichtig sind, passt dieser Beitrag gut zu Warum Ihr KI-Agent ständig sein Gedächtnis verliert, dem One-Key-Chatbot-Leitfaden und dem KI-API-Rate-Limiting-Leitfaden.

Der Bug-Report

Der Bericht war simpel: "Ich habe den semantischen Cache deaktiviert, aber jede Übersetzung liefert das gleiche Ergebnis."

Drei Request-IDs, drei verschiedene Übersetzungssegmente, identische gecachte Antworten. Die Request-Bodies reichten von 1.564 bis 8.676 Bytes. Die ID der gecachten Antwort war bei allen gleich: chatcmpl-DG6J03nhdvcF7Ek0C8rJkjh7lN9pF.

Erster Verdacht: Die Cache-Einstellungen des Benutzers wurden nicht angewendet. Das stellte sich als separater Bug bei der Datenquellen-Synchronisierung heraus (das Admin-Panel schrieb in eine Tabelle, das API-Gateway las aus einer anderen). Aber die Behebung löste nur die Hälfte des Problems. Selbst mit aktiviertem und korrekt funktionierendem Cache lieferte der semantische Cache Treffer für Anfragen, die niemals übereinstimmen sollten.

Die Produktionsdaten

Wir zogen Cache-Treffer-Daten der letzten 24 Stunden aus ClickHouse. Die Zahlen waren verheerend.

Modell Anfragen insgesamt Cache-Treffer Eindeutige Anfragen Eindeutige Antworten Trefferquote
gpt-4.1-nano 200 199 198 1 99,5%
glm-4.6-thinking 100 38 13 1 38%
gpt-5-nano 31 29 28 2 93,5%
gpt-oss-120b 18 17 17 1 94,4%
qwen3-vl-flash 17 16 16 1 94,1%

198 eindeutige Übersetzungsanfragen, die alle dieselbe einzige gecachte Antwort zurückgeben. Das ist kein Cache. Das ist eine defekte Funktion, die eine Konstante zurückgibt.

Jedes betroffene Modell wies zwei Merkmale auf: Alle Anfragen stammten von einem einzigen Benutzer, und alle verwendeten ein festes System-Prompt-Template mit variierendem Benutzerinhalt.

Warum Embeddings bei strukturierten Eingaben scheitern

Das Übersetzungs-Plugin sendet Anfragen wie diese:

System: "Act as a translation API. Output a single raw JSON object only.
         Input: {"targetLanguage":"<lang>","title":"...","segments":[...]}"

User:   {"targetLanguage":"zh","title":"Product Page",
         "description":"Translate product descriptions",
         "tone":"formal",
         "segments":[{"text":"actual varying content here"}]}

Der System-Prompt ist bei allen Anfragen identisch. Die Benutzernachricht ist ein JSON-Objekt, bei dem targetLanguage, title, description und tone fixiert sind. Nur segments[].text ändert sich.

Wenn unser semantischer Cache Text für das Embedding extrahiert, verkettet er den System-Prompt und die Benutzernachricht. Das feste Template macht etwa 80 % des Textes aus. Das Embedding-Modell (all-mpnet-base-v2, 768 Dimensionen) komprimiert dies in einen Vektor, in dem die Template-Struktur dominiert. Der eigentliche Übersetzungsinhalt fällt kaum ins Gewicht.

Ergebnis: Die Kosinus-Ähnlichkeit zwischen "translate 'Hello world'" und "translate 'The quarterly financial report shows a 15% increase in revenue'" übersteigt 0,95. Unser Schwellenwert liegt bei 0,95. Jede Übersetzungsanfrage matcht den ersten gecachten Eintrag.

Beim Durchforsten der Logs fanden wir drei Arten, wie dies schiefgeht:

Das Übersetzungs-Plugin ist der schlimmste Übeltäter. Feste JSON-Keys und -Values übertönen die tatsächlichen Übersetzungssegmente. Sowohl gpt-4.1-nano als auch gpt-5-nano waren davon betroffen.

Ein Assistent zur Kontext-Zusammenfassung hatte eine andere Variante desselben Problems. Sein System-Prompt war so lang, dass der Benutzerinhalt (zwischen 5 KB und 47 KB) im Embedding kaum auffiel. So kam es, dass glm-4.6-thinking für jede Konversation dieselbe Zusammenfassung zurückgab.

Das dritte Muster war subtiler. Bei gpt-oss-120b und qwen3-vl-flash waren die ersten 500 Zeichen jeder Anfrage Byte für Byte identisch. Der variierende Inhalt kam erst danach, aber das Embedding wurde bereits durch den gemeinsamen Präfix dominiert.

Was die Forschung sagt

Dies ist kein neues Problem. Jüngste Studien haben es quantifiziert.

Das vCache-Projekt der UC Berkeley fand heraus, dass korrekte und inkorrekte Cache-Treffer "stark überlappende Ähnlichkeitsverteilungen" aufweisen. Der optimale Schwellenwert variiert je nach gecachtem Eintrag zwischen 0,71 und 1,0. Ein einziger Wert funktioniert nicht. Ihre Lösung: Einen separaten Schwellenwert pro Cache-Eintrag lernen, was die Fehlerraten um das 6-fache senkte und gleichzeitig die Trefferquoten verdoppelte. (vCache, 2025)

Es wird noch schlimmer, wenn man verschiedene Abfragetypen mischt. Eine Studie zum kategoriebewussten Caching zeigte, dass ein Schwellenwert von 0,80 bei Code-Abfragen 15 % Fehlmatches produziert (sort_ascending vs. sort_descending), während derselbe Schwellenwert gültige Paraphrasen in Konversationsabfragen übersieht. Ein Schwellenwert, zwei Fehlermodi. (Category-Aware Semantic Caching, 2025)

Auch Banken sind davon betroffen. Eine InfoQ-Fallstudie dokumentierte ein RAG-System, bei dem "Kann ich meine Kreditrate diesen Monat aussetzen" mit 88,7 % Ähnlichkeit auf "Was passiert, wenn ich eine Kreditrate verpasse" matchte. Unterschiedliche Absicht, gleiche gecachte Antwort. Sie begannen mit einer False-Positive-Rate von 99 % und benötigten vier Optimierungsrunden, um auf 3,8 % zu kommen. (InfoQ Banking Case Study, 2025)

Das tiefer liegende Problem: Embeddings messen, ob zwei Prompts semantisch ähnlich sind, nicht ob dieselbe Antwort beide beantworten kann. In dieser Lücke entstehen falsche Cache-Treffer. (Efficient Prompt Caching via Embedding Similarity, 2024)

Jede Studie, die wir fanden, ist sich in einem Punkt einig: Embedding-Ähnlichkeit allein reicht nicht aus. Man benötigt eine Verifizierungsschicht.

Die Zwei-Ebenen-Lösung

Wir haben zwei Schutzmechanismen eingebaut. Der erste entfernt Template-Rauschen vor dem Embedding. Der zweite verifiziert Treffer nach dem Matching.

Ebene 2: Inhaltsextraktion für Embeddings

Bevor wir ein Embedding generieren, erkennen wir nun strukturierte Eingaben (JSON) und extrahieren nur den aussagekräftigen, variablen Inhalt.

Die Logik:

  1. Prüfen, ob der Nachrichteninhalt mit { oder [ beginnt.
  2. Falls es als JSON parst, rekursiv alle String-Blattwerte sammeln.
  3. Kurze Werte (20 Zeichen oder weniger) herausfiltern, da es sich typischerweise um Konfigurationsfelder wie "zh", "formal" oder "Product Page" handelt.
  4. Falls der extrahierte Text zu kurz oder leer ist, auf den Originaltext zurückgreifen.
function extractContentForEmbedding(text: string): string {
  const extracted = tryExtractJsonContent(text);
  return extracted && extracted.length > 20 ? extracted : text;
}

Dies gilt sowohl für den System-Prompt als auch für die Benutzernachricht. Für das Übersetzungs-Plugin repräsentiert das Embedding nun "Hello world" anstelle eines 2 KB großen JSON-Blobs. Für den Zusammenfassungs-Assistenten zieht es die eigentliche Konversation aus der Template-Hülle heraus.

Der Schwellenwert von 20 Zeichen wurde empirisch gewählt:

  • "zh" (2 Zeichen): gefiltert. Konfigurationswert.
  • "formal" (6 Zeichen): gefiltert. Konfigurationswert.
  • "Product Page" (12 Zeichen): gefiltert. Template-Feld.
  • "Translate product descriptions" (31 Zeichen): behalten. Sinnvoller Inhalt.
  • "The quarterly financial report..." (40+ Zeichen): behalten. Tatsächlicher Übersetzungsinhalt.

Ebene 3: Fingerabdruck-Verifizierung

Nach einem semantischen Cache-Treffer vergleichen wir einen Hash des extrahierten Textes der aktuellen Anfrage mit dem im Cache-Eintrag gespeicherten Hash. Wenn sie nicht übereinstimmen, wird der Treffer abgelehnt.

// Beim Schreiben in den Cache
entry.metadata.textHash = fnv1aHash(extractedText);

// Beim Lesen aus dem Cache, nach einem Ähnlichkeits-Match
if (entry.metadata.textHash !== undefined) {
  if (entry.metadata.textHash !== fnv1aHash(currentExtractedText)) {
    // False Positive: semantisch ähnlich, aber unterschiedlicher Inhalt
    metrics.recordFingerprintRejection();
    return null;
  }
}

Der Hash verwendet den extrahierten Text (nach Ebene 2), nicht die Rohdaten. Zwei Anfragen mit unterschiedlichen Template-Hüllen, aber identischem tatsächlichem Inhalt, matchen weiterhin. Unterschiedlicher Inhalt führt zu unterschiedlichem Hash und wird abgelehnt.

Alte Cache-Einträge ohne textHash überspringen die Verifizierung (abwärtskompatibel). Sie laufen natürlich über die TTL ab.

Wir verwenden FNV-1a (32-Bit) für den Hash. Schnell, deterministisch und eine Kollisionsrate von ca. 1 zu 4 Milliarden ist völlig ausreichend für die Überprüfung eines einzelnen Cache-Treffers.

Warum nicht einfach den Schwellenwert erhöhen?

Unser Schwellenwert liegt bereits bei 0,95. Ihn weiter zu erhöhen, hilft nicht. Das Problem ist, dass strukturell ähnliche Eingaben Ähnlichkeitswerte über 0,95 erzeugen, egal was der eigentliche Inhalt sagt.

Die Daten von vCache bestätigen dies: Die Ähnlichkeitsverteilungen von korrekten und inkorrekten Treffern überlappen sich so stark, dass kein einzelner Cut-off sie trennen kann. Setzt man den Schwellenwert auf 0,99, killt man legitime Cache-Treffer für Paraphrasen, ohne die False Positives von template-lastigen Anfragen zu eliminieren.

Korrigieren Sie die Eingabe, verifizieren Sie die Ausgabe. Schrauben Sie nicht am Schwellenwert herum.

Ergebnisse

Mit beiden implementierten Ebenen:

Metrik Vorher Nachher
gpt-4.1-nano False Positives 198/199 0
Anteil False Positives an allen Cache-Treffern ~95% <5%
Legitime Cache-Trefferquote Unverändert Unverändert
Zusätzliche Latenz pro Anfrage 0 <1ms (JSON-Parse + FNV-Hash)

Ebene 2 allein hätte das Übersetzungs-Plugin repariert. Ebene 3 ist das Sicherheitsnetz für Fälle, in denen die JSON-Extraktion den Inhalt nicht vollständig trennt oder für strukturierte Eingaben, die kein JSON sind.

Fazit

Wenn Sie einen semantischen Cache in der Produktion betreiben:

  1. Überwachen Sie die Antwortdiversität. Wenn ein Modell eine Cache-Trefferquote von 100 % und nur eine einzige eindeutige Antwort hat, haben Sie ein False-Positive-Problem. Abfrage: SELECT model, uniqExact(substring(response_body, 1, 200)) as unique_responses, count() as total FROM request_logs WHERE cache_hit = true GROUP BY model.

  2. Strukturierte Eingaben töten naives Embedding. Jede Anfrage mit einem festen Template (JSON-APIs, System-Prompt-Wrapper, Formular-Aufgaben) wird künstlich hohe Ähnlichkeitswerte erzeugen. Führen Sie ein Preprocessing vor dem Embedding durch.

  3. Eine Verifizierungsschicht ist nicht optional. Jeder semantische Cache in der Fachliteratur, der produktiv eingesetzt wird, verfügt über eine. Die Frage ist nur, ob Sie einen leichtgewichtigen Hash-Check, einen Cross-Encoder-Reranker oder einen vollständigen LLM-Verifizierungsaufruf verwenden. Wählen Sie basierend auf Ihrem Latenzbudget.

  4. Globale Schwellenwerte sind ein Kompromiss, keine Lösung. Verschiedene Abfragetypen benötigen unterschiedliche Schwellenwerte. Wenn Sie keine Schwellenwerte pro Kategorie oder Eintrag festlegen können, fügen Sie zumindest ein Input-Preprocessing hinzu, um die Embedding-Qualität über Kategorien hinweg zu normalisieren.

Semantisches Caching kann 30–70 % der LLM-API-Kosten einsparen. Aber ohne Input-Preprocessing und Treffer-Verifizierung liefern Sie veraltete Antworten und verkaufen es als Performance-Gewinn.


LemonData bietet vereinheitlichten Zugriff auf über 300 KI-Modelle mit integriertem Caching, Routing und Kostenoptimierung. Kostenlos testen mit 1 $ Startguthaben.

Share: