ms-ai-architect/skills/ms-ai-security/references/performance-scalability/batch-api-usage-optimization.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

575 lines
20 KiB
Markdown

# Batch API Usage and Optimization
**Last updated:** 2026-06-24
**Status:** GA
**Category:** Performance & Scalability
**Source:** https://learn.microsoft.com/azure/foundry/openai/how-to/batch-blob-storage
**Type:** reference
---
## Innhold
- [Introduksjon](#introduksjon)
- [Oversikt over Batch API](#oversikt-over-batch-api)
- [Batch Job-sammensetning](#batch-job-sammensetning)
- [Filopplasting og -handtering](#filopplasting-og--handtering)
- [Batch Job-oppretting og -overvaking](#batch-job-oppretting-og--overvaking)
- [Kostnadsberegning og besparelser](#kostnadsberegning-og-besparelser)
- [Retry og feilhhandtering](#retry-og-feilhhandtering)
- [Bruksomrader for norsk offentlig sektor](#bruksomrader-for-norsk-offentlig-sektor)
- [Sjekkliste for batch-optimalisering](#sjekkliste-for-batch-optimalisering)
- [For Cosmo](#for-cosmo)
## Introduksjon
Azure OpenAI Batch API er designet for storskala, asynkron prosessering av AI-arbeidslaster. Med 50% lavere kostnad enn Global Standard-prising og separat kvote som ikke pavirker online-trafikken, er Batch API ideelt for norsk offentlig sektor som trenger a prosessere store volumer av dokumenter, klassifiseringer eller analyser.
For offentlige virksomheter som Direktoratet for digital tjenesteutvikling, Nav, eller Skatteetaten kan Batch API brukes til masseprosessering av henvendelser, dokumentanalyse, oversettelser og datautvinning uten a forstyrre sanntidstjenestene. Tjenesten er spesielt verdifull for periodiske oppgaver som kvartalsvis rapportering, arsavslutning, eller migrering av historiske data.
Denne referansen dekker hele arbeidsflyten for Batch API, fra filsammensetning og opplasting til kostnadsberegning og feilhhandtering, med fokus pa optimalisering for store volumer.
## Oversikt over Batch API
### Nokkelegenskaper
| Egenskap | Verdi |
|----------|-------|
| Kostnadsreduksjon | 50% lavere enn Global Standard |
| Malsatt leveringstid | 24 timer |
| Maksimal filstorrelse | 200 MB (direkte), 1 GB (via Blob Storage) |
| Maksimalt antall requests per fil | 100 000 |
| Maks batch-filer per ressurs | 500 (uten utlop), 10 000 (med utlop) |
| Kvotetype | Separat enqueued token-kvote |
| Stottede modeller | GPT-4o, GPT-4o mini, GPT-4.1, o3-mini m.fl. |
| Deployment-type | GlobalBatch eller DataZoneBatch |
### Batch API vs. Standard API
| Aspekt | Standard API | Batch API |
|--------|-------------|-----------|
| Prosessering | Synkron, umiddelbar | Asynkron, 24-timers mal |
| Kostnad | Full pris | 50% rabatt |
| Kvote | Delt TPM-kvote | Separat enqueued token-kvote |
| Bruksomrade | Sanntid, interaktivt | Masseprosessering, analyse |
| Pavirkning pa online | Ja, deler kapasitet | Nei, separat kapasitet |
| Filformat | JSON per request | JSONL (samlet fil) |
## Batch Job-sammensetning
### JSONL-filformat
Batch API bruker JSON Lines-format (`.jsonl`), der hver linje er en selvstendig JSON-objekt:
```jsonl
{"custom_id": "req-001", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-batch", "messages": [{"role": "system", "content": "Klassifiser henvendelsen som KLAGE, SPORSMAL, eller TILBAKEMELDING."}, {"role": "user", "content": "Jeg er svart misfornoyd med ventetiden pa fornyelse av forerkort."}], "max_tokens": 50}}
{"custom_id": "req-002", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-batch", "messages": [{"role": "system", "content": "Klassifiser henvendelsen som KLAGE, SPORSMAL, eller TILBAKEMELDING."}, {"role": "user", "content": "Hvordan soker jeg om nytt forerkort?"}], "max_tokens": 50}}
{"custom_id": "req-003", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-batch", "messages": [{"role": "system", "content": "Klassifiser henvendelsen som KLAGE, SPORSMAL, eller TILBAKEMELDING."}, {"role": "user", "content": "Fint arbeid med den nye tunnelen i Rogaland!"}], "max_tokens": 50}}
```
**Viktige regler:**
- `custom_id` er obligatorisk og lar deg koble respons til input
- `model` ma vaere identisk pa alle linjer og matche deployment-navnet
- Responser returneres IKKE i samme rekkefolge som input
- For best ytelse: send store filer fremfor mange sma filer
### Responses API-format (nyere)
```jsonl
{"custom_id": "req-001", "method": "POST", "url": "/v1/responses", "body": {"model": "gpt-4o-batch", "input": [{"role": "user", "content": "Oppsummer dette dokumentet: ..."}], "max_output_tokens": 500}}
```
### Programmatisk filgenerering (Python)
```python
import json
from pathlib import Path
from typing import Iterator
def generate_batch_file(
items: list[dict],
system_prompt: str,
model: str,
output_path: str,
max_tokens: int = 200
) -> Path:
"""Generer JSONL batch-fil fra en liste med items."""
path = Path(output_path)
with open(path, "w", encoding="utf-8") as f:
for i, item in enumerate(items):
request = {
"custom_id": f"req-{i:06d}",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": item["content"]}
],
"max_tokens": max_tokens,
"temperature": 0.1 # Lav temperatur for konsistens
}
}
f.write(json.dumps(request, ensure_ascii=False) + "\n")
file_size = path.stat().st_size
print(f"Generert batch-fil: {path}")
print(f"Antall requests: {len(items)}")
print(f"Filstorrelse: {file_size / (1024*1024):.1f} MB")
return path
def chunk_batch_file(
input_path: str,
max_requests: int = 100_000,
max_size_mb: int = 190
) -> list[str]:
"""Del opp en stor batch-fil i mindre filer innenfor grensene."""
chunks = []
current_chunk = []
current_size = 0
chunk_index = 0
with open(input_path, "r", encoding="utf-8") as f:
for line in f:
line_size = len(line.encode("utf-8"))
if (len(current_chunk) >= max_requests or
(current_size + line_size) > max_size_mb * 1024 * 1024):
# Skriv gjeldende chunk og start ny
chunk_path = f"{input_path}.chunk_{chunk_index:03d}.jsonl"
with open(chunk_path, "w", encoding="utf-8") as cf:
cf.writelines(current_chunk)
chunks.append(chunk_path)
current_chunk = []
current_size = 0
chunk_index += 1
current_chunk.append(line)
current_size += line_size
# Skriv siste chunk
if current_chunk:
chunk_path = f"{input_path}.chunk_{chunk_index:03d}.jsonl"
with open(chunk_path, "w", encoding="utf-8") as cf:
cf.writelines(current_chunk)
chunks.append(chunk_path)
return chunks
```
## Filopplasting og -handtering
### Opplasting via Python SDK
```python
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint="https://your-resource.openai.azure.com/",
api_key="your-api-key",
api_version="2025-03-01-preview"
)
# Last opp batch-fil med utlopsdato (14 dager)
file_response = client.files.create(
file=open("batch_requests.jsonl", "rb"),
purpose="batch",
extra_body={
"expires_after": {
"seconds": 1209600, # 14 dager
"anchor": "created_at"
}
}
)
print(f"Fil-ID: {file_response.id}")
print(f"Status: {file_response.status}")
print(f"Storrelse: {file_response.bytes} bytes")
```
### Opplasting via REST API
```bash
curl https://YOUR_RESOURCE.openai.azure.com/openai/files?api-version=2025-03-01-preview \
-H "api-key: $AZURE_OPENAI_API_KEY" \
-F "purpose=batch" \
-F "file=@batch_requests.jsonl" \
-F "expires_after.seconds=1209600" \
-F "expires_after.anchor=created_at"
```
### Stor fil via Azure Blob Storage (BYOS)
For filer over 200 MB (opptil 1 GB):
```python
from azure.storage.blob import BlobServiceClient
# 1. Last opp til Azure Blob Storage
blob_service = BlobServiceClient.from_connection_string(conn_str)
container = blob_service.get_container_client("batch-files")
with open("large_batch.jsonl", "rb") as data:
container.upload_blob(
name="large_batch.jsonl",
data=data,
overwrite=True
)
# 2. Konfigurer Azure OpenAI til a bruke Blob Storage
# Se: https://learn.microsoft.com/azure/foundry-classic/openai/how-to/batch-blob-storage
```
### Filgrenser
| Grense | Verdi | Med utlopsdato |
|--------|-------|----------------|
| Maks input-filstorrelse | 200 MB | 200 MB |
| Maks input-filstorrelse (BYOS) | 1 GB | 1 GB |
| Maks requests per fil | 100 000 | 100 000 |
| Maks input-filer per ressurs | 500 | 10 000 |
| Utlopstid | Ingen utlop | 14-30 dager |
## Batch Job-oppretting og -overvaking
### Opprett batch job
```python
# Opprett batch job
batch_response = client.batches.create(
input_file_id=file_response.id,
endpoint="/v1/chat/completions",
completion_window="24h",
extra_body={
"output_expires_after": {
"seconds": 1209600,
"anchor": "created_at"
}
}
)
print(f"Batch-ID: {batch_response.id}")
print(f"Status: {batch_response.status}")
```
### Overvak batch-status
```python
import time
def monitor_batch(client, batch_id: str, poll_interval: int = 60):
"""Overvak batch job til den er ferdig."""
while True:
batch = client.batches.retrieve(batch_id)
print(f"Status: {batch.status}")
print(f" Requests total: {batch.request_counts.total}")
print(f" Completed: {batch.request_counts.completed}")
print(f" Failed: {batch.request_counts.failed}")
if batch.status in ("completed", "failed", "cancelled", "expired"):
return batch
time.sleep(poll_interval)
# Overvak med 60 sekunders intervall
final_batch = monitor_batch(client, batch_response.id)
```
### Batch-statusflyten
```
validating -> in_progress -> completed
-> failed
-> cancelled (manuelt)
-> expired (sjelden)
```
### Hent resultater
```python
if final_batch.status == "completed":
# Hent resultatfil
output_file_id = final_batch.output_file_id
result_content = client.files.content(output_file_id)
# Parse resultater
results = {}
for line in result_content.text.strip().split("\n"):
result = json.loads(line)
custom_id = result["custom_id"]
response_body = result["response"]["body"]
if result["response"]["status_code"] == 200:
content = response_body["choices"][0]["message"]["content"]
results[custom_id] = {
"status": "success",
"content": content,
"tokens": response_body["usage"]
}
else:
results[custom_id] = {
"status": "error",
"error": result.get("error", {})
}
# Sjekk feilfil
if final_batch.error_file_id:
error_content = client.files.content(final_batch.error_file_id)
errors = [json.loads(line) for line in error_content.text.strip().split("\n")]
print(f"Antall feil: {len(errors)}")
```
## Kostnadsberegning og besparelser
### Prissammenligning
| Modell | Standard input (per 1M tokens) | Batch input (per 1M tokens) | Besparelse |
|--------|-------------------------------|---------------------------|-----------|
| GPT-4o | $2.50 | $1.25 | 50% |
| GPT-4o mini | $0.15 | $0.075 | 50% |
| GPT-4.1 | $2.00 | $1.00 | 50% |
*Priser er illustrative og kan variere. Sjekk azure.microsoft.com/pricing for oppdaterte priser.*
### Kostnadsestimering
```python
def estimate_batch_cost(
num_requests: int,
avg_input_tokens: int,
avg_output_tokens: int,
model: str = "gpt-4o"
) -> dict:
"""Estimer kostnad for batch vs. standard prosessering."""
# Priser per 1M tokens (NOK, ca. kurs)
prices = {
"gpt-4o": {
"standard_input": 27.50, # NOK per 1M tokens
"standard_output": 110.00,
"batch_input": 13.75, # 50% rabatt
"batch_output": 55.00 # 50% rabatt
},
"gpt-4o-mini": {
"standard_input": 1.65,
"standard_output": 6.60,
"batch_input": 0.825,
"batch_output": 3.30
}
}
p = prices.get(model, prices["gpt-4o"])
total_input_tokens = num_requests * avg_input_tokens
total_output_tokens = num_requests * avg_output_tokens
standard_cost = (
(total_input_tokens / 1_000_000) * p["standard_input"] +
(total_output_tokens / 1_000_000) * p["standard_output"]
)
batch_cost = (
(total_input_tokens / 1_000_000) * p["batch_input"] +
(total_output_tokens / 1_000_000) * p["batch_output"]
)
return {
"num_requests": num_requests,
"total_tokens": total_input_tokens + total_output_tokens,
"standard_cost_nok": round(standard_cost, 2),
"batch_cost_nok": round(batch_cost, 2),
"savings_nok": round(standard_cost - batch_cost, 2),
"savings_percent": 50
}
# Eksempel: 50 000 henvendelser, 500 input tokens, 100 output tokens
estimate = estimate_batch_cost(50_000, 500, 100, "gpt-4o")
print(f"Standard: {estimate['standard_cost_nok']} NOK")
print(f"Batch: {estimate['batch_cost_nok']} NOK")
print(f"Besparelse: {estimate['savings_nok']} NOK ({estimate['savings_percent']}%)")
```
### Kostnadsoptimalisering for batch
| Strategi | Beskrivelse | Effekt |
|----------|-------------|--------|
| Bruk GPT-4o mini | For enklere oppgaver (klassifisering, utvinning) | 90%+ billigere enn GPT-4o |
| Lav max_tokens | Tilpass til forventet output | Unngaer overfakturering |
| Lav temperatur | Mer konsistent, potensielt kortere output | 5-15% |
| Strukturert output | JSON schema for forutsigbar lengde | 10-20% |
| Store batch-filer | Samle mange requests i en fil | Bedre throughput |
## Retry og feilhhandtering
### Koe-handtering ved kvotegrense
Nar batch jobs er for store for tilgjengelig kvote, bruk fail-fast med eksponentiell backoff:
```python
import time
from openai import AzureOpenAI, BadRequestError
def submit_batch_with_retry(
client: AzureOpenAI,
file_id: str,
max_retries: int = 10,
initial_wait: int = 300 # 5 minutter
):
"""Submit batch job med automatisk retry ved kvotegrense."""
for attempt in range(max_retries):
try:
batch = client.batches.create(
input_file_id=file_id,
endpoint="/v1/chat/completions",
completion_window="24h"
)
print(f"Batch opprettet: {batch.id}")
return batch
except BadRequestError as e:
if "enqueued token limit" in str(e).lower():
wait_time = initial_wait * (2 ** attempt)
print(f"Kvotegrense nadd. Venter {wait_time}s for forsok {attempt + 1}/{max_retries}")
time.sleep(wait_time)
else:
raise
raise Exception(f"Kunne ikke opprette batch etter {max_retries} forsok")
```
### Handtering av delvise feil
```python
def process_batch_results(client, batch_id: str) -> dict:
"""Prosesser batch-resultater og segreger suksess/feil."""
batch = client.batches.retrieve(batch_id)
results = {"success": [], "errors": [], "stats": {}}
# Hent suksessfulle resultater
if batch.output_file_id:
output = client.files.content(batch.output_file_id)
for line in output.text.strip().split("\n"):
result = json.loads(line)
if result["response"]["status_code"] == 200:
results["success"].append(result)
else:
results["errors"].append(result)
# Hent dedikerte feil
if batch.error_file_id:
errors = client.files.content(batch.error_file_id)
for line in errors.text.strip().split("\n"):
results["errors"].append(json.loads(line))
# Statistikk
results["stats"] = {
"total": batch.request_counts.total,
"completed": batch.request_counts.completed,
"failed": batch.request_counts.failed,
"success_rate": (
batch.request_counts.completed / batch.request_counts.total * 100
if batch.request_counts.total > 0 else 0
)
}
return results
def retry_failed_requests(
client: AzureOpenAI,
failed_results: list,
original_file_path: str,
model: str
) -> str:
"""Generer ny batch-fil fra feilede requests for retry."""
# Les originale requests for a finne matchende custom_ids
original_requests = {}
with open(original_file_path, "r") as f:
for line in f:
req = json.loads(line)
original_requests[req["custom_id"]] = req
# Generer retry-fil
retry_path = original_file_path.replace(".jsonl", "_retry.jsonl")
failed_ids = {r["custom_id"] for r in failed_results}
with open(retry_path, "w") as f:
for custom_id in failed_ids:
if custom_id in original_requests:
f.write(json.dumps(original_requests[custom_id]) + "\n")
print(f"Retry-fil generert: {retry_path} ({len(failed_ids)} requests)")
return retry_path
```
## Bruksomrader for norsk offentlig sektor
### Masseklassifisering av innbyggerhenvendelser
```python
# Eksempel: Klassifiser 100 000 henvendelser fra innbyggerportal
system_prompt = """Klassifiser henvendelsen. Svar med JSON:
{"kategori": "KLAGE|SPORSMAL|TILBAKEMELDING|SOKNAD",
"prioritet": "HOY|NORMAL|LAV",
"etat": "FORERKORT|KJORETOYREG|VEIPROSJEKT|ANNET"}"""
batch_file = generate_batch_file(
items=henvendelser,
system_prompt=system_prompt,
model="gpt-4o-mini-batch",
output_path="henvendelser_batch.jsonl",
max_tokens=100
)
```
### Dokumentanalyse og oppsummering
```python
# Eksempel: Oppsummer 10 000 hoeringsuttalelser
system_prompt = """Oppsummer hoeringsuttalelsen i 2-3 setninger.
Identifiser hovedstandpunkt og eventuelle konkrete forslag."""
batch_file = generate_batch_file(
items=hoeringsuttalelser,
system_prompt=system_prompt,
model="gpt-4o-batch",
output_path="hoering_batch.jsonl",
max_tokens=300
)
```
### Sprakvasking og oversettelse
```python
# Eksempel: Oversett 50 000 dokumentfragmenter til nynorsk
system_prompt = "Oversett teksten fra bokmaal til nynorsk. Bevar fagterminologi."
```
## Sjekkliste for batch-optimalisering
| Nr | Tiltak | Prioritet |
|----|--------|-----------|
| 1 | Bruk store filer (ikke mange sma) | Hoy |
| 2 | Sett utlop pa filer (expires_after) for a unnga 500-filgrensen | Hoy |
| 3 | Velg GPT-4o mini for enklere oppgaver | Hoy |
| 4 | Implementer retry med eksponentiell backoff | Hoy |
| 5 | Tilpass max_tokens til faktisk behov | Medium |
| 6 | Bruk strukturert output (JSON schema) | Medium |
| 7 | Overvak med polling (60s intervall) | Medium |
| 8 | Implementer retry for feilede requests | Medium |
| 9 | Bruk Blob Storage for filer over 200 MB | Ved behov |
| 10 | Sett opp alerting for batch completion | Anbefalt |
## For Cosmo
- **50% kostnadsreduksjon** gjor Batch API til forstevalgdet for all ikke-sanntids AI-prosessering i offentlig sektor. Masseklassifisering, dokumentanalyse og oversettelse bor alltid bruke batch.
- **Datasuverenitet:** Batch API prosesserer data i enhver Azure OpenAI-region for Global Batch. Bruk DataZoneBatch for a begrense til EU-regioner, eller Regional Batch for strengeste krav.
- **Completion window pa 24 timer** er et mal, ikke en garanti. Jobs som tar lengre tid utloper ikke, men du kan kansellere og fa resultater for fullfort arbeid.
- **Enqueued token-kvote** er separat fra online-kvote, sa batch-prosessering pavirker ikke sanntidstjenester. Ideelt for nattlige batch-kjoeringer.
- **Retry-pattern er kritisk:** Store batch jobs kan feile pa kvotegrenser. Implementer alltid fail-fast med eksponentiell backoff og retry av feilede requests.