# Circuit Breaker Patterns for AI Models

**Last updated:** 2026-02
**Status:** GA
**Category:** API Management & AI Gateway

---

## Introduksjon

Circuit breaker-mønsteret er en grunnleggende resiliensmekanisme for AI-applikasjoner som kommuniserer med Azure OpenAI og andre LLM-backends. Når en backend-tjeneste blir overbelastet eller utilgjengelig, forhindrer circuit breaker at applikasjonen fortsetter å sende forespørsler som uansett vil feile. I stedet "bryter kretsen" og returnerer en feilmelding umiddelbart, slik at backend-tjenesten får tid til å gjenopprette seg.

For Azure AI-tjenester er circuit breaker spesielt viktig fordi Azure OpenAI returnerer 429 (Too Many Requests) med en `Retry-After`-header som kan ha verdier opp til 24 timer. Uten circuit breaker vil applikasjoner fortsette å hamre på en throttlet tjeneste, noe som forverrer situasjonen og kaster bort gateway-ressurser. APIMsintegrerte circuit breaker håndterer dette automatisk ved å respektere `Retry-After`-headeren.

I kombinasjon med backend pools og lastbalansering utgjør circuit breaker selve nervesystemet i en intelligent AI-gateway: den detekterer problemer, isolerer feilende backends, ruter trafikk til friske alternativer, og gjenoppretter normal drift automatisk når backend er tilbake.

---

## Circuit Breaker State Machine

### Tre tilstander

Circuit breaker opererer som en tilstandsmaskin med tre tilstander:

```
                 Feil > threshold
    ┌─────────┐ ───────────────► ┌──────────┐
    │ CLOSED  │                  │  OPEN    │
    │ (normal)│ ◄──────────────  │  (trip)  │
    └─────────┘  Alle OK i       └──────────┘
         │       half-open           │
         │                           │ Trip duration
         │                           │ utløpt
         │       ┌──────────────┐    │
         │       │ HALF-OPEN    │◄───┘
         │       │ (test)       │
         │       └──────────────┘
         │              │
         │    Suksess   │ Feil
         ◄──────────────┘───────────► OPEN
```

| Tilstand | Oppførsel | Varighet |
|----------|-----------|----------|
| **Closed** | Normal drift, alle requests videresendes til backend | Ubegrenset (til feilbetingelse oppstår) |
| **Open** | Alle requests avvises umiddelbart med 503 | Trip duration (konfigurerbar, eller fra Retry-After) |
| **Half-Open** | Et begrenset antall test-requests sendes til backend | Til nok suksesser ELLER ny feil |

### APIM-spesifikk oppførsel

APIMcircuit breaker har noen viktige forskjeller fra den generelle circuit breaker-mønsteret:

| Egenskap | APIM Circuit Breaker |
|----------|---------------------|
| **Konfigurasjons-scope** | Per backend (ikke per API eller policy) |
| **Antall regler** | Kun én regel per backend (p.t.) |
| **Synkronisering** | Ingen mellom gateway-instanser (approksimasjon) |
| **Retry-After respekt** | Ja, dynamisk trip duration basert på backend-respons |
| **Støttede tiers** | Alle unntatt Consumption |
| **Respons ved Open** | 503 Service Unavailable |

---

## Konfigurasjon

### Grunnleggende circuit breaker for Azure OpenAI

```bicep
resource openaiBackend 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = {
  parent: apim
  name: 'openai-norwayeast'
  properties: {
    url: 'https://aoai-norwayeast.openai.azure.com/openai'
    protocol: 'http'
    circuitBreaker: {
      rules: [
        {
          name: 'ai-resilience-rule'
          failureCondition: {
            count: 3
            interval: 'PT1M'
            statusCodeRanges: [
              { min: 429, max: 429 }
              { min: 500, max: 599 }
            ]
          }
          tripDuration: 'PT10S'
          acceptRetryAfter: true
        }
      ]
    }
  }
}
```

### Attributter forklart

| Attributt | Type | Beskrivelse | Anbefalt verdi for AI |
|-----------|------|-------------|----------------------|
| `failureCondition.count` | int | Antall feil som trigger trip | 3-5 |
| `failureCondition.interval` | duration | Tidsvindu for feil-telling | PT1M (1 minutt) |
| `failureCondition.statusCodeRanges` | array | HTTP-koder som teller som feil | 429 + 500-599 |
| `tripDuration` | duration | Hvor lenge circuit er åpent | PT10S - PT1M |
| `acceptRetryAfter` | bool | Bruk Retry-After header som trip duration | **true** (alltid for AI) |

### Prosentbasert vs. count-basert triggering

APIM støtter to modeller for feil-deteksjon:

