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.
422 lines
16 KiB
Markdown
422 lines
16 KiB
Markdown
# Caching Strategies for AI Responses in APIM
|
|
|
|
**Last updated:** 2026-06-24
|
|
**Status:** GA
|
|
**Category:** API Management & AI Gateway
|
|
**Type:** reference
|
|
**Source:** https://learn.microsoft.com/azure/api-management/caching-overview
|
|
|
|
---
|
|
|
|
## Innhold
|
|
|
|
- [Introduksjon](#introduksjon)
|
|
- [Prompt-baserte caching-nokler](#prompt-baserte-caching-nokler)
|
|
- [Semantisk deduplisering](#semantisk-deduplisering)
|
|
- [TTL-konfigurasjon](#ttl-konfigurasjon)
|
|
- [Cache-invalidering](#cache-invalidering)
|
|
- [Kostnadsbesparelsesanalyse](#kostnadsbesparelsesanalyse)
|
|
- [Caching-tjenester: Intern vs. Ekstern](#caching-tjenester-intern-vs-ekstern)
|
|
- [Referanser](#referanser)
|
|
- [For Cosmo](#for-cosmo)
|
|
|
|
## Introduksjon
|
|
|
|
Caching er en av de mest effektive strategiene for a redusere kostnader og forbedre ytelse i AI-applikasjoner. Azure API Management tilbyr bade tradisjonell HTTP-caching og semantisk caching spesielt designet for LLM-API-er. Semantisk caching bruker embedding-vektorer for a identifisere prompts som er semantisk like -- ikke bare identiske -- og returnere cachede svar uten a kalle backend-modellen.
|
|
|
|
For norsk offentlig sektor kan caching-strategier gi vesentlige besparelser. En typisk offentlig virksomhet som bruker Azure OpenAI for chatbot-tjenester, intern dokumentanalyse eller innbyggerveiledning vil ofte motta mange lignende sporsmol. Semantisk caching kan redusere token-forbruket med 20-40% for slike workloads, med tilsvarende kostnadsbesparelse og forbedret responstid.
|
|
|
|
APIM stotter to hovedtyper caching: intern (innebygd) og ekstern (Redis-basert). For semantisk caching av AI-svar er ekstern cache via Azure Managed Redis med RediSearch-modulen pakrevd. Denne referansen dekker bade tradisjonell og semantisk caching, med fokus pa praktisk implementering for AI-workloads.
|
|
|
|
---
|
|
|
|
## Prompt-baserte caching-nokler
|
|
|
|
### Tradisjonell caching med eksakte matcher
|
|
|
|
For identiske prompts kan standard `cache-lookup` / `cache-store` policies brukes:
|
|
|
|
```xml
|
|
<policies>
|
|
<inbound>
|
|
<base />
|
|
<!-- Cache lookup based on exact request body hash -->
|
|
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false">
|
|
<vary-by-header>x-tenant-id</vary-by-header>
|
|
<vary-by-query-parameter>model</vary-by-query-parameter>
|
|
</cache-lookup>
|
|
</inbound>
|
|
<outbound>
|
|
<base />
|
|
<!-- Store response in cache for 5 minutes -->
|
|
<cache-store duration="300" />
|
|
</outbound>
|
|
</policies>
|
|
```
|
|
|
|
### Custom cache-nokler for AI-foresporsler
|
|
|
|
Bygg tilpassede cache-nokler basert pa prompt-innhold:
|
|
|
|
```xml
|
|
<policies>
|
|
<inbound>
|
|
<base />
|
|
<!-- Generate cache key from normalized prompt content -->
|
|
<set-variable name="cacheKey" value="@{
|
|
var body = context.Request.Body.As<JObject>(preserveContent: true);
|
|
var messages = (JArray)body?["messages"];
|
|
if (messages == null) return "";
|
|
|
|
// Build key from role+content pairs, normalized
|
|
var keyParts = new System.Collections.Generic.List<string>();
|
|
foreach (var msg in messages)
|
|
{
|
|
var role = msg["role"]?.ToString() ?? "";
|
|
var content = msg["content"]?.ToString()?.Trim().ToLower() ?? "";
|
|
keyParts.Add($"{role}:{content}");
|
|
}
|
|
|
|
var model = body["model"]?.ToString() ?? "default";
|
|
var combined = model + "|" + string.Join("|", keyParts);
|
|
|
|
// Generate SHA256 hash
|
|
using (var sha = System.Security.Cryptography.SHA256.Create())
|
|
{
|
|
var bytes = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(combined));
|
|
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
|
|
}
|
|
}" />
|
|
|
|
<!-- Lookup in cache -->
|
|
<cache-lookup-value key="@((string)context.Variables["cacheKey"])"
|
|
variable-name="cachedResponse" />
|
|
|
|
<choose>
|
|
<when condition="@(context.Variables.ContainsKey("cachedResponse"))">
|
|
<return-response>
|
|
<set-status code="200" reason="OK" />
|
|
<set-header name="Content-Type" exists-action="override">
|
|
<value>application/json</value>
|
|
</set-header>
|
|
<set-header name="x-cache-hit" exists-action="override">
|
|
<value>true</value>
|
|
</set-header>
|
|
<set-body>@((string)context.Variables["cachedResponse"])</set-body>
|
|
</return-response>
|
|
</when>
|
|
</choose>
|
|
</inbound>
|
|
<outbound>
|
|
<base />
|
|
<!-- Store successful responses in cache -->
|
|
<choose>
|
|
<when condition="@(context.Response.StatusCode == 200)">
|
|
<cache-store-value key="@((string)context.Variables["cacheKey"])"
|
|
value="@(context.Response.Body.As<string>(preserveContent: true))"
|
|
duration="600" />
|
|
<set-header name="x-cache-hit" exists-action="override">
|
|
<value>false</value>
|
|
</set-header>
|
|
</when>
|
|
</choose>
|
|
</outbound>
|
|
</policies>
|
|
```
|
|
|
|
---
|
|
|
|
## Semantisk deduplisering
|
|
|
|
### Oversikt over semantisk caching i APIM
|
|
|
|
Semantisk caching bruker embeddings for a matche prompts basert pa meningsbetydning, ikke bare eksakt tekst. To prompts som "Hva er Microsoft Azure?" og "Kan du forklare hva Azure er?" vil ga i cache-treff selv om ordlyden er ulik.
|
|
|
|
### Arkitektur
|
|
|
|
```
|
|
Klient --> APIM --> [Semantic Cache Lookup] --> Azure Managed Redis (RediSearch)
|
|
| |
|
|
| (cache miss) | (cache hit)
|
|
v v
|
|
[Embeddings API] [Returner cached svar]
|
|
|
|
|
v
|
|
[AI Backend (Chat)]
|
|
|
|
|
v
|
|
[Semantic Cache Store] --> Azure Managed Redis
|
|
```
|
|
|
|
### Forutsetninger
|
|
|
|
| Komponent | Krav |
|
|
|-----------|------|
|
|
| Azure Managed Redis | RediSearch-modul aktivert (velges ved opprettelse) |
|
|
| Embeddings deployment | text-embedding-ada-002 eller nyere modell |
|
|
| APIM | Alle tiers stotter semantisk caching med ekstern cache |
|
|
| Autentisering | Managed Identity til bade OpenAI og Redis |
|
|
|
|
### Konfigurering av semantisk caching
|
|
|
|
#### 1. Opprett embeddings-backend
|
|
|
|
```xml
|
|
<!-- Backend for embeddings API -->
|
|
<set-backend-service backend-id="embeddings-backend" />
|
|
```
|
|
|
|
I Azure Portal:
|
|
- **Type:** Custom URL
|
|
- **Runtime URL:** `https://{aoai-name}.openai.azure.com/openai/deployments/{embedding-deployment}/embeddings`
|
|
- **Managed Identity:** System-assigned, Resource ID: `https://cognitiveservices.azure.com/`
|
|
|
|
#### 2. Konfigurer semantic cache lookup (inbound)
|
|
|
|
For Azure OpenAI API-er:
|
|
|
|
```xml
|
|
<policies>
|
|
<inbound>
|
|
<base />
|
|
<!-- 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>
|
|
|
|
<!-- Rate limit as fallback if cache is unavailable -->
|
|
<rate-limit calls="20" renewal-period="60" />
|
|
</inbound>
|
|
</policies>
|
|
```
|
|
|
|
For andre LLM-API-er (ikke Azure OpenAI):
|
|
|
|
```xml
|
|
<policies>
|
|
<inbound>
|
|
<base />
|
|
<llm-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>
|
|
</llm-semantic-cache-lookup>
|
|
</inbound>
|
|
</policies>
|
|
```
|
|
|
|
#### 3. Konfigurer semantic cache store (outbound)
|
|
|
|
```xml
|
|
<policies>
|
|
<outbound>
|
|
<base />
|
|
<!-- Store response for 60 seconds -->
|
|
<azure-openai-semantic-cache-store duration="60" />
|
|
</outbound>
|
|
</policies>
|
|
```
|
|
|
|
---
|
|
|
|
## TTL-konfigurasjon
|
|
|
|
### Strategier for Time-to-Live
|
|
|
|
Riktig TTL-konfigurasjon balanserer mellom kostnadsbesparelse og datakvalitet:
|
|
|
|
| Bruksscenario | Anbefalt TTL | Begrunnelse |
|
|
|--------------|-------------|-------------|
|
|
| FAQ/statisk veiledning | 3600s (1 time) | Innholdet endres sjelden |
|
|
| Generell chatbot | 300s (5 min) | Balanse mellom friskhet og kostnad |
|
|
| Dokumentanalyse | 600s (10 min) | Dokumenter endres sjelden innen sesjon |
|
|
| Sanntidsdata-sporring | 30-60s | Data kan endres raskt |
|
|
| Kodegenerering | 120s (2 min) | Brukere itererer raskt |
|
|
| Intern kunnskapssok | 1800s (30 min) | Intern kunnskap er relativt stabil |
|
|
|
|
### Dynamisk TTL basert pa kontekst
|
|
|
|
```xml
|
|
<policies>
|
|
<outbound>
|
|
<base />
|
|
<!-- Dynamic TTL based on request type -->
|
|
<set-variable name="cacheDuration" value="@{
|
|
var body = context.Request.Body.As<JObject>(preserveContent: true);
|
|
var messages = (JArray)body?["messages"];
|
|
var lastMessage = messages?.Last?["content"]?.ToString() ?? "";
|
|
|
|
// Longer TTL for FAQ-like questions
|
|
if (lastMessage.Contains("hva er") || lastMessage.Contains("forklar"))
|
|
return 3600;
|
|
|
|
// Shorter TTL for data queries
|
|
if (lastMessage.Contains("status") || lastMessage.Contains("siste"))
|
|
return 60;
|
|
|
|
// Default TTL
|
|
return 300;
|
|
}" />
|
|
|
|
<azure-openai-semantic-cache-store
|
|
duration="@((int)context.Variables["cacheDuration"])" />
|
|
</outbound>
|
|
</policies>
|
|
```
|
|
|
|
---
|
|
|
|
## Cache-invalidering
|
|
|
|
### Manuell invalidering med cache-remove-value
|
|
|
|
```xml
|
|
<!-- Remove specific cached value -->
|
|
<cache-remove-value key="specific-cache-key" />
|
|
```
|
|
|
|
### Automatisk invalidering ved modellbytte
|
|
|
|
```xml
|
|
<policies>
|
|
<inbound>
|
|
<base />
|
|
<!-- Include model version in cache key to auto-invalidate on model change -->
|
|
<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 model deployment to invalidate cache on model update -->
|
|
<vary-by>@(context.Subscription.Id)</vary-by>
|
|
<vary-by>@(context.Request.Headers.GetValueOrDefault("x-model-version", "default"))</vary-by>
|
|
</azure-openai-semantic-cache-lookup>
|
|
</inbound>
|
|
</policies>
|
|
```
|
|
|
|
### Cache-invalidering via API-kall
|
|
|
|
Opprett en dedikert operasjon for administratorer:
|
|
|
|
```xml
|
|
<!-- Cache purge operation -->
|
|
<policies>
|
|
<inbound>
|
|
<base />
|
|
<!-- Verify admin access -->
|
|
<validate-jwt header-name="Authorization"
|
|
failed-validation-httpcode="401"
|
|
failed-validation-error-message="Unauthorized">
|
|
<required-claims>
|
|
<claim name="roles" match="any">
|
|
<value>CacheAdmin</value>
|
|
</claim>
|
|
</required-claims>
|
|
</validate-jwt>
|
|
|
|
<!-- Purge cache - requires external cache API call -->
|
|
<send-request mode="new" response-variable-name="purgeResult" timeout="10">
|
|
<set-url>@($"https://{cacheHost}:10000/FLUSHDB")</set-url>
|
|
<set-method>POST</set-method>
|
|
</send-request>
|
|
|
|
<return-response>
|
|
<set-status code="200" reason="Cache Purged" />
|
|
<set-body>{"status":"cache_purged","timestamp":"@(DateTime.UtcNow.ToString("o"))"}</set-body>
|
|
</return-response>
|
|
</inbound>
|
|
</policies>
|
|
```
|
|
|
|
---
|
|
|
|
## Kostnadsbesparelsesanalyse
|
|
|
|
### Beregningsmodell
|
|
|
|
| Parameter | Verdi |
|
|
|-----------|-------|
|
|
| Gjennomsnittlig tokens per request | 2 000 (prompt) + 500 (completion) |
|
|
| GPT-4o pris per 1M input tokens | $2.50 |
|
|
| GPT-4o pris per 1M output tokens | $10.00 |
|
|
| Antall requests per dag | 10 000 |
|
|
| Gjennomsnittlig cache hit rate | 30% |
|
|
|
|
### Kostnadsberegning
|
|
|
|
| Scenario | Daglig kostnad (NOK) | Manedlig kostnad (NOK) |
|
|
|----------|---------------------|----------------------|
|
|
| Uten caching | ~750 | ~22 500 |
|
|
| Med 30% cache hit | ~525 | ~15 750 |
|
|
| Med 50% cache hit | ~375 | ~11 250 |
|
|
| Med 70% cache hit | ~225 | ~6 750 |
|
|
|
|
### Tilleggskostnader for caching-infrastruktur
|
|
|
|
| Komponent | Manedlig kostnad (NOK) |
|
|
|-----------|----------------------|
|
|
| Azure Managed Redis (Balanced B1) | ~2 500 |
|
|
| Embeddings API-kall (for semantisk caching) | ~150 |
|
|
| **Total caching-overhead** | **~2 650** |
|
|
|
|
### Netto besparelse ved 30% hit rate
|
|
|
|
- Besparelse: 22 500 - 15 750 = **6 750 NOK/mnd**
|
|
- Caching-kostnad: **2 650 NOK/mnd**
|
|
- **Netto besparelse: ~4 100 NOK/mnd** (18% av total)
|
|
|
|
### Score-threshold tuning
|
|
|
|
`score-threshold` i semantisk caching pavirker hit rate og kvalitet:
|
|
|
|
| Threshold | Hit Rate | Kvalitetsrisiko |
|
|
|-----------|----------|----------------|
|
|
| 0.05 | Hoy (50-70%) | Hoy -- kan returnere irrelevante svar |
|
|
| 0.10 | Middels-hoy (30-50%) | Lav-middels |
|
|
| 0.15 (anbefalt) | Middels (20-35%) | Lav |
|
|
| 0.25 | Lav (10-15%) | Svart lav |
|
|
| 0.50 | Svart lav (<5%) | Neglisjerbar |
|
|
|
|
---
|
|
|
|
## Caching-tjenester: Intern vs. Ekstern
|
|
|
|
| Egenskap | Intern cache | Ekstern (Redis) |
|
|
|----------|-------------|----------------|
|
|
| Automatisk provisjonering | Ja | Nei |
|
|
| Tilleggskostnad | Nei | Ja |
|
|
| Semantisk caching | Nei | Ja |
|
|
| Tilgjengelig i alle tiers | Nei (ikke Consumption) | Ja |
|
|
| Persistent lagring | Ja (v2), Nei (classic) | Ja |
|
|
| Delt mellom instanser | Nei | Ja |
|
|
| Data preloading | Nei | Ja |
|
|
|
|
---
|
|
|
|
## Referanser
|
|
|
|
- [Caching overview in Azure API Management](https://learn.microsoft.com/en-us/azure/api-management/caching-overview) -- oversikt over caching-alternativer
|
|
- [Enable semantic caching for LLM APIs](https://learn.microsoft.com/en-us/azure/api-management/azure-openai-enable-semantic-caching) -- trinnvis veiledning
|
|
- [AI gateway capabilities - Semantic caching](https://learn.microsoft.com/en-us/azure/api-management/genai-gateway-capabilities#scalability-and-performance) -- AI gateway-kontekst
|
|
- [llm-semantic-cache-lookup policy](https://learn.microsoft.com/en-us/azure/api-management/llm-semantic-cache-lookup-policy) -- policy-referanse
|
|
- [llm-semantic-cache-store policy](https://learn.microsoft.com/en-us/azure/api-management/llm-semantic-cache-store-policy) -- policy-referanse
|
|
- [Set up an external cache in Azure API Management](https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-cache-external) -- Redis-oppsett
|
|
- [Application design for AI workloads - Caching strategies](https://learn.microsoft.com/en-us/azure/well-architected/ai/application-design#implement-multi-layer-caching-strategies) -- Well-Architected-anbefalinger
|
|
|
|
## For Cosmo
|
|
|
|
- **Bruk denne referansen** nar kunden onsker a redusere AI-kostnader gjennom caching, eller nar de trenger a forbedre responstider for brukere som stiller lignende sporsmol.
|
|
- Start med `score-threshold="0.15"` for semantisk caching -- dette gir god balanse. Juster ned til 0.10 for hoyere hit rate i FAQ-scenarier, eller opp til 0.25 for mer presise matcher i kritiske applikasjoner.
|
|
- Husk at semantisk caching krever Azure Managed Redis med RediSearch-modulen -- denne modulen ma velges ved opprettelse av Redis-instansen og kan ikke legges til i ettertid.
|
|
- For norsk offentlig sektor med hoy grad av repetitive sporsmol (innbyggertjenester, veiledning), er semantisk caching en lavthengende frukt med typisk 20-40% kostnadsreduksjon.
|
|
- Inkluder alltid `<vary-by>@(context.Subscription.Id)</vary-by>` for a forhindre at en leietakers svar returneres til en annen -- dette er kritisk for personvern og dataskille.
|