ms-ai-architect/skills/ms-ai-engineering/references/api-management/semantic-caching-apim.md
Kjell Tore Guttormsen ddce43d8b2 feat(ms-ai-architect): Spor 1 — Port-1-substrat migrert på 4 ikke-advisor-skills (243 Source + 327 Type + 325 TOC + stale-verified poison fjernet) [skip-docs]
Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/
infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk
(fra første ## seksjon), advisor urørt (0 endringer).

To applier-fixes oppdaget under kjøring (TDD, RED→GREEN):
- insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer
  500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor
  scan-vinduet, applierens post-write-assertion fanget + restaurerte).
- normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i
  500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var
  ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent
  utvidelse av carve-out; kun stray metadata-linjer, aldri prosa.

test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet
(fila bærer nå Source). Suite 728/728 grønn.
2026-07-04 10:19:11 +02:00

598 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Semantic Caching in APIM
**Last updated:** 2026-02
**Status:** GA
**Category:** API Management & AI Gateway
**Type:** reference
---
## Innhold
- [Introduksjon](#introduksjon)
- [Arkitektur](#arkitektur)
- [Forutsetninger](#forutsetninger)
- [Cache-lookup og Cache-store Policies](#cache-lookup-og-cache-store-policies)
- [Embedding-Based Similarity](#embedding-based-similarity)
- [Cache Invalidation Strategies](#cache-invalidation-strategies)
- [Cost Savings Analysis](#cost-savings-analysis)
- [Privacy Considerations](#privacy-considerations)
- [Rate Limiting etter Cache Lookup](#rate-limiting-etter-cache-lookup)
- [Verifisering og feilsøking](#verifisering-og-feilsøking)
- [Komplett policy for semantic caching](#komplett-policy-for-semantic-caching)
- [Tier-kompatibilitet](#tier-kompatibilitet)
- [For Cosmo](#for-cosmo)
## 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
<azure-openai-semantic-cache-lookup
score-threshold="0.15"
embeddings-backend-id="embeddings-backend"
embeddings-backend-auth="system-assigned"
ignore-system-messages="true"
max-message-count="10">
<vary-by>@(context.Subscription.Id)</vary-by>
</azure-openai-semantic-cache-lookup>
```
**Outbound (cache store):**
```xml
<azure-openai-semantic-cache-store duration="3600" />
```
### Generelle LLM-policies
For tredjeparts LLM-er eller OpenAI-kompatible endepunkter:
**Inbound:**
```xml
<llm-semantic-cache-lookup
score-threshold="0.15"
embeddings-backend-id="embeddings-backend"
embeddings-backend-auth="system-assigned"
max-message-count="10">
<vary-by>@(context.Subscription.Id)</vary-by>
</llm-semantic-cache-lookup>
```
**Outbound:**
```xml
<llm-semantic-cache-store duration="3600" />
```
### 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
`<vary-by>` sikrer at cache er isolert per konsument/kontekst:
```xml
<!-- Isoler cache per subscription -->
<vary-by>@(context.Subscription.Id)</vary-by>
<!-- Isoler per subscription OG modell -->
<vary-by>@(context.Subscription.Id + "-" + context.Request.MatchedParameters["deployment-id"])</vary-by>
<!-- Isoler per etat -->
<vary-by>@(context.Request.Headers.GetValueOrDefault("x-etat-id", "shared"))</vary-by>
```
---
## 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
<!-- Cache entries utløper etter 1 time -->
<azure-openai-semantic-cache-store duration="3600" />
```
**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
<vary-by>@("v2-" + context.Subscription.Id)</vary-by>
```
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
<policies>
<inbound>
<!-- Skip cache for requests med PII-flag -->
<choose>
<when condition="@(context.Request.Headers.GetValueOrDefault("x-contains-pii", "false") == "true")">
<!-- Ingen cache lookup, gå direkte til backend -->
</when>
<otherwise>
<azure-openai-semantic-cache-lookup
score-threshold="0.15"
embeddings-backend-id="embeddings-backend"
embeddings-backend-auth="system-assigned">
<vary-by>@(context.Subscription.Id)</vary-by>
</azure-openai-semantic-cache-lookup>
</otherwise>
</choose>
</inbound>
<outbound>
<choose>
<when condition="@(context.Request.Headers.GetValueOrDefault("x-contains-pii", "false") != "true")">
<azure-openai-semantic-cache-store duration="3600" />
</when>
</choose>
</outbound>
</policies>
```
---
## 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
<policies>
<inbound>
<!-- 1. Semantic cache lookup -->
<azure-openai-semantic-cache-lookup
score-threshold="0.15"
embeddings-backend-id="embeddings-backend"
embeddings-backend-auth="system-assigned">
<vary-by>@(context.Subscription.Id)</vary-by>
</azure-openai-semantic-cache-lookup>
<!-- 2. Rate limit for cache misses (beskytter backend) -->
<rate-limit-by-key
calls="100"
renewal-period="60"
counter-key="@(context.Subscription.Id)" />
<!-- 3. Token limit -->
<llm-token-limit
counter-key="@(context.Subscription.Id)"
tokens-per-minute="50000"
estimate-prompt-tokens="true" />
</inbound>
</policies>
```
---
## 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
<policies>
<inbound>
<base />
<!-- Autentisering -->
<authentication-managed-identity
resource="https://cognitiveservices.azure.com/" />
<!-- Token rate limit -->
<llm-token-limit
counter-key="@(context.Subscription.Id)"
tokens-per-minute="50000"
estimate-prompt-tokens="true"
remaining-tokens-variable-name="remainingTokens" />
<!-- Semantic cache lookup -->
<azure-openai-semantic-cache-lookup
score-threshold="0.15"
embeddings-backend-id="embeddings-backend"
embeddings-backend-auth="system-assigned"
ignore-system-messages="true"
max-message-count="10">
<vary-by>@(context.Subscription.Id)</vary-by>
</azure-openai-semantic-cache-lookup>
<!-- Fallback rate limit hvis cache er nede -->
<rate-limit-by-key
calls="100"
renewal-period="60"
counter-key="@(context.Subscription.Id)" />
<!-- Backend pool -->
<set-backend-service backend-id="openai-pool" />
</inbound>
<outbound>
<base />
<!-- Cache store -->
<azure-openai-semantic-cache-store duration="3600" />
<!-- Token metrikk -->
<llm-emit-token-metric namespace="ai-gateway">
<dimension name="Subscription"
value="@(context.Subscription.Id)" />
<dimension name="CacheHit"
value="@(context.Response.Headers.GetValueOrDefault("x-cache", "MISS"))" />
</llm-emit-token-metric>
</outbound>
<on-error>
<base />
</on-error>
</policies>
```
---
## 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).