**Count-basert (anbefalt for AI):**
```json
{
  "failureCondition": {
    "count": 3,
    "interval": "PT1M",
    "statusCodeRanges": [{"min": 429, "max": 429}]
  }
}
```
Trigger: 3 eller flere 429-responser innen 1 minutt.

**Prosentbasert:**
```json
{
  "failureCondition": {
    "percentage": 50,
    "interval": "PT1M",
    "statusCodeRanges": [{"min": 429, "max": 429}]
  }
}
```
Trigger: 50% eller mer av requests feiler innen 1 minutt.

**Anbefaling:** Count-basert er mer forutsigbar for AI-workloads. Prosentbasert kan gi falske positiver ved lav trafikk (2 av 3 requests feiler = 66%).

---

## Failure Threshold Tuning

### Faktorene som påvirker threshold

| Faktor | Lav threshold (1-2) | Høy threshold (5-10) |
|--------|---------------------|---------------------|
| **Reaksjonstid** | Rask, reagerer umiddelbart | Tregere, tolererer noen feil |
| **Falske positiver** | Høy risiko | Lav risiko |
| **Backend-beskyttelse** | Sterk, minimal ekstra belastning | Svakere, flere feil-requests |
| **Anbefalt for** | Kritiske, kapasitetsbegrensede backends | Robuste backends med transiente feil |

### Anbefalte innstillinger per scenario

| Scenario | count | interval | tripDuration | acceptRetryAfter |
|----------|-------|----------|--------------|-----------------|
| **PTU-instans (high priority)** | 3 | PT1M | PT10S | true |
| **PAYGO-instans (fallback)** | 5 | PT2M | PT30S | true |
| **Dev/test** | 2 | PT30S | PT5S | true |
| **Business-critical** | 3 | PT1M | PT10S | true |
| **Batch processing** | 10 | PT5M | PT1M | true |

### Dynamisk trip duration med Retry-After

Når `acceptRetryAfter: true` er satt, overstyrer Azure OpenAIs `Retry-After`-header den konfigurerte `tripDuration`:

```
Scenario: OpenAI returnerer 429 med Retry-After: 30
  → Circuit breaker åpner i 30 sekunder (ikke konfiguert tripDuration)
  → Etter 30 sekunder: half-open → test request
  → Suksess: circuit lukkes
  → Feil: circuit åpner igjen med ny Retry-After

Scenario: OpenAI returnerer 429 med Retry-After: 86400 (1 dag!)
  → Circuit breaker åpner i 24 timer
  → All trafikk rutes til andre backends i poolen
```

> **Viktig advarsel:** Azure OpenAI kan returnere Retry-After verdier opp til 1 dag (86400 sekunder). Med `acceptRetryAfter: true` vil dette bety at backend er utilgjengelig i 24 timer. Sørg for at backend-poolen har nok kapasitet i andre backends til å håndtere dette.

---

## Fallback-policies

### Mønster 1: Backend Pool med automatisk failover

Den enkleste og mest robuste tilnærmingen:

```xml
<policies>
    <inbound>
        <!-- Backend pool håndterer failover automatisk -->
        <set-backend-service backend-id="openai-pool-priority" />
        <authentication-managed-identity
            resource="https://cognitiveservices.azure.com/" />
    </inbound>
</policies>
```

Når circuit breaker utløses på Priority 1-backends, ruter APIM automatisk til Priority 2, osv.

### Mønster 2: Retry med backend-bytte

Eksplisitt retry-logikk som bytter backend ved 429:

```xml
<policies>
    <backend>
        <retry condition="@(context.Response.StatusCode == 429)"
               count="3"
               interval="0"
               first-fast-retry="true">
            <set-backend-service backend-id="openai-pool-priority" />
        </retry>
    </backend>
</policies>
```

**Viktig:** `interval="0"` betyr umiddelbar retry til neste backend, IKKE ventetid. Server-side retries bør aldri ha delay — det holder opp klienten og bruker gateway-ressurser.

### Mønster 3: Graceful Degradation med cache-fallback

```xml
<policies>
    <inbound>
        <!-- Prøv semantic cache først -->
        <azure-openai-semantic-cache-lookup
            score-threshold="0.20"
            embeddings-backend-id="embeddings-backend"
            embeddings-backend-auth="system-assigned" />

        <set-backend-service backend-id="openai-pool-priority" />
    </inbound>

    <on-error>
        <choose>
            <!-- Alle backends utilgjengelige -->
            <when condition="@(context.Response.StatusCode == 503)">
                <return-response>
                    <set-status code="503" reason="AI Service Temporarily Unavailable" />
                    <set-header name="Retry-After" exists-action="override">
                        <value>60</value>
                    </set-header>
                    <set-body>@{
                        return new JObject(
                            new JProperty("error", new JObject(
                                new JProperty("code", "service_unavailable"),
                                new JProperty("message", "AI-tjenesten er midlertidig utilgjengelig. Prøv igjen om 60 sekunder."),
                                new JProperty("type", "circuit_breaker_open")
                            ))
                        ).ToString();
                    }</set-body>
                </return-response>
            </when>
        </choose>
    </on-error>
</policies>
```

