feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command
Add /ultraresearch-local for structured research combining local codebase analysis with external knowledge via parallel agent swarms. Produces research briefs with triangulation, confidence ratings, and source quality assessment. New command: /ultraresearch-local with modes --quick, --local, --external, --fg. New agents: research-orchestrator (opus), docs-researcher, community-researcher, security-researcher, contrarian-researcher, gemini-bridge (all sonnet). New template: research-brief-template.md. Integration: --research flag in /ultraplan-local accepts pre-built research briefs (up to 3), enriches the interview and exploration phases. Planning orchestrator cross-references brief findings during synthesis. Design principle: Context Engineering — right information to right agent at right time. Research briefs are structured artifacts in the pipeline: ultraresearch → brief → ultraplan --research → plan → ultraexecute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
baa2d0220b
488 changed files with 213221 additions and 0 deletions
|
|
@ -0,0 +1,443 @@
|
|||
# Token-Based Rate Limiting & Quota Policies
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** API Management & AI Gateway
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Token-basert rate limiting er den viktigste mekanismen for å kontrollere forbruk av AI-tjenester i Azure API Management. I motsetning til tradisjonell request-basert throttling, teller APIM faktisk antall tokens som konsumeres av hver LLM-forespørsel og håndhever grenser basert på dette. Dette er essensielt for norsk offentlig sektor der flere etater og prosjekter deler Azure OpenAI-ressurser og trenger presis kostnadskontroll.
|
||||
|
||||
APIM tilbyr to parallelle sett med token-policies: ett spesifikt for Azure OpenAI (`azure-openai-token-limit`) og ett generelt for alle LLM-er (`llm-token-limit`). Begge fungerer likt, men det generelle settet støtter også tredjeparts LLM-endepunkter som er kompatible med OpenAI API-formatet. For de fleste scenarier anbefales `llm-token-limit` da det gir størst fleksibilitet.
|
||||
|
||||
I tillegg til tokens per minutt (TPM) rate limits, støtter APIM token-kvoter over lengre perioder (time, dag, uke, måned, år). Kombinasjonen av rate limits og kvoter gir finkornet kontroll: rate limits beskytter mot plutselige spikes, mens kvoter sikrer rettferdig fordeling over tid.
|
||||
|
||||
---
|
||||
|
||||
## Token-telling i APIM
|
||||
|
||||
### Hvordan APIM teller tokens
|
||||
|
||||
APIM bruker to metoder for token-telling:
|
||||
|
||||
| Metode | Tidspunkt | Nøyaktighet | Konfigurasjon |
|
||||
|--------|-----------|-------------|---------------|
|
||||
| **Estimert (prompt)** | Før request sendes til backend | Omtrentlig, basert på tegnantall | `estimate-prompt-tokens="true"` |
|
||||
| **Faktisk (completion)** | Etter respons fra backend | Eksakt, fra `usage`-feltet i respons | Alltid aktiv for completions |
|
||||
|
||||
**Estimering av prompt tokens:**
|
||||
Når `estimate-prompt-tokens="true"` er satt, beregner APIM et estimat av prompt-tokens basert på innholdet i forespørselen. Dette muliggjør pre-validering: hvis estimatet allerede overskrider kvoten, avvises forespørselen umiddelbart uten å bruke backend-ressurser.
|
||||
|
||||
```
|
||||
Token-estimat = f(antall tegn i prompt, modelltype)
|
||||
```
|
||||
|
||||
> **Viktig:** Token-estimatet er en tilnærming. Det faktiske tokenforbruket kan avvike, spesielt for ikke-engelske tekster (norsk bruker typisk 20-40% flere tokens enn engelsk for tilsvarende tekst).
|
||||
|
||||
### Token-flyt i APIM
|
||||
|
||||
```
|
||||
1. Request mottas av APIM gateway
|
||||
2. IF estimate-prompt-tokens=true:
|
||||
Estimer prompt tokens
|
||||
IF estimat > gjenstående kvote:
|
||||
RETURNER 429 umiddelbart (ingen backend-kall)
|
||||
3. Send request til Azure OpenAI backend
|
||||
4. Motta respons med usage-data:
|
||||
{
|
||||
"usage": {
|
||||
"prompt_tokens": 127,
|
||||
"completion_tokens": 350,
|
||||
"total_tokens": 477
|
||||
}
|
||||
}
|
||||
5. Oppdater token-teller med faktiske verdier
|
||||
6. Emit metrikk til Application Insights
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Policy-referanse
|
||||
|
||||
### llm-token-limit (anbefalt for nye implementasjoner)
|
||||
|
||||
```xml
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
tokens-per-minute="50000"
|
||||
token-quota="1000000"
|
||||
token-quota-period="monthly"
|
||||
estimate-prompt-tokens="true"
|
||||
remaining-tokens-variable-name="remainingTokens"
|
||||
remaining-token-quota-variable-name="remainingQuota"
|
||||
tokens-consumed-variable-name="tokensConsumed">
|
||||
</llm-token-limit>
|
||||
```
|
||||
|
||||
**Attributter:**
|
||||
|
||||
| Attributt | Påkrevd | Beskrivelse | Eksempel |
|
||||
|-----------|---------|-------------|---------|
|
||||
| `counter-key` | Ja | Nøkkel for å identifisere konsumenten | `@(context.Subscription.Id)` |
|
||||
| `tokens-per-minute` | Ja | Maks tokens per minutt (TPM) | `50000` |
|
||||
| `token-quota` | Nei | Total tokenkvote for perioden | `1000000` |
|
||||
| `token-quota-period` | Nei | Kvoteperiode | `hourly`, `daily`, `weekly`, `monthly`, `yearly` |
|
||||
| `estimate-prompt-tokens` | Nei | Pre-estimer prompt tokens | `true` / `false` |
|
||||
| `remaining-tokens-variable-name` | Nei | Variabel for gjenstående TPM | `remainingTokens` |
|
||||
| `remaining-token-quota-variable-name` | Nei | Variabel for gjenstående kvote | `remainingQuota` |
|
||||
| `tokens-consumed-variable-name` | Nei | Variabel for brukte tokens | `tokensConsumed` |
|
||||
| `retry-after-variable-name` | Nei | Variabel for retry-after sekunder | `retryAfter` |
|
||||
|
||||
### azure-openai-token-limit (Azure OpenAI-spesifikk)
|
||||
|
||||
```xml
|
||||
<azure-openai-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
tokens-per-minute="50000"
|
||||
estimate-prompt-tokens="true"
|
||||
remaining-tokens-variable-name="remainingTokens">
|
||||
</azure-openai-token-limit>
|
||||
```
|
||||
|
||||
> **Merk:** `azure-openai-token-limit` støtter kun TPM rate limiting, ikke token-kvoter over lengre perioder. Bruk `llm-token-limit` for full kvotestøtte.
|
||||
|
||||
---
|
||||
|
||||
## Counter-key-strategier
|
||||
|
||||
Valg av `counter-key` bestemmer granulariteten av rate limiting:
|
||||
|
||||
### Strategi 1: Per subscription (standard)
|
||||
|
||||
```xml
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
tokens-per-minute="50000" />
|
||||
```
|
||||
|
||||
**Bruk:** Standard for de fleste scenarier. Hvert team/prosjekt får egen APIM subscription med dedikert kvote.
|
||||
|
||||
### Strategi 2: Per IP-adresse
|
||||
|
||||
```xml
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Request.IpAddress)"
|
||||
tokens-per-minute="10000" />
|
||||
```
|
||||
|
||||
**Bruk:** Beskyttelse mot individuelle klienter som overforbruker. Nyttig for interne applikasjoner.
|
||||
|
||||
### Strategi 3: Per avdeling/etat (custom header)
|
||||
|
||||
```xml
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Request.Headers.GetValueOrDefault("x-etat-id", "default"))"
|
||||
tokens-per-minute="100000"
|
||||
token-quota="5000000"
|
||||
token-quota-period="monthly" />
|
||||
```
|
||||
|
||||
**Bruk:** Offentlig sektor der flere etater deler infrastruktur. Krever at klienter sender header.
|
||||
|
||||
### Strategi 4: Per bruker (JWT claim)
|
||||
|
||||
```xml
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization", "").AsJwt()?.Claims["oid"]?.FirstOrDefault() ?? "anonymous")"
|
||||
tokens-per-minute="5000" />
|
||||
```
|
||||
|
||||
**Bruk:** Individuell brukerbegrensning. Krever JWT-token med brukeridentitet.
|
||||
|
||||
### Strategi 5: Kombinert (subscription + bruker)
|
||||
|
||||
```xml
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id + "-" + context.Request.Headers.GetValueOrDefault("x-user-id", "shared"))"
|
||||
tokens-per-minute="5000" />
|
||||
```
|
||||
|
||||
**Bruk:** Finkornet kontroll der hvert team har en total kvote, men individuelle brukere innen teamet også begrenses.
|
||||
|
||||
---
|
||||
|
||||
## Rate Limit-algoritmer
|
||||
|
||||
### Classic tiers: Sliding Window
|
||||
|
||||
I Classic tiers (Developer, Basic, Standard, Premium) bruker APIM en sliding window-algoritme:
|
||||
|
||||
```
|
||||
Tidslinje:
|
||||
[────────── 60 sek vindu ──────────]
|
||||
^-- request evalueres her
|
||||
|
||||
Tokens brukt i vinduet: 45 000 av 50 000 TPM
|
||||
Ny request med estimert 6 000 tokens → AVVIST (429)
|
||||
Ny request med estimert 4 000 tokens → GODKJENT
|
||||
```
|
||||
|
||||
**Egenskaper:**
|
||||
- Jevn fordeling over tid
|
||||
- Kan gi uventede 429-svar ved burst-trafikk
|
||||
- Teller akkumuleres over glidende 60-sekunders vindu
|
||||
|
||||
### V2 tiers: Token Bucket
|
||||
|
||||
V2 tiers (Basic v2, Standard v2, Premium v2) bruker en token bucket-algoritme:
|
||||
|
||||
```
|
||||
Bucket-kapasitet: 50 000 tokens
|
||||
Refill rate: 50 000 tokens / 60 sek = 833 tokens/sek
|
||||
|
||||
Tidspunkt 0: Bucket = 50 000 (full)
|
||||
Request A: -10 000 → Bucket = 40 000
|
||||
Request B: -15 000 → Bucket = 25 000
|
||||
... 10 sek ...
|
||||
Refill: +8 330 → Bucket = 33 330
|
||||
Request C: -35 000 → AVVIST (overstiger bucket)
|
||||
```
|
||||
|
||||
**Egenskaper:**
|
||||
- Tillater korte bursts opp til bucket-kapasiteten
|
||||
- Jevnere throttling-opplevelse
|
||||
- Mer effektiv for ujevn trafikk (typisk for AI-workloads)
|
||||
|
||||
---
|
||||
|
||||
## Kvoter over lengre perioder
|
||||
|
||||
### Kvote vs. Rate Limit
|
||||
|
||||
| Egenskap | Rate Limit (TPM) | Kvote |
|
||||
|----------|-------------------|-------|
|
||||
| **Tidshorisont** | Per minutt | Time, dag, uke, måned, år |
|
||||
| **Formål** | Beskytt mot spikes | Rettferdig fordeling over tid |
|
||||
| **Counter scope** | Per gateway-instans (regional) | Globalt (på tvers av regioner) |
|
||||
| **HTTP-kode ved overskridelse** | 429 Too Many Requests | 403 Forbidden |
|
||||
| **Retry-After header** | Ja | Ja |
|
||||
|
||||
### Eksempel: Månedlig kvote med daglig rate limit
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<!-- Rate limit: 50 000 TPM -->
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
tokens-per-minute="50000"
|
||||
estimate-prompt-tokens="true" />
|
||||
|
||||
<!-- Kvote: 2 000 000 tokens per måned -->
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
token-quota="2000000"
|
||||
token-quota-period="monthly"
|
||||
tokens-per-minute="999999999"
|
||||
estimate-prompt-tokens="true" />
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### Kvoteberegning for offentlig sektor
|
||||
|
||||
Eksempel for en etat med 50 ansatte som bruker AI-tjenester:
|
||||
|
||||
| Brukerkategori | Antall | TPM per bruker | Månedlig kvote per bruker | Total månedlig |
|
||||
|----------------|--------|----------------|---------------------------|----------------|
|
||||
| Power users | 5 | 10 000 | 500 000 | 2 500 000 |
|
||||
| Standard users | 30 | 3 000 | 100 000 | 3 000 000 |
|
||||
| Light users | 15 | 1 000 | 30 000 | 450 000 |
|
||||
| **Totalt** | **50** | - | - | **5 950 000** |
|
||||
|
||||
**Buffer-anbefaling:** Legg til 20-30% buffer for uforutsette topper.
|
||||
|
||||
---
|
||||
|
||||
## Multi-region-hensyn
|
||||
|
||||
### Rate limits er regionale
|
||||
|
||||
```
|
||||
APIM Gateway (Norway East) → Rate limit counter: 50 000 TPM
|
||||
APIM Gateway (Sweden Central) → Rate limit counter: 50 000 TPM
|
||||
(separate tellere!)
|
||||
```
|
||||
|
||||
**Viktig:** I multi-region deployments har hver regional gateway sin egen rate limit-teller. En konsument kan potensielt bruke 50 000 TPM i Norway East OG 50 000 TPM i Sweden Central = 100 000 TPM totalt.
|
||||
|
||||
### Kvoter er globale
|
||||
|
||||
```
|
||||
APIM Instance (global)
|
||||
├── Gateway (Norway East) ─┐
|
||||
└── Gateway (Sweden Central) ─┤── Delt kvote-teller: 2 000 000/mnd
|
||||
└── (synkronisert globalt)
|
||||
```
|
||||
|
||||
**Anbefaling for offentlig sektor:** Bruk kvoter for total kostnadskontroll, og rate limits for burst-beskyttelse. Vurder å justere regionale rate limits basert på forventet trafikkfordeling.
|
||||
|
||||
---
|
||||
|
||||
## Feilhåndtering og respons-headers
|
||||
|
||||
### HTTP-responser ved overskridelse
|
||||
|
||||
| Scenario | HTTP-kode | Response header | Body |
|
||||
|----------|-----------|-----------------|------|
|
||||
| TPM rate limit nådd | 429 | `Retry-After: <sekunder>` | Feilmelding med gjenstående tokens |
|
||||
| Kvote brukt opp | 403 | `Retry-After: <sekunder til reset>` | Feilmelding med kvoteinformasjon |
|
||||
| Prompt estimat overskrider | 429 | `Retry-After: <sekunder>` | Avvist uten backend-kall |
|
||||
|
||||
### Bruk av context-variabler
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
tokens-per-minute="50000"
|
||||
remaining-tokens-variable-name="remainingTokens"
|
||||
tokens-consumed-variable-name="tokensConsumed"
|
||||
retry-after-variable-name="retryAfter" />
|
||||
</inbound>
|
||||
|
||||
<outbound>
|
||||
<!-- Inkluder gjenstående tokens i respons-header -->
|
||||
<set-header name="X-Remaining-Tokens" exists-action="override">
|
||||
<value>@(context.Variables.GetValueOrDefault<int>("remainingTokens").ToString())</value>
|
||||
</set-header>
|
||||
<set-header name="X-Tokens-Consumed" exists-action="override">
|
||||
<value>@(context.Variables.GetValueOrDefault<int>("tokensConsumed").ToString())</value>
|
||||
</set-header>
|
||||
</outbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Burst Allowances og Concurrency Control
|
||||
|
||||
### Kombinere token limit med concurrency limit
|
||||
|
||||
For å beskytte mot mange samtidige store requests:
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<!-- Maks 10 samtidige requests per subscription -->
|
||||
<limit-concurrency key="@(context.Subscription.Id)" max-count="10">
|
||||
<!-- Token rate limiting innenfor concurrency-grensen -->
|
||||
<llm-token-limit
|
||||
counter-key="@(context.Subscription.Id)"
|
||||
tokens-per-minute="50000"
|
||||
estimate-prompt-tokens="true" />
|
||||
</limit-concurrency>
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### Burst-håndtering med Token Bucket (V2 tiers)
|
||||
|
||||
Token bucket-algoritmen i V2 tiers tillater naturlig burst-kapasitet:
|
||||
|
||||
| Konfigurasjon | Effektiv burst | Sustained rate |
|
||||
|---------------|----------------|----------------|
|
||||
| TPM=50 000 | Opp til 50 000 tokens i enkelt-request | ~833 tokens/sek |
|
||||
| TPM=100 000 | Opp til 100 000 tokens i enkelt-request | ~1 667 tokens/sek |
|
||||
| TPM=200 000 | Opp til 200 000 tokens i enkelt-request | ~3 333 tokens/sek |
|
||||
|
||||
---
|
||||
|
||||
## Monitorering av token-forbruk
|
||||
|
||||
### Token-metrikk policy
|
||||
|
||||
```xml
|
||||
<outbound>
|
||||
<llm-emit-token-metric namespace="ai-token-usage">
|
||||
<dimension name="Subscription" value="@(context.Subscription.Id)" />
|
||||
<dimension name="Etat" value="@(context.Request.Headers.GetValueOrDefault("x-etat", "ukjent"))" />
|
||||
<dimension name="Model" value="@(context.Request.Headers.GetValueOrDefault("x-model", "default"))" />
|
||||
</llm-emit-token-metric>
|
||||
</outbound>
|
||||
```
|
||||
|
||||
### KQL-spørring for token-forbruk
|
||||
|
||||
```kusto
|
||||
customMetrics
|
||||
| where name == "Total Tokens"
|
||||
| extend etat = tostring(customDimensions["Etat"])
|
||||
| summarize TotalTokens = sum(value) by etat, bin(timestamp, 1h)
|
||||
| order by timestamp desc
|
||||
| render timechart
|
||||
```
|
||||
|
||||
### Azure Monitor Alert for kvote-overskridelse
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Microsoft.Insights/metricAlerts",
|
||||
"properties": {
|
||||
"criteria": {
|
||||
"metricName": "Total Tokens",
|
||||
"metricNamespace": "ai-token-usage",
|
||||
"operator": "GreaterThan",
|
||||
"threshold": 4000000,
|
||||
"timeAggregation": "Total",
|
||||
"dimensions": [
|
||||
{
|
||||
"name": "Subscription",
|
||||
"operator": "Include",
|
||||
"values": ["*"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"windowSize": "P1D",
|
||||
"evaluationFrequency": "PT1H"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tier-kompatibilitet
|
||||
|
||||
| Policy | Classic | V2 | Consumption | Self-hosted | Workspace |
|
||||
|--------|---------|----|-----------|----------- |-----------|
|
||||
| `azure-openai-token-limit` | Ja | Ja | Nei | Ja | Ja |
|
||||
| `llm-token-limit` | Ja | Ja | Nei | Ja | Ja |
|
||||
| `azure-openai-emit-token-metric` | Ja | Ja | Nei | Ja | Ja |
|
||||
| `llm-emit-token-metric` | Ja | Ja | Nei | Ja | Ja |
|
||||
| `rate-limit-by-key` | Ja | Ja | Nei | Ja | Ja |
|
||||
| `quota-by-key` | Ja | Nei | Nei | Ja | Ja |
|
||||
|
||||
> **Merk:** Token-policies er IKKE tilgjengelige i Consumption tier. For AI-workloads, bruk minimum Basic v2 eller Standard v2.
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Anbefalt oppsett for norsk offentlig sektor
|
||||
|
||||
1. **Bruk `llm-token-limit`** fremfor `azure-openai-token-limit` for fremtidig fleksibilitet
|
||||
2. **Aktiver `estimate-prompt-tokens`** for å avvise for store requests tidlig
|
||||
3. **Kombiner TPM rate limit med månedlig kvote** for dobbel beskyttelse
|
||||
4. **Bruk subscription-basert counter-key** som primær granularitet
|
||||
5. **Legg til custom header-dimensjoner** for kostnadsrapportering per etat/prosjekt
|
||||
6. **Sett opp Azure Monitor Alerts** ved 80% kvotebruk
|
||||
7. **Dokumenter kvoteallokeringer** i tjenestekataloger og SLA-er
|
||||
8. **Test med GenAI Gateway Toolkit** for å verifisere policy-oppførsel under last
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- Token rate limiting er den viktigste AI gateway-policyen -- alltid start her når du setter opp APIM for Azure OpenAI. Bruk `llm-token-limit` som standard, med `estimate-prompt-tokens="true"` for tidlig avvisning.
|
||||
- Counter-key-strategien bestemmer granulariteten: subscription-basert for team-nivå, custom headers for etat/prosjekt, JWT claims for bruker-nivå. For offentlig sektor anbefales subscription per team + custom header for kostnadsrapportering.
|
||||
- V2 tiers bruker token bucket-algoritme som håndterer bursts bedre enn sliding window i Classic tiers -- anbefal Standard v2 for nye deployments.
|
||||
- Rate limits er regionale (per gateway-instans), men kvoter er globale. I multi-region oppsett må du dimensjonere rate limits per region, men bruke kvoter for total kostnadskontroll.
|
||||
- Kombiner alltid `llm-token-limit` med `llm-emit-token-metric` for full observabilitet, og sett opp alerts ved 80% kvotebruk for proaktiv kapasitetsstyring.
|
||||
Loading…
Add table
Add a link
Reference in a new issue