KB-currency refresh (medium priority, 2026-06-19) via /architect:kb-update. 74 medium-prioritets filer re-verifisert mot Microsoft Learn (MCP) — delegert til 15 parallelle Opus-subagenter (3 bølger) gruppert etter delt kilde, med disjunkte fil-sett. Verifisert i hovedkontekst (scope-sjekk + diff-review av de faktatunge gruppene + tester). Hovedendringer (faktuelle korreksjoner + currency): - Azure AI Search semantic ranker: TILGJENGELIG PÅ ALLE TIERS (også Free/Basic m/ gratis månedlig kvote) — gammel KB sa feilaktig "kun S1+". Korrigert i tier-tabell, anti-patterns og beslutningstabell (azure-ai-search-setup). - APIM score-threshold = DISTANSE (lavere = strengere): tuning-tabellen i rag-caching-optimization hadde retningen baklengs — invertert til korrekt. - Agentic retrieval GA/preview-nyanse presisert (hovedkontekst-korreksjon mot agentic-retrieval-how-to-migrate): GA via REST 2026-04-01 returnerer EKSTRAKTIV grounding (references + activity), IKKE syntetiserte svar. Answer synthesis, ikke-minimal reasoning effort (LLM query planning) og multi-turn messages forblir preview (2026-05-01-preview). Subagent hadde overforenklet til "hele kjernepipelinen GA"; rettet i agentic-rag-patterns + citation-tracking. - Copilot Studio modell-tabeller (platforms/copilot-studio): fjernet Claude Opus 4.5 + GPT-5.2 (borte fra kilde), lagt til Claude Sonnet 4.6/Opus 4.6 (GA), Opus 4.7 + Mistral Medium 3.5 (experimental); GPT-5 Reasoning/Auto = preview; A2A GA (apr 2026). - Computer Use (CUA): Copilot Studio GA 2026-05-07; 4 modeller m/ tier/status (OpenAI CUA + Sonnet 4.5 GA, Sonnet 4.6 + Opus 4.6 experimental); 5 credits/ steg standard, 15 premium; US-only region-krav FJERNET i GA-dok; Cloud PC pool + Hosted browser + bring-your-own-machine. - Azure AI Search REST API-versjoner bumpet: 2025-09-01 -> 2026-04-01 (stabil), 2025-11-01-preview -> 2026-05-01-preview (hybrid-search, rag-security-rbac, chunking). - Power Automate-integrasjon: trigger "Run a flow from Copilot" -> "When an agent calls the flow"; App Service innebygd MCP (preview) lagt til. - M365 Copilot-manifest v1.26 -> v1.28 (GA, mai) / v1.29 dokumentert (juni); "Tenant graph grounding" -> "Work IQ". - Speech fast transcription 2t/300MB -> 5t/500MB; multilingual 14 -> 15 locales (+ pt-BR). Content Understanding reasoning preview -> GA (v1.0, 2025-11-01). - Security Copilot E5 -> E5+E7. Død Databricks-URL ci-cd/best-practices -> ci-cd/flows. Prompt Flow retirement (2027-04-20 -> MAF) notert der den presenteres som go-forward. Gateway-topologi-tabell-feil rettet. - Alle 74 Last updated -> 2026-06-19. Discovery ikke kjørt (historisk kun Databricks-støy) -> 389-telling uendret, ingen resync. validate 239 PASS, kb-integrity 115/115 (262 orphan-warnings uendret), gitleaks clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REiKFhP4w6xGXXqWKpPCJJ
748 lines
23 KiB
Markdown
748 lines
23 KiB
Markdown
# Azure AI Services - API Design and Best Practices
|
|
|
|
**Last updated:** 2026-06-19 | Verified: MCP 2026-06-19
|
|
**Status:** GA
|
|
**Category:** Azure AI Services (Foundry Tools)
|
|
|
|
---
|
|
|
|
## Introduksjon
|
|
|
|
Når du bygger produksjonsklare applikasjoner med Azure AI Services (Azure OpenAI, Content Safety, Translator, Document Intelligence, Computer Vision, etc.), er robust API-design og feilhåndtering kritisk. Distribuerte skytjenester krever at applikasjoner håndterer midlertidige feil, throttling, nettverksproblemer og uventede responser på en strukturert måte.
|
|
|
|
Denne referansen dekker best practices for:
|
|
- **Error handling** — Strukturert feilhåndtering med Azure SDK exception hierarchy
|
|
- **Retry logic** — Eksponentiell backoff, rate limiting og retry storms
|
|
- **Rate limiting** — Throttling-håndtering og quota management
|
|
- **Batching** — Effektiv bruk av Batch API for høyvolum-operasjoner
|
|
- **Connection management** — Connection pooling og timeout-konfigurering
|
|
- **Idempotency** — Design for at identiske requests kan håndteres trygt
|
|
- **Authentication patterns** — Managed Identity vs. API keys
|
|
|
|
**Kilde:** Microsoft Learn (verified via MCP 2026-02)
|
|
|
|
---
|
|
|
|
## Kjernekomponenter / Nøkkelegenskaper
|
|
|
|
### 1. Azure SDK Exception Hierarchy
|
|
|
|
Azure SDK for Python og .NET bruker en hierarkisk exception-modell som gir både generiske og spesifikke error-handling capabilities.
|
|
|
|
**Exception-hierarki:**
|
|
|
|
```
|
|
AzureError (base)
|
|
├── ClientAuthenticationError
|
|
├── ResourceNotFoundError
|
|
├── ResourceExistsError
|
|
├── ResourceModifiedError
|
|
├── ResourceNotModifiedError
|
|
├── ServiceRequestError
|
|
├── ServiceResponseError
|
|
└── HttpResponseError
|
|
```
|
|
|
|
**Viktige exception-typer:**
|
|
|
|
| Exception | HTTP Status | Når den kastes | Retry? |
|
|
|-----------|-------------|----------------|--------|
|
|
| `ClientAuthenticationError` | 401 | Authentication failure | ❌ Nei — fix credentials |
|
|
| `ResourceNotFoundError` | 404 | Resource doesn't exist | ❌ Nei (unless transient) |
|
|
| `ResourceExistsError` | 409 | Resource already exists | ❌ Nei — handle duplicate |
|
|
| `HttpResponseError` (429) | 429 | Rate limit exceeded | ✅ Ja — med backoff |
|
|
| `HttpResponseError` (500-504) | 500-504 | Server/gateway error | ✅ Ja — transient |
|
|
| `ServiceRequestError` | N/A | Network/DNS failure | ✅ Ja — network transient |
|
|
|
|
### 2. HTTP Error Codes (Azure OpenAI)
|
|
|
|
| Status Code | Error Type | Retry Strategy |
|
|
|-------------|-----------|----------------|
|
|
| 400 | Bad Request | ❌ Fix input — don't retry |
|
|
| 401 | Authentication Error | ❌ Fix credentials |
|
|
| 403 | Permission Denied | ❌ Fix RBAC assignments |
|
|
| 404 | Not Found | ❌ Verify resource exists |
|
|
| 408 | Request Timeout | ✅ Retry with backoff |
|
|
| 422 | Unprocessable Entity | ❌ Fix input validation |
|
|
| 429 | Rate Limit Error | ✅ Retry with `retry-after` header |
|
|
| 500 | Internal Server Error | ✅ Retry with backoff |
|
|
| 502 | Bad Gateway | ✅ Retry with backoff |
|
|
| 503 | Service Unavailable | ✅ Retry with backoff |
|
|
| 504 | Gateway Timeout | ✅ Retry with backoff |
|
|
|
|
**Azure OpenAI SDKs** (Python, .NET, Go) retry automatisk 408, 429, 500, 502, 503, 504 — opptil 3 ganger med exponentiell backoff.
|
|
|
|
### 3. Retry Logic Patterns
|
|
|
|
**Eksponentiell backoff (anbefalt):**
|
|
|
|
```python
|
|
from azure.core.pipeline.policies import RetryPolicy
|
|
|
|
retry_policy = RetryPolicy(
|
|
retry_total=5, # Max retry attempts
|
|
retry_backoff_factor=2, # 2^n seconds
|
|
retry_backoff_max=60, # Max backoff: 60s
|
|
retry_on_status_codes=[408, 429, 500, 502, 503, 504]
|
|
)
|
|
|
|
client = BlobServiceClient(
|
|
account_url="https://...",
|
|
credential=credential,
|
|
retry_policy=retry_policy
|
|
)
|
|
```
|
|
|
|
**Azure OpenAI custom retry (Python):**
|
|
|
|
```python
|
|
from openai import AzureOpenAI
|
|
|
|
client = AzureOpenAI(
|
|
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
|
|
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
|
|
api_version="2024-10-21",
|
|
max_retries=5 # Default: 2
|
|
)
|
|
```
|
|
|
|
**C# retry med Polly:**
|
|
|
|
```csharp
|
|
using Azure;
|
|
using Azure.AI.Inference;
|
|
|
|
try {
|
|
var response = client.Complete(requestOptions);
|
|
} catch (RequestFailedException ex) {
|
|
if (ex.ErrorCode == "content_filter") {
|
|
Console.WriteLine($"Content filter triggered: {ex.Message}");
|
|
} else if (ex.Status == 429) {
|
|
// Implement exponential backoff
|
|
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
|
|
} else {
|
|
throw;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Rate Limiting og 429 Responses
|
|
|
|
**Azure OpenAI Provisioned Throughput:**
|
|
|
|
- **429 respons** betyr at provisjonerte PTU-er er fullt benyttet
|
|
- Service returnerer `retry-after` og `retry-after-ms` headers
|
|
- **Standard SDK-oppførsel:** Respekterer `retry-after` og retrier automatisk
|
|
|
|
**Håndtering av 429:**
|
|
|
|
| Strategi | Når bruke | Latency Impact |
|
|
|----------|-----------|----------------|
|
|
| **Client-side retry** | OK med høyere latency | ⬆️ Høyere (venter på retry-after) |
|
|
| **Fallback til annen deployment** | Low-latency krav | ⬇️ Lavere (umiddelbar failover) |
|
|
| **Fallback til global-standard** | Cost/availability balance | ➡️ Moderat (noe høyere cost) |
|
|
|
|
**Rate limiting pattern (for bulk operations):**
|
|
|
|
```python
|
|
# Bad practice: Naive retry storm
|
|
for record in records:
|
|
try:
|
|
client.process(record)
|
|
except RateLimitError:
|
|
time.sleep(1) # Fixed delay — overwhelms service
|
|
|
|
# Good practice: Rate limiter + durable queue
|
|
# 1. Enqueue to Azure Event Hubs/Service Bus
|
|
# 2. Job processor dequeues at controlled rate
|
|
# 3. Tracks PTU utilization via Azure Monitor
|
|
```
|
|
|
|
### 5. Batching (Azure OpenAI Batch API)
|
|
|
|
**Batch API:** Asynkrone batch-operasjoner med 50% lavere kostnad enn real-time API.
|
|
|
|
**Bruksområder:**
|
|
- Large-scale data processing (embeddings, summarization)
|
|
- Content generation (product descriptions, translations)
|
|
- Document review (legal, compliance)
|
|
- NLP tasks (sentiment analysis, classification)
|
|
|
|
**Batch limits:**
|
|
|
|
| Parameter | Limit |
|
|
|-----------|-------|
|
|
| Max batch files (no expiration) | 500 |
|
|
| Max batch files (with expiration) | 10,000 |
|
|
| Max input file size | 200 MB (BYOS: 1 GB) |
|
|
| Max requests per file | 100,000 |
|
|
|
|
**Queueing with exponential backoff (Python):**
|
|
|
|
```python
|
|
import time
|
|
|
|
max_retries = 10
|
|
retry_count = 0
|
|
batch_job = None
|
|
|
|
while retry_count < max_retries:
|
|
try:
|
|
batch_job = client.batches.create(
|
|
input_file_id=file_id,
|
|
endpoint="/chat/completions",
|
|
completion_window="24h"
|
|
)
|
|
break # Success
|
|
except Exception as e:
|
|
if "token limit exceeded" in str(e):
|
|
retry_count += 1
|
|
wait_time = 2 ** retry_count
|
|
time.sleep(wait_time)
|
|
else:
|
|
raise
|
|
```
|
|
|
|
**Fail-fast regions (for batching):** Australia East, East US, Germany West Central, Italy North, North Central US, Poland Central, Sweden Central, Switzerland North, East US 2, West US.
|
|
|
|
### 6. Connection Pooling og Timeouts
|
|
|
|
**HTTP connection pooling (Python):**
|
|
|
|
```python
|
|
import requests
|
|
|
|
# Keep-alive enabled by default
|
|
session = requests.Session()
|
|
response = session.get("https://api.example.com")
|
|
```
|
|
|
|
**Azure OpenAI timeout configuration (Python):**
|
|
|
|
```python
|
|
from openai import AzureOpenAI
|
|
|
|
client = AzureOpenAI(
|
|
azure_endpoint="...",
|
|
api_key="...",
|
|
timeout=300.0 # 5 minutes (default: 600s/10 min)
|
|
)
|
|
```
|
|
|
|
**Connection pooling for database SDKs:**
|
|
|
|
| SDK | Module |
|
|
|-----|--------|
|
|
| MySQL | `mysql.connector.pooling` |
|
|
| PostgreSQL | `psycopg2.pool` |
|
|
| SQLAlchemy | `sqlalchemy.pool` |
|
|
| Pyodbc | Built-in pooling |
|
|
|
|
**Best practice:**
|
|
- ✅ Bruk connection pools for database/HTTP clients
|
|
- ✅ Sett realistiske timeouts (ikke 10 min for user-facing apps)
|
|
- ✅ Implementer keepalives for long-running connections
|
|
- ❌ IKKE opprett nye connections for hver request
|
|
|
|
### 7. Idempotency
|
|
|
|
**Definisjon:** En operasjon er idempotent hvis den kan kalles flere ganger uten å produsere flere side-effekter etter første kall.
|
|
|
|
**HTTP idempotency:**
|
|
|
|
| HTTP Method | Idempotent? | Beskrivelse |
|
|
|-------------|-------------|-------------|
|
|
| `GET` | ✅ Ja | Read-only, ingen side-effekter |
|
|
| `PUT` | ✅ Ja | Replaces resource at URI |
|
|
| `DELETE` | ✅ Ja | Deletes resource (samme outcome) |
|
|
| `POST` | ❌ Nei | Creates new resource hver gang |
|
|
| `PATCH` | ❌ Nei | Partial update (depends) |
|
|
|
|
**Idempotency-teknikker for Azure AI Services:**
|
|
|
|
```python
|
|
# 1. Check if already processed (database lookup)
|
|
def process_document(doc_id):
|
|
if already_processed(doc_id):
|
|
return cached_result(doc_id)
|
|
|
|
result = client.analyze_document(...)
|
|
save_result(doc_id, result)
|
|
return result
|
|
|
|
# 2. Event-carried state transfer (Event Hubs)
|
|
event = {
|
|
"doc_id": "12345",
|
|
"operation": "set_status",
|
|
"status": "completed", # Not "increment_count" — idempotent
|
|
"timestamp": "2026-02-03T10:00:00Z"
|
|
}
|
|
|
|
# 3. Deduplication window (Service Bus)
|
|
# Enable duplicate detection with MessageId
|
|
message.message_id = f"{order_id}-{timestamp}"
|
|
```
|
|
|
|
**Duplicate detection (Azure Service Bus):**
|
|
- Default deduplication window: 10 minutes
|
|
- Min: 20 seconds, Max: 7 days
|
|
- Based on `MessageId` (or `MessageId + PartitionKey` if partitioned)
|
|
|
|
---
|
|
|
|
## Arkitekturmønstre
|
|
|
|
### Pattern 1: Rate Limiting med Durable Messaging
|
|
|
|
**Problem:** Bulk ingestion til throttled service (Azure Cosmos DB, Azure AI Search) resulterer i retry storms og høy feilrate.
|
|
|
|
**Løsning:** Bruk Azure Event Hubs/Service Bus som buffer + job processor med rate limiting.
|
|
|
|
```
|
|
User API → Event Hubs → Job Processor (rate-limited) → Azure AI Service
|
|
(buffer) (100 req/s controlled)
|
|
```
|
|
|
|
**Implementering:**
|
|
|
|
1. **API enqueues messages** (millions per second capacity)
|
|
2. **Job processor** leases partitions from blob storage (15s lease)
|
|
- Each partition = 100 PTUs (requests/s)
|
|
- Process dequeues only what it can handle in 1s
|
|
3. **Monitor utilization** via Azure Monitor (`Provisioned-Managed Utilization V2`)
|
|
|
|
**Fordeler:**
|
|
- ✅ Reduserer 429 errors fra 80% til <5%
|
|
- ✅ Predikterbar throughput
|
|
- ✅ Ingen data loss ved crash (durable queue)
|
|
- ✅ Skalerer horisontalt (multiple job processors)
|
|
|
|
### Pattern 2: Circuit Breaker (for transient faults)
|
|
|
|
**Problem:** Gjentatte kall til utilgjengelig service forverrer problemet (thundering herd).
|
|
|
|
**Løsning:** Circuit Breaker pattern.
|
|
|
|
**States:**
|
|
|
|
| State | Oppførsel |
|
|
|-------|-----------|
|
|
| **Closed** | Normal operation — forwards requests |
|
|
| **Open** | Service unavailable — fails fast (no requests) |
|
|
| **Half-open** | Test if service recovered — 1 request |
|
|
|
|
**Implementering (Python):**
|
|
|
|
```python
|
|
class CircuitBreaker:
|
|
def __init__(self, failure_threshold=5, recovery_timeout=60):
|
|
self.failure_threshold = failure_threshold
|
|
self.recovery_timeout = recovery_timeout
|
|
self.failure_count = 0
|
|
self.state = 'closed'
|
|
self.last_failure_time = None
|
|
|
|
def call(self, func, *args, **kwargs):
|
|
if self.state == 'open':
|
|
if time.time() - self.last_failure_time > self.recovery_timeout:
|
|
self.state = 'half-open'
|
|
else:
|
|
raise Exception("Circuit breaker open")
|
|
|
|
try:
|
|
result = func(*args, **kwargs)
|
|
if self.state == 'half-open':
|
|
self.state = 'closed'
|
|
self.failure_count = 0
|
|
return result
|
|
except Exception:
|
|
self.failure_count += 1
|
|
self.last_failure_time = time.time()
|
|
if self.failure_count >= self.failure_threshold:
|
|
self.state = 'open'
|
|
raise
|
|
```
|
|
|
|
### Pattern 3: Idempotent Consumer (Event Hubs + Functions)
|
|
|
|
**Problem:** Event Hubs garanterer at-least-once delivery — events kan prosesseres flere ganger.
|
|
|
|
**Løsning:** Idempotent function design.
|
|
|
|
**Teknikker:**
|
|
|
|
1. **Duplicate detection via database:**
|
|
```python
|
|
def process_event(event):
|
|
if db.exists(event.id):
|
|
return # Already processed
|
|
|
|
result = ai_client.analyze(event.data)
|
|
db.save(event.id, result)
|
|
```
|
|
|
|
2. **Event-carried state transfer:**
|
|
```json
|
|
{
|
|
"account_id": "12345",
|
|
"operation": "set_balance",
|
|
"new_balance": 1000 // Not "withdraw 100" — idempotent
|
|
}
|
|
```
|
|
|
|
3. **PeekLock receive mode (Service Bus):**
|
|
- Consumer får exclusive lock (configurable duration)
|
|
- Sender acknowledgment ved success
|
|
- Message returneres til queue ved timeout/failure
|
|
|
|
### Pattern 4: Fallback Strategy (429 Handling)
|
|
|
|
**Multi-tier fallback:**
|
|
|
|
```python
|
|
from openai import AzureOpenAI
|
|
|
|
def generate_completion(prompt):
|
|
try:
|
|
# 1. Try provisioned deployment (lowest latency)
|
|
return provisioned_client.chat.completions.create(...)
|
|
except Exception as e:
|
|
if e.status_code == 429:
|
|
# 2. Fallback to standard deployment
|
|
return standard_client.chat.completions.create(...)
|
|
raise
|
|
|
|
# Alternative: Retry with backoff
|
|
client = AzureOpenAI(
|
|
max_retries=5,
|
|
timeout=300.0
|
|
)
|
|
response = client.with_options(max_retries=5).chat.completions.create(...)
|
|
```
|
|
|
|
---
|
|
|
|
## Beslutningsveiledning
|
|
|
|
### Når bruke Batch API vs. Real-time API?
|
|
|
|
| Kriterium | Batch API | Real-time API |
|
|
|-----------|-----------|---------------|
|
|
| **Latency krav** | >24 timer OK | <1 sekund nødvendig |
|
|
| **Volume** | >10,000 requests | <1,000 requests |
|
|
| **Cost sensitivity** | Høy (50% saving) | Moderat |
|
|
| **Use case** | Offline analytics, bulk processing | User-facing chat, real-time translation |
|
|
|
|
### Retry Strategy Decision Tree
|
|
|
|
```
|
|
429 Error?
|
|
├─ Ja → Sjekk retry-after header → Vent og retry (max 5x)
|
|
│ └─ Hvis fortsatt 429 → Fallback til annen deployment
|
|
│
|
|
└─ 500-504? → Exponential backoff (2^n seconds, max 60s)
|
|
├─ Transient → Retry opptil 5 ganger
|
|
└─ Persistent → Log error + alert ops team
|
|
|
|
401/403? → IKKE retry → Fix authentication/RBAC
|
|
400/422? → IKKE retry → Fix input validation
|
|
```
|
|
|
|
### Rate Limiting Strategy
|
|
|
|
| Scenario | Anbefalt Løsning |
|
|
|----------|------------------|
|
|
| **Single client, moderate load** | SDK default retry logic (max_retries=5) |
|
|
| **Multiple uncoordinated clients** | Distributed lease system (blob storage) + partitions |
|
|
| **Bulk ingestion** | Event Hubs + job processor med rate limiter |
|
|
| **User-facing app** | Fallback til standard deployment ved 429 |
|
|
|
|
---
|
|
|
|
## Integrasjon med Microsoft-stakken
|
|
|
|
### Azure AI Foundry Integration
|
|
|
|
**SDK-er som støtter Azure AI Foundry:**
|
|
|
|
- **Python:** `azure-ai-inference`, `openai` (Azure variant)
|
|
- **.NET:** `Azure.AI.Inference`, `Azure.AI.OpenAI`
|
|
- **JavaScript/TypeScript:** `@azure/openai`, `@azure/ai-inference`
|
|
- **Go:** `github.com/openai/openai-go` (med Azure endpoint)
|
|
|
|
**Authentication patterns:**
|
|
|
|
```python
|
|
# 1. DefaultAzureCredential (anbefalt for prod)
|
|
from azure.identity import DefaultAzureCredential
|
|
from azure.ai.inference import ChatCompletionsClient
|
|
|
|
credential = DefaultAzureCredential()
|
|
client = ChatCompletionsClient(
|
|
endpoint="https://<resource>.openai.azure.com",
|
|
credential=credential
|
|
)
|
|
|
|
# 2. Managed Identity (Azure-hosted apps)
|
|
from azure.identity import ManagedIdentityCredential
|
|
|
|
credential = ManagedIdentityCredential()
|
|
|
|
# 3. API Key (development only)
|
|
from azure.core.credentials import AzureKeyCredential
|
|
|
|
credential = AzureKeyCredential(os.getenv("AZURE_OPENAI_API_KEY"))
|
|
```
|
|
|
|
### Azure Monitor Integration
|
|
|
|
**Metrics å overvåke:**
|
|
|
|
| Metric | Threshold | Alert |
|
|
|--------|-----------|-------|
|
|
| `Provisioned-Managed Utilization V2` | >95% | Scale up PTUs |
|
|
| `Dependency failures` | >10% | Check retry logic |
|
|
| `Request duration` | >10s | Optimize prompts/batching |
|
|
| `429 error rate` | >5% | Increase quota or add fallback |
|
|
|
|
**Kusto query (Log Analytics):**
|
|
|
|
```kusto
|
|
AzureDiagnostics
|
|
| where ResourceType == "COGNITIVE-SERVICES"
|
|
| where Category == "RequestResponse"
|
|
| where resultCode_d == 429
|
|
| summarize count() by bin(TimeGenerated, 5m), clientIp_s
|
|
| order by count_ desc
|
|
```
|
|
|
|
### Power Automate / Logic Apps Integration
|
|
|
|
**Error handling i flows:**
|
|
|
|
1. **Configure retry policy:**
|
|
- Retry count: 4
|
|
- Retry interval: Exponential (PT10S, PT20S, PT40S, PT80S)
|
|
- Retry on: 408, 429, 500, 502, 503, 504
|
|
|
|
2. **Handle 429 with condition:**
|
|
```json
|
|
{
|
|
"condition": "@equals(actions('Call_Azure_AI').statusCode, 429)",
|
|
"ifTrue": {
|
|
"Wait": "@actions('Call_Azure_AI').outputs.headers['retry-after']"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Offentlig sektor (Norge)
|
|
|
|
### Compliance og Error Handling
|
|
|
|
**GDPR/Personopplysningsloven:**
|
|
- ✅ Logg ALDRI personidentifiserende informasjon i error logs
|
|
- ✅ Bruk correlation IDs (ikke bruker-ID) i telemetry
|
|
- ✅ Respekter `retry-after` headers (ikke spam API-er)
|
|
|
|
**Eksempel (sanitized logging):**
|
|
|
|
```python
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
try:
|
|
result = client.analyze_document(doc_id)
|
|
except HttpResponseError as e:
|
|
logger.error(
|
|
"Document analysis failed",
|
|
extra={
|
|
"correlation_id": e.response.headers.get('x-ms-request-id'),
|
|
"status_code": e.status_code,
|
|
"doc_id": hash(doc_id), # Hash, not plaintext
|
|
"error_code": e.error.code if e.error else None
|
|
}
|
|
)
|
|
```
|
|
|
|
### Idempotency for Offentlig Sektor Use Cases
|
|
|
|
**Saksbehandlingssystemer:**
|
|
- ✅ Bruk MessageId = `{saksID}-{operasjon}-{timestamp}`
|
|
- ✅ Aktiver duplicate detection (Service Bus)
|
|
- ✅ Check database før processing (deduplication table)
|
|
|
|
**E-post varsling (som må være idempotent):**
|
|
```python
|
|
def send_notification(case_id, notification_type):
|
|
message_id = f"{case_id}-{notification_type}"
|
|
|
|
if already_sent(message_id):
|
|
return # Idempotent — don't resend
|
|
|
|
send_email(...)
|
|
mark_sent(message_id)
|
|
```
|
|
|
|
---
|
|
|
|
## Kostnad og lisensiering
|
|
|
|
### Kostnad-konsekvenser av API Design
|
|
|
|
**429 Errors kosten ingenting** (ingen PTU consumption), MEN:
|
|
- ❌ 400 errors (content filter) **koster** (prompt ble prosessert)
|
|
- ❌ 408 timeout **koster** (delvis processing)
|
|
- ❌ `finish_reason: content_filter` **koster** (completion ble filtrert)
|
|
|
|
**Batch API savings:**
|
|
|
|
| Scenario | Real-time Cost | Batch Cost | Savings |
|
|
|----------|----------------|------------|---------|
|
|
| 1M tokens (GPT-4o) | ~$10 | ~$5 | 50% |
|
|
| Embeddings (1M tokens) | ~$0.13 | ~$0.065 | 50% |
|
|
|
|
**Provisioned vs. Standard:**
|
|
|
|
- **Provisioned:** Fast kostnad (per PTU/hour), predictable latency
|
|
- **Standard:** Pay-per-token, ingen garantier ved high traffic
|
|
|
|
**Reservation discounts (Provisioned):**
|
|
- 1-årig commitment: ~37% discount
|
|
- 3-årig commitment: ~57% discount
|
|
|
|
---
|
|
|
|
## For arkitekten (Cosmo)
|
|
|
|
### Design Principles for Robust API Integration
|
|
|
|
1. **Error Handling Hierarchy:**
|
|
```
|
|
Try specific exceptions first → HttpResponseError → AzureError → generic Exception
|
|
```
|
|
|
|
2. **Retry Decision Matrix:**
|
|
- **Transient (retry):** 408, 429, 500-504, network errors
|
|
- **Permanent (don't retry):** 400, 401, 403, 404, 422
|
|
- **Custom logic:** 429 with fallback
|
|
|
|
3. **Rate Limiting Strategy:**
|
|
- **Low volume (<100 req/s):** SDK default retry
|
|
- **High volume (>1000 req/s):** Event Hubs + job processor
|
|
- **Provisioned deployments:** Monitor utilization, implement fallback
|
|
|
|
4. **Batching Decision:**
|
|
- Latency >1 min? → Batch API
|
|
- Volume >10k requests? → Batch API
|
|
- Cost critical? → Batch API
|
|
|
|
5. **Idempotency Checklist:**
|
|
- [ ] Operations designed for identical input?
|
|
- [ ] Duplicate detection enabled (if using Service Bus)?
|
|
- [ ] Database check before processing?
|
|
- [ ] Correlation IDs for tracing?
|
|
|
|
### Common Anti-Patterns (og hvordan unngå dem)
|
|
|
|
| Anti-Pattern | Problem | Løsning |
|
|
|--------------|---------|---------|
|
|
| **while(true) retry loop** | Retry storm → overwhelms service | Max retries + exponential backoff |
|
|
| **Fixed 1-second delays** | Ignores `retry-after` header | Use SDK retry eller respekter header |
|
|
| **Ingen connection pooling** | SNAT port exhaustion | Enable connection pooling |
|
|
| **Hardcoded API keys** | Security risk | Use Managed Identity + Key Vault |
|
|
| **No timeout configuration** | Hanging requests (10 min default) | Set realistic timeouts (30-300s) |
|
|
| **Logging sensitive data** | GDPR violation | Hash/mask PII in logs |
|
|
|
|
### Monitoring og Alerting
|
|
|
|
**Kritiske metrics:**
|
|
|
|
```python
|
|
# Azure Monitor query for error rate trends
|
|
AzureDiagnostics
|
|
| where ResourceType == "COGNITIVE-SERVICES"
|
|
| where TimeGenerated > ago(1h)
|
|
| summarize
|
|
total_requests = count(),
|
|
errors = countif(resultCode_d >= 400)
|
|
by bin(TimeGenerated, 5m)
|
|
| extend error_rate = (errors * 100.0) / total_requests
|
|
| where error_rate > 5 # Alert if >5% error rate
|
|
```
|
|
|
|
**Alert rules:**
|
|
- **429 rate >5%** → Scale PTUs eller enable fallback
|
|
- **500-504 errors** → Check service health dashboard
|
|
- **Average latency >5s** → Optimize prompts eller batch processing
|
|
|
|
### Architecture Decision Records (ADR) Triggers
|
|
|
|
**Når skal du lage en ADR?**
|
|
|
|
- [ ] Velger Batch API over real-time API for produksjon
|
|
- [ ] Implementerer custom retry logic (avviker fra SDK defaults)
|
|
- [ ] Bruker distributed rate limiting (blob leases)
|
|
- [ ] Velger Provisioned over Standard (cost/latency trade-off)
|
|
- [ ] Implementerer multi-region fallback strategy
|
|
|
|
---
|
|
|
|
## Kilder og verifisering
|
|
|
|
**Verification status:** ✅ Verified via Microsoft Learn MCP (2026-02)
|
|
|
|
**Primary sources (fetched):**
|
|
|
|
1. **Handle errors produced by the Azure SDK for Python**
|
|
- URL: https://learn.microsoft.com/en-us/azure/developer/python/sdk/fundamentals/errors
|
|
- Confidence: **Verified** (MCP fetch)
|
|
|
|
2. **Rate Limiting pattern**
|
|
- URL: https://learn.microsoft.com/en-us/azure/architecture/patterns/rate-limiting-pattern
|
|
- Confidence: **Verified** (MCP fetch)
|
|
|
|
3. **Retry Storm antipattern**
|
|
- URL: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/retry-storm
|
|
- Confidence: **Verified** (MCP fetch)
|
|
|
|
4. **Get started using provisioned deployments on Azure OpenAI**
|
|
- URL: https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/provisioned-get-started
|
|
- Confidence: **Verified** (MCP fetch)
|
|
|
|
5. **Getting started with Azure OpenAI batch deployments**
|
|
- URL: https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/batch
|
|
- Confidence: **Verified** (MCP search)
|
|
|
|
6. **Azure AI services authentication and authorization using .NET**
|
|
- URL: https://learn.microsoft.com/en-us/dotnet/ai/azure-ai-services-authentication
|
|
- Confidence: **Verified** (MCP search)
|
|
|
|
7. **Designing Azure Functions for identical input (idempotency)**
|
|
- URL: https://learn.microsoft.com/en-us/azure/azure-functions/functions-idempotent
|
|
- Confidence: **Verified** (MCP search)
|
|
|
|
8. **Duplicate detection (Azure Service Bus)**
|
|
- URL: https://learn.microsoft.com/en-us/azure/service-bus-messaging/duplicate-detection
|
|
- Confidence: **Verified** (MCP search)
|
|
|
|
**Code samples (verified):**
|
|
|
|
- Azure.AI.Inference (C#) error handling
|
|
- Azure SDK Python retry policies
|
|
- OpenAI Python SDK custom retry configuration
|
|
|
|
**Related documentation:**
|
|
|
|
- Azure Monitor metrics and logging
|
|
- Circuit Breaker pattern (Azure Architecture Center)
|
|
- Connection pooling (Azure App Service best practices)
|
|
|
|
**Baseline knowledge (model):**
|
|
- HTTP idempotency semantics (RFC 7231)
|
|
- Exponential backoff algorithms
|
|
- Connection pooling concepts
|
|
|
|
**MCP call summary:** 7 microsoft_docs_search + 4 microsoft_docs_fetch + 1 microsoft_code_sample_search = 12 total MCP calls
|