### Mønster 4: Fallback til enklere modell

```xml
<policies>
    <backend>
        <retry condition="@(context.Response.StatusCode == 429 || context.Response.StatusCode == 503)"
               count="1"
               interval="0"
               first-fast-retry="true">
            <!-- Fallback til mindre modell -->
            <set-backend-service backend-id="openai-gpt4o-mini-backend" />
            <rewrite-uri template="/deployments/gpt-4o-mini/chat/completions" />
        </retry>
    </backend>
</policies>
```

---

## Recovery-mekanismer

### Automatisk recovery

Circuit breaker håndterer recovery automatisk:

```
1. Trip: Circuit åpner (503 til klienter)
2. Wait: tripDuration utløper (eller Retry-After)
3. Half-Open: Test-request sendes til backend
4. Success: Circuit lukkes, normal drift gjenopptas
5. Failure: Circuit åpner igjen, ny tripDuration starter
```

### Recovery-timing

| Kilde | Prioritet | Typisk verdi |
|-------|-----------|--------------|
| `Retry-After` header (når `acceptRetryAfter: true`) | Høyest | 1-86400 sekunder |
| Konfigurert `tripDuration` | Fallback | PT10S - PT1M |
| Default (uten konfigurasjon) | Lavest | PT1M |

### Overvåking av circuit breaker-tilstand

```xml
<outbound>
    <!-- Log circuit breaker state -->
    <trace source="circuit-breaker" severity="information">
        <message>@{
            var backendId = context.Backend?.Id ?? "unknown";
            var statusCode = context.Response.StatusCode;
            return $"Backend: {backendId}, Status: {statusCode}";
        }</message>
    </trace>
</outbound>
```

**KQL for circuit breaker-hendelser:**

```kusto
ApiManagementGatewayLogs
| where ResponseCode == 503
| extend backendId = tostring(parse_json(BackendResponseBody).backendId)
| summarize CircuitBreakerTrips = count() by backendId, bin(TimeGenerated, 5m)
| render timechart
```

---

## Timeout-konfigurasjon

### Forward-request timeout

Kontroller hvor lenge APIM venter på backend-respons:

```xml
<policies>
    <backend>
        <forward-request timeout="120" />
    </backend>
</policies>
```

**Anbefalte timeouts for AI-workloads:**

| Operasjon | Anbefalt timeout | Begrunnelse |
|-----------|------------------|-------------|
| Chat Completion (standard) | 60-120 sek | GPT-4o kan bruke tid på komplekse prompts |
| Chat Completion (streaming) | 120-180 sek | Streaming starter raskt, men kan vare lenge |
| Embeddings | 30-60 sek | Raskere operasjon, typisk < 10 sek |
| Image Generation (DALL-E) | 120-180 sek | Bildegenerering er CPU-intensivt |
| Assistants API | 180-300 sek | Multi-step agent workflows |
| Batch API | 300-600 sek | Store batch-operasjoner |

### Timeout + Circuit Breaker interaksjon

```
Timeout utløper (120 sek)
  → APIM registrerer det som feil
  → Teller mot circuit breaker threshold
  → Etter N timeouts → Circuit breaker trip
  → Backend fjernes fra pool → Trafikk til alternativer
```

> **Best practice:** Sett timeout lavere enn det du tror er nødvendig. Bedre å få rask feil og retry til annen backend enn å vente 120 sekunder på en hengende request.

---

## Avanserte mønstre

### Mønster: Cascading Circuit Breakers

For multi-tier arkitekturer der APIM ruter til mellomtjenester som igjen kaller Azure OpenAI:

```
Client → APIM [CB-1] → Custom Service [CB-2] → Azure OpenAI
```

```bicep
// CB-1: APIM → Custom Service
resource customServiceBackend 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = {
  properties: {
    circuitBreaker: {
      rules: [{
        failureCondition: {
          count: 5
          interval: 'PT2M'
          statusCodeRanges: [
            { min: 502, max: 504 }
          ]
        }
        tripDuration: 'PT30S'
      }]
    }
  }
}
```

Applikasjons-nivå CB-2 implementeres med Polly (.NET), resilience4j (Java), eller tilsvarende.

### Mønster: Health Endpoint Monitoring

Kombiner circuit breaker med aktiv health checking:

```xml
<policies>
    <inbound>
        <!-- Sjekk health endpoint før ruting -->
        <send-request mode="new"
                      response-variable-name="healthCheck"
                      timeout="5"
                      ignore-error="true">
            <set-url>@("https://aoai-norwayeast.openai.azure.com/openai/models?api-version=2024-10-21")</set-url>
            <set-method>GET</set-method>
            <authentication-managed-identity
                resource="https://cognitiveservices.azure.com/" />
        </send-request>

        <choose>
            <when condition="@(((IResponse)context.Variables["healthCheck"]).StatusCode != 200)">
                <!-- Backend er nede, bruk fallback direkte -->
                <set-backend-service backend-id="openai-fallback-pool" />
            </when>
            <otherwise>
                <set-backend-service backend-id="openai-primary-pool" />
            </otherwise>
        </choose>
    </inbound>
</policies>
```

> **Merk:** Health endpoint monitoring legger til latens og backend-belastning. Bruk det kun for scenarier der circuit breaker alene ikke gir rask nok failover.

---

## Anti-mønstre

| Anti-mønster | Problem | Løsning |
|--------------|---------|---------|
| **Ingen circuit breaker** | Backend overbelastes, cascading failure | Aktiver circuit breaker på alle AI-backends |
| **For lav threshold (count=1)** | Falske positiver ved transiente feil | Bruk count=3-5 for produksjon |
| **For lang tripDuration** | Backend er unødvendig utilgjengelig | Bruk `acceptRetryAfter: true` |
| **Ignorere Retry-After** | Hammer backend som eksplisitt ber om pause | Sett `acceptRetryAfter: true` alltid |
| **Server-side delay** | Retry med sleep/delay holder opp klienter | Bruk `interval="0"` med backend pool failover |
| **Timeout for lang** | Gateway-ressurser brukt opp på hengende requests | Sett realistiske timeouts per operasjonstype |
| **Mangle monitoring** | Ingen innsikt i circuit breaker-oppførsel | Emit metrikk + KQL dashboards |

---

## Komplett resiliens-policy

```xml
<policies>
    <inbound>
        <!-- 1. Token rate limiting -->
        <llm-token-limit
            counter-key="@(context.Subscription.Id)"
            tokens-per-minute="50000"
            estimate-prompt-tokens="true" />

        <!-- 2. Backend pool med circuit breaker -->
        <set-backend-service backend-id="openai-pool-priority" />

        <!-- 3. Managed Identity auth -->
        <authentication-managed-identity
            resource="https://cognitiveservices.azure.com/" />
    </inbound>

    <backend>
        <!-- 4. Retry til annen backend ved 429 -->
        <retry condition="@(context.Response.StatusCode == 429)"
               count="3"
               interval="0"
               first-fast-retry="true">
            <set-backend-service backend-id="openai-pool-priority" />
        </retry>

        <!-- 5. Timeout -->
        <forward-request timeout="120" />
    </backend>

    <outbound>
        <!-- 6. Token metrikk -->
        <llm-emit-token-metric namespace="ai-gateway">
            <dimension name="Backend" value="@(context.Backend?.Id ?? "unknown")" />
            <dimension name="StatusCode" value="@(context.Response.StatusCode.ToString())" />
        </llm-emit-token-metric>
    </outbound>

    <on-error>
        <!-- 7. Graceful degradation -->
        <choose>
            <when condition="@(context.Response.StatusCode == 503)">
                <return-response>
                    <set-status code="503" reason="AI Service Unavailable" />
                    <set-header name="Retry-After" exists-action="override">
                        <value>60</value>
                    </set-header>
                    <set-body>{"error":{"code":"all_backends_unavailable","message":"Alle AI-backends er midlertidig utilgjengelige"}}</set-body>
                </return-response>
            </when>
        </choose>
    </on-error>
</policies>
```

---

## For Cosmo

- Circuit breaker er obligatorisk for alle Azure OpenAI backends i produksjon -- uten det risikerer du cascading failures og bortkastet gateway-kapasitet mot throttlede backends.
- Sett ALLTID `acceptRetryAfter: true` for Azure OpenAI backends. Azure OpenAI returnerer presise Retry-After verdier som gir optimal recovery-timing. Uten dette bruker du den statiske tripDuration som kan være for kort eller for lang.
- Anbefalt baseline: `count: 3`, `interval: PT1M`, `tripDuration: PT10S`, `acceptRetryAfter: true`. Juster count opp for PAYGO-backends med mer toleranse.
- Kombiner circuit breaker med backend pools for automatisk failover: når PTU-backend tripper, ruter APIM automatisk til PAYGO-backends uten klient-endringer.
- Advarsler: Azure OpenAI kan returnere Retry-After opp til 86400 sekunder (1 dag). Sørg for at arkitekturen har nok alternative backends til å håndtere langvarige circuit breaks.
