# Semantic Caching in APIM **Last updated:** 2026-02 **Status:** GA **Category:** API Management & AI Gateway --- ## Introduksjon Semantic caching i Azure API Management er en teknikk som reduserer kostnader og latens for LLM-baserte applikasjoner ved å gjenbruke tidligere genererte completions. I motsetning til tradisjonell nøkkelbasert caching, bruker semantic caching embeddings og vektorlikhet til å identifisere semantisk like prompts -- selv når ordlyden er forskjellig. "Hva er hovedstaden i Norge?" og "Hvilken by er Norges hovedstad?" gir samme cachede svar. For norsk offentlig sektor, der mange brukere stiller lignende spørsmål til interne AI-assistenter og chatbots, kan semantic caching gi betydelige kostnadsbesparelser. Typiske kundeservicescenarier med repeterende spørsmål om åpningstider, tjenester og prosedyrer oppnår cache hit rates på 30-60%, noe som tilsvarer tilsvarende reduksjon i token-forbruk og kostnader. APIM implementerer semantic caching gjennom dedikerte policies som samarbeider med Azure Managed Redis (med RediSearch-modulen) og en Azure OpenAI Embeddings API-deployment. Hele flyten -- fra prompt-inngang til cache-oppslag og lagring -- håndteres av APIM-policies uten egenutviklet kode. --- ## Arkitektur ### Dataflyt ``` 1. Bruker sender prompt til APIM 2. APIM → Embeddings API → Vektor [0.23, -0.45, 0.67, ...] 3. Vektor → Azure Managed Redis (RediSearch) → Similarity search 4. IF similarity score < threshold (lavere = mer lik): RETURN cached completion (cache HIT) ELSE: Forward til Azure OpenAI → Generer completion Store completion + embedding i Redis (cache STORE) RETURN completion til bruker ``` ### Komponentoversikt ``` ┌─────────────┐ │ Client │ └──────┬──────┘ │ ┌──────▼──────┐ │ APIM │ │ (AI Gateway)│ └──┬───┬──┬──┘ │ │ │ ┌────────┘ │ └────────┐ ▼ ▼ ▼ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ Embeddings │ │ Redis │ │ Azure │ │ API │ │(RediSearch│ │ OpenAI │ │ (vektor) │ │ cache) │ │(LLM) │ └────────────┘ └──────────┘ └──────────┘ ``` | Komponent | Rolle | Azure-tjeneste | |-----------|-------|----------------| | **APIM** | Orkestrerer cache-logikk via policies | Azure API Management (Standard v2+) | | **Embeddings API** | Konverterer prompts til vektorer | Azure OpenAI text-embedding-3-large | | **Vector Cache** | Lagrer embeddings + completions, utfører similarity search | Azure Managed Redis med RediSearch | | **LLM Backend** | Genererer nye completions ved cache miss | Azure OpenAI GPT-4o / GPT-4o-mini | --- ## Forutsetninger ### 1. Azure Managed Redis med RediSearch ```bicep resource redis 'Microsoft.Cache/redisEnterprise@2024-09-01-preview' = { name: 'redis-semantic-cache-${environment}' location: location sku: { name: 'Enterprise_E10' capacity: 2 } properties: {} } resource database 'Microsoft.Cache/redisEnterprise/databases@2024-09-01-preview' = { parent: redis name: 'default' properties: { clientProtocol: 'Encrypted' evictionPolicy: 'VolatileLRU' modules: [ { name: 'RediSearch' } ] } } ``` > **Viktig:** RediSearch-modulen kan KUN aktiveres ved opprettelse av Redis-instansen. Du kan ikke legge den til i etterkant. Planlegg for dette fra starten. ### 2. Embeddings API Deployment ```bicep resource embeddingsDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = { parent: openaiAccount name: 'text-embedding-3-large' sku: { name: 'Standard' capacity: 120 // 120K TPM for embeddings } properties: { model: { format: 'OpenAI' name: 'text-embedding-3-large' version: '1' } } } ``` **Valg av embeddings-modell:** | Modell | Dimensjoner | Pris (per 1M tokens) | Anbefaling | |--------|-------------|---------------------|------------| | text-embedding-3-large | 3072 | ~$0.13 | Høyest kvalitet, anbefalt | | text-embedding-3-small | 1536 | ~$0.02 | Kostnadseffektiv, god nok for de fleste | | text-embedding-ada-002 | 1536 | ~$0.10 | Legacy, ikke anbefalt for nye prosjekter | ### 3. APIM External Cache-konfigurasjon Koble Redis som ekstern cache i APIM: ```bicep resource externalCache 'Microsoft.ApiManagement/service/caches@2023-09-01-preview' = { parent: apim name: 'redis-semantic' properties: { connectionString: '${redis.properties.hostName}:10000,password=${listKeys(redis.id, redis.apiVersion).keys[0].value},ssl=True,abortConnect=False' useFromLocation: 'default' description: 'Azure Managed Redis for semantic caching' resourceId: redis.id } } ``` ### 4. Embeddings Backend i APIM ```bicep resource embeddingsBackend 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = { parent: apim name: 'embeddings-backend' properties: { url: 'https://aoai-norwayeast.openai.azure.com/openai/deployments/text-embedding-3-large/embeddings' protocol: 'http' } } ``` --- ## Cache-lookup og Cache-store Policies ### Azure OpenAI-spesifikke policies For Azure OpenAI APIs, bruk de spesialbestemte policies: **Inbound (cache lookup):** ```xml @(context.Subscription.Id) ``` **Outbound (cache store):** ```xml ``` ### Generelle LLM-policies For tredjeparts LLM-er eller OpenAI-kompatible endepunkter: **Inbound:** ```xml @(context.Subscription.Id) ``` **Outbound:** ```xml ``` ### Policy-attributter | Attributt | Type | Beskrivelse | Anbefalt verdi | |-----------|------|-------------|----------------| | `score-threshold` | float | Maks avstand for cache hit (lavere = strengere) | 0.10-0.20 | | `embeddings-backend-id` | string | Backend-ID for Embeddings API | `embeddings-backend` | | `embeddings-backend-auth` | string | Autentiseringsmetode | `system-assigned` | | `ignore-system-messages` | bool | Ignorer system message i cache-nøkkel | `true` (oftest) | | `max-message-count` | int | Maks antall meldinger i konversasjonshistorikk å cache | 10 | | `duration` | int | Cache TTL i sekunder | 3600 (1 time) | ### vary-by-element `` sikrer at cache er isolert per konsument/kontekst: ```xml @(context.Subscription.Id) @(context.Subscription.Id + "-" + context.Request.MatchedParameters["deployment-id"]) @(context.Request.Headers.GetValueOrDefault("x-etat-id", "shared")) ``` --- ## Embedding-Based Similarity ### Hvordan score-threshold fungerer APIM bruker cosine distance (ikke cosine similarity) for å sammenligne embeddings: ``` Cosine Distance = 1 - Cosine Similarity Distance 0.0 = Identiske prompts (perfekt match) Distance 0.15 = Svært like prompts Distance 0.30 = Noe like prompts Distance 1.0 = Helt ulike prompts ``` **Threshold-valg:** | score-threshold | Matchstrenghet | Cache hit rate | Presisjon | Anbefalt for | |-----------------|---------------|----------------|-----------|--------------| | 0.05 | Ekstremt streng | Lav (5-15%) | Svært høy | Faktabaserte spørsmål | | 0.10 | Streng | Moderat (15-30%) | Høy | Standard anbefaling | | 0.15 | Balansert | God (25-45%) | God | De fleste use cases | | 0.20 | Liberal | Høy (35-60%) | Moderat | FAQ/kundeservice | | 0.30 | Aggressiv | Svært høy (50-70%) | Lavere | Generelle spørsmål | **Kalibreringsprosess:** 1. Start med `score-threshold="0.15"` (balansert) 2. Kjør produksjonstrafikk i 1-2 uker 3. Analyser cache hit rate og brukertilfredshet 4. Juster ned (strengere) hvis brukere rapporterer irrelevante svar 5. Juster opp (mer liberal) hvis cache hit rate er under 20% ### Eksempler på semantisk matching | Prompt A | Prompt B | Typisk distance | Match ved 0.15? | |----------|----------|-----------------|------------------| | "Hva er hovedstaden i Norge?" | "Hvilken by er Norges hovedstad?" | ~0.05 | Ja | | "Forklar maskinlæring" | "Hva er machine learning?" | ~0.10 | Ja | | "Hvordan søker jeg om byggetillatelse?" | "Prosessen for å få byggetillatelse" | ~0.12 | Ja | | "Hva er veibygging?" | "Hvordan bygger man en bro?" | ~0.25 | Nei | | "Fortell om AI" | "Hva er kvantemekanikk?" | ~0.45 | Nei | --- ## Cache Invalidation Strategies ### TTL-basert invalidation (standard) ```xml ``` **Anbefalte TTL-verdier:** | Innholdstype | TTL | Begrunnelse | |-------------|-----|-------------| | Statisk fakta (hovedsteder, lover) | 86400 (24t) | Endres sjelden | | Generell kunnskap | 3600 (1t) | God balanse | | Dynamisk innhold (priser, status) | 300 (5min) | Endres ofte | | Real-time data | 0 (ingen cache) | Må alltid være oppdatert | ### Manuell cache-invalidation APIM har ingen innebygd policy for selektiv cache-invalidation av semantic cache. Alternativa tilnærminger: **1. Redis CLI flush:** ```bash # Flush all cached entries (krever Redis-tilgang) redis-cli -h redis-cache.norwayeast.redis.cache.windows.net -p 10000 --tls FLUSHDB ``` **2. TTL-basert rotasjon:** Bruk kort TTL og la entries utløpe naturlig. **3. vary-by med versjonsnøkkel:** ```xml @("v2-" + context.Subscription.Id) ``` Endre "v2" til "v3" i policy for å effektivt invalidere all cache (nye nøkler gir cache miss). --- ## Cost Savings Analysis ### Beregningsmodell ``` Kostnad UTEN caching: Totale requests × gjennomsnittlig tokens per request × pris per token Kostnad MED caching: (Cache misses × tokens per request × pris per token) + (Alle requests × embedding tokens × embedding pris) + Redis-kostnad Besparelse = Kostnad UTEN - Kostnad MED ``` ### Eksempelberegning for norsk offentlig sektor **Scenario:** Intern AI-assistent for 500 ansatte, 10 000 requests/dag. | Parameter | Verdi | |-----------|-------| | Requests per dag | 10 000 | | Gjennomsnittlig prompt tokens | 200 | | Gjennomsnittlig completion tokens | 500 | | GPT-4o pris (input) | $2.50 / 1M tokens | | GPT-4o pris (output) | $10.00 / 1M tokens | | Embedding pris | $0.13 / 1M tokens | | Cache hit rate | 40% | **Beregning:** | Kostnadspost | Uten caching | Med caching (40% hit rate) | |-------------|-------------|---------------------------| | LLM input tokens | 10K × 200 = 2M → $5.00/dag | 6K × 200 = 1.2M → $3.00/dag | | LLM output tokens | 10K × 500 = 5M → $50.00/dag | 6K × 500 = 3M → $30.00/dag | | Embedding tokens | $0/dag | 10K × 200 = 2M → $0.26/dag | | Redis (E10) | $0/dag | ~$6.00/dag ($182/mnd) | | **Total per dag** | **$55.00** | **$39.26** | | **Total per måned** | **$1 650** | **$1 178** | | **Besparelse** | - | **$472/mnd (29%)** | ### ROI-beregning | Cache hit rate | Månedlig besparelse (LLM) | Redis-kostnad | Netto besparelse | ROI | |----------------|--------------------------|---------------|-----------------|-----| | 20% | $330 | $182 | $148 | Positiv | | 40% | $660 | $182 | $478 | Sterk | | 60% | $990 | $182 | $808 | Svært sterk | | 80% | $1 320 | $182 | $1 138 | Eksepsjonell | > **Break-even punkt:** Semantic caching er kostnadseffektivt ved cache hit rates over ~15% for typiske workloads. --- ## Privacy Considerations ### Datalagrings-hensyn | Hensyn | Risiko | Mitigering | |--------|--------|------------| | **PII i cache** | Persondata caches i Redis | Bruk `vary-by` per bruker, kort TTL, eller ekskluder PII-requests | | **Cross-tenant data** | En brukers svar vises for annen bruker | `vary-by` per subscription/bruker isolerer cache | | **Cache i feil region** | Data lagres utenfor tillatt geografi | Deploy Redis i samme region som APIM og OpenAI | | **Langvarig lagring** | Sensitive svar lagret for lenge | Sett passende TTL, minimum mulig | | **Logging av prompts** | Prompts logges via APIM diagnostics | Konfigurer masking i diagnostic settings | ### Anbefalinger for offentlig sektor 1. **Isoler cache per etat/avdeling** med `vary-by` element 2. **Sett TTL til maksimalt 1 time** for generelle spørsmål, kortere for sensitive 3. **Ekskluder sensitive APIer** fra semantic caching (fjern policies for spesifikke operasjoner) 4. **Deploy Redis i Norway East** eller Sweden Central for datasuverenitet 5. **Aktiver TLS** (`ssl=True`) for all Redis-kommunikasjon 6. **Bruk private endpoints** for Redis og APIM 7. **Vurder DPIA** (Data Protection Impact Assessment) for cache av brukerdata ### Ekskludering av sensitive requests ```xml @(context.Subscription.Id) ``` --- ## Rate Limiting etter Cache Lookup ### Beskyttelse mot cache-utilgjengelighet Legg alltid til en rate limit ETTER cache lookup for å beskytte backend hvis Redis er nede: ```xml @(context.Subscription.Id) ``` --- ## Verifisering og feilsøking ### Bekrefte at caching fungerer Bruk APIM Test Console med tracing aktivert: 1. Send en request via Test Console med tracing 2. Inspiser trace-output: - **Cache HIT:** `azure-openai-semantic-cache-lookup` viser "Cache lookup resulted in a hit" - **Cache MISS:** Viser "Cache lookup resulted in a miss" + backend-kall ### KQL for cache-metrikk ```kusto // Cache hit rate over tid ApiManagementGatewayLogs | where OperationId contains "chat" | extend cacheHit = ResponseHeaders contains "x-cache: HIT" | summarize TotalRequests = count(), CacheHits = countif(cacheHit), CacheMisses = countif(not(cacheHit)), HitRate = round(100.0 * countif(cacheHit) / count(), 2) by bin(TimeGenerated, 1h) | render timechart ``` ```kusto // Kostnadsbesparelse estimat ApiManagementGatewayLogs | where OperationId contains "chat" | extend cacheHit = ResponseHeaders contains "x-cache: HIT" | extend estimatedTokensSaved = iff(cacheHit, 700, 0) // avg tokens per request | summarize TokensSaved = sum(estimatedTokensSaved), EstimatedCostSavedUSD = round(sum(estimatedTokensSaved) * 0.000010, 2) by bin(TimeGenerated, 1d) ``` --- ## Komplett policy for semantic caching ```xml @(context.Subscription.Id) ``` --- ## Tier-kompatibilitet | Policy | Classic | V2 | Consumption | Self-hosted | Workspace | |--------|---------|----|-----------|----------- |-----------| | `azure-openai-semantic-cache-lookup` | Ja | Ja | Ja | Nei | Nei | | `azure-openai-semantic-cache-store` | Ja | Ja | Ja | Nei | Nei | | `llm-semantic-cache-lookup` | Ja | Ja | Ja | Nei | Nei | | `llm-semantic-cache-store` | Ja | Ja | Ja | Nei | Nei | > **Merk:** Semantic caching krever ekstern cache (Azure Managed Redis) og er IKKE tilgjengelig i self-hosted gateway eller workspace gateway. --- ## For Cosmo - Semantic caching er den mest kostnadseffektive optimaliseringen for AI-workloads med repeterende spørsmål. Start med `score-threshold="0.15"` og juster basert på cache hit rate og brukerfeedback. For FAQ/kundeservice-scenarier, vurder 0.20 for høyere hit rate. - Krav: Azure Managed Redis med RediSearch-modul (MÅ aktiveres ved opprettelse, kan ikke legges til etterpå) + Azure OpenAI Embeddings deployment. Planlegg disse ressursene fra starten. - Bruk `vary-by` per subscription/bruker for å isolere cache og forhindre data-lekkasje mellom konsumenter. For offentlig sektor er dette en forutsetning for compliance. - Legg alltid til en `rate-limit` policy ETTER cache lookup som beskyttelse mot situasjoner der Redis er utilgjengelig -- uten dette vil alle requests gå direkte til backend uten throttling. - Kostnadsbesparelse ved 40% cache hit rate er typisk 25-35% for standard AI-assistenter. Break-even punkt er ca. 15% hit rate (under dette er Redis-kostnaden høyere enn besparelsen).