Ordre 20260912T193441Z-7358817909. Steg 1 var ikke transformen, men å rette
roadmapens R13-gate og få den ratifisert. Gaten `grep -rl "Cosmo"
skills/*/references -> 0` var usann på to uavhengige måter:
1. Ordren fanget den første: 451 av forekomstene er Azure Cosmos DB, ekte
produktinnhold. Diskriminatoren er ikke bokstaven «s» — `Cosmos <norsk
substantiv>` er genitiv av personaen (`### Cosmos tonalitet`), mens
`Cosmos DB`/`CosmosClient`/`cosmos_ru` er produkt.
2. Denne økten fant den andre: 132 persona-forekomster ligger i prosa,
tabeller, dialog-replikker og proveniens-linjer. Heading-nøytralisering
kan ikke nå dem, så «0 persona» er uoppnåelig også under den ratifiserte
formen. Operatøren ratifiserte alternativ A: gaten speiler formen, og de
132 bokføres til R13b/R14.
Tre korreksjoner av premisser som sto i ordren og STATE:
«ca 320 produkt» -> 451 (case-sensitivt nett manglet 327 lowercase
TOC-ankre + 99 identifikatorer; sann nevner 1 638)
«169 headinger» -> 401. 169 var `^## For Cosmo`-prefikset (168) og var
internt inkonsistent med sin egen topp-variant (204)
«417 matcher ingen
populasjon» -> 417 er cosmo-headinger utenfor kodefences; briefens
nevner var reell hele tiden
Fence-bevissthet er målt skadelig, ikke nødvendig: begge toggle-regler er
gale på dette korpuset (naiv toggle skjuler en ekte heading i
chain-of-thought-prompting.md, CommonMark-regelen ubalanserer
service-level-documentation-dr.md). Fence-agnostisk deteksjon finner 401
heading-linjer i nøyaktig de samme 40 variantene som fence-bevisst finner
400 i — ingen kodeblokk-linje er byte-identisk til en persona-heading. Derfor
nøkles transformen på 40 enumererte heading-tekster og ignorerer fences. En
ukjent variant kaster; en slug-kollisjon kaster. Ingenting auto-fikses.
TOC-en regenereres ikke, den rettes kirurgisk: alle 327 persona-lenker hadde
lenketekst lik én av de 40 heading-tekstene og anker lik slugify av den
(327/327, 0 avvik), så heading og TOC-entry skrives i samme operasjon og
ingen mellomtilstand etterlater en død lenke.
Ratifisert målform: `For Cosmo`, `For Cosmo Skyberg` og `For arkitekten
(Cosmo)` konvergerer på `For arkitekten`. To filer kolliderte og er adjudisert
ved å lese dem, ikke ved regel.
Verifisering (alle 7 kriterier fra ordren):
G1 persona på heading-linjer 401 -> 0
G2 døde fragmentlenker 1 -> 1 (pre-eksisterende, unntatt)
G3 produkt-forekomster 451 -> 451; `Cosmos DB|Azure Cosmos` 308 = 308
de 3 kun-produkt-filene byte-identiske
nettet validert begge veier injisert persona feller G1; genitiv feller G1;
produkt-heading og de 3 filene passerer
hele diffen 802 heading-linjer + 654 TOC-linjer, ANNET = 0
linjeantall 728 lagt til = 728 slettet
suite 1120/1120 (1097 + 23 nye)
validate-plugin 250 PASS / 0 FAIL
stikkprøve 10 filer, alle 5 skills, inkl. de 3 mest
produkt-tunge (26/20/19) — kun heading+TOC
Utenfor scope, urørt: de 4 SKILL.md, de 23 commands, CLAUDE.md, README.md,
NOTICE.md, docs/ (alt R14).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18 KiB
Cost Tracking & Chargeback via APIM Policies
Last updated: 2026-06-19 Status: GA Category: API Management & AI Gateway Type: reference Source: https://learn.microsoft.com/azure/cost-management-billing/costs/overview-cost-management Verified: 2026-07-24 Verified by: judge-v3.1
Innhold
- Introduksjon
- Token Counting from Responses
- Model Routing Tracking
- Chargeback Tagging
- Azure Cost Management Integration
- Custom Metrics
- FinOps Integrasjon
- Referanser
- For arkitekten
Introduksjon
Når organisasjoner skalerer sin bruk av Azure OpenAI og andre AI-tjenester, blir kostnadssynlighet og tildeling av kostnader til riktig avdeling, prosjekt eller team en kritisk utfordring. Azure API Management (APIM) fungerer som et naturlig punkt for å samle inn kostnadsdata fra AI-modeller gjennom policyer som fanger token-bruk, modell-informasjon og forbruker-identitet. Denne informasjonen kan deretter brukes for intern fakturering (chargeback) og kostnadsoptimalisering.
For norsk offentlig sektor med stramme budsjetter og krav om transparens i ressursbruk er APIM-basert kostnadssporing spesielt verdifull. Mange statlige virksomheter deler AI-infrastruktur på tvers av avdelinger og prosjekter, og trenger mekanismer for å fordele kostnader rettferdig. Denne referansen dekker token-telling fra responser, modell-routing-tracking, chargeback-tagging, integrasjon med Azure Cost Management, og egendefinerte metriker.
APIM tilbyr innebygde policyer for å emittere token-metriker (llm-emit-token-metric) og logge LLM API-requests med fullstendig token-bruk. Kombinert med Azure Monitor, Application Insights og Cost Management gir dette en komplett pipeline for AI-kostnadssporing fra request til faktura.
Token Counting from Responses
Azure OpenAI Token Usage
Hver Azure OpenAI-respons inkluderer token-bruk i usage-feltet:
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"usage": {
"prompt_tokens": 150,
"completion_tokens": 250,
"total_tokens": 400
}
}
llm-emit-token-metric Policy
Den primære policyen for å emittere token-metriker til Azure Monitor:
<outbound>
<base />
<llm-emit-token-metric namespace="ai-cost-metrics">
<!-- Maks 5 custom dimensions per policy (Azure Monitor-grense) -->
<dimension name="Subscription" value="@(context.Subscription.Name)" />
<dimension name="Product" value="@(context.Product.Name)" />
<dimension name="UserId"
value="@(context.Request.Headers.GetValueOrDefault("X-User-Id", "unknown"))" />
<dimension name="Department"
value="@(context.Request.Headers.GetValueOrDefault("X-Department", "unassigned"))" />
<dimension name="CostCenter"
value="@(context.Request.Headers.GetValueOrDefault("X-Cost-Center", "default"))" />
</llm-emit-token-metric>
</outbound>
Viktig (MCP 2026-06): llm-emit-token-metric tillater maks 5 custom dimensions per policy (Azure Monitor-grense — 5 default-dimensjoner brukes allerede av tjenesten; eksempelet over er derfor trimmet fra 8 til 5). Flere dimensjoner gir policy-feil. Policyen konfigureres i inbound-seksjonen og virker for OpenAI Chat Completions/Responses, Anthropic Messages API (API Management v2-tiers) og Google Vertex AI. Token-kategoriene inkluderer nå (preview) også cached-, reasoning- og thinking-tokens i tillegg til prompt/completion/total. Sett include_usage=true ved streaming, ellers blir token-tellingen unøyaktig.
Token-typer og Kostnader
| Token-type | Beskrivelse | Kostnadsandel |
|---|---|---|
| Prompt tokens | Input-tokens (brukerens melding + system prompt) | Typisk 30-50% av kostnad |
| Completion tokens | Output-tokens (modellens svar) | Typisk 50-70% av kostnad |
| Cached tokens | Tokens fra prompt caching | Rabattert (opptil 50%) |
| Total tokens | Sum av prompt + completion | Grunnlag for fakturering |
Kostnadsberegning per Request
<outbound>
<base />
<!-- Beregn estimert kostnad per request -->
<set-variable name="estimated-cost-nok" value="@{
// Eksempel: GPT-4o priser (tilpasses faktiske priser)
var promptRate = 0.025m; // kr per 1000 prompt tokens
var completionRate = 0.10m; // kr per 1000 completion tokens
var body = context.Response.Body.As<JObject>(preserveContent: true);
var usage = body?["usage"];
if (usage == null) return "0";
var promptTokens = usage["prompt_tokens"]?.Value<decimal>() ?? 0;
var completionTokens = usage["completion_tokens"]?.Value<decimal>() ?? 0;
var cost = (promptTokens / 1000m * promptRate) +
(completionTokens / 1000m * completionRate);
return cost.ToString("F4");
}" />
<!-- Legg til kostnadsinfo i response header -->
<set-header name="X-Estimated-Cost-NOK" exists-action="override">
<value>@((string)context.Variables["estimated-cost-nok"])</value>
</set-header>
</outbound>
Model Routing Tracking
Spore Hvilken Modell som Brukes
Når backend pools med forskjellige modeller brukes, er det viktig å spore hvilken modell og deployment som faktisk betjener requesten:
<outbound>
<base />
<!-- Ekstraher modell-info fra respons -->
<set-variable name="model-used" value="@{
var body = context.Response.Body.As<JObject>(preserveContent: true);
return body?["model"]?.ToString() ?? "unknown";
}" />
<set-variable name="deployment-id" value="@{
return context.Request.MatchedParameters.GetValueOrDefault("deployment-id", "unknown");
}" />
<!-- Emit metrikker med modell-dimensjon -->
<llm-emit-token-metric namespace="ai-model-metrics">
<dimension name="Model" value="@((string)context.Variables["model-used"])" />
<dimension name="DeploymentId" value="@((string)context.Variables["deployment-id"])" />
<dimension name="Backend" value="@(context.Request.Url.Host)" />
<dimension name="DeploymentType"
value="@(context.Request.Url.Host.Contains("ptu") ? "PTU" : "PayGo")" />
</llm-emit-token-metric>
</outbound>
Modell-pris-mapping
| Modell | Prompt (kr/1K tokens) | Completion (kr/1K tokens) | Type |
|---|---|---|---|
| GPT-4o | 0.025 | 0.10 | Standard |
| GPT-4o-mini | 0.0015 | 0.006 | Standard |
| GPT-4 Turbo | 0.10 | 0.30 | Standard |
| text-embedding-ada-002 | 0.001 | N/A | Embedding |
| text-embedding-3-large | 0.0013 | N/A | Embedding |
| PTU (alle modeller) | Fast pris/time | Fast pris/time | Provisioned |
Merk: Priser varierer og bør oppdateres jevnlig. PTU faktureres per time uavhengig av faktisk bruk.
Chargeback Tagging
Implementere Chargeback-modell
En effektiv chargeback-modell krever at hver AI-request tagges med identifiserbar informasjon:
<inbound>
<base />
<!-- Ekstraher chargeback-info fra JWT token -->
<set-variable name="department" value="@{
var jwt = context.Request.Headers.GetValueOrDefault("Authorization", "")
.Replace("Bearer ", "");
if (string.IsNullOrEmpty(jwt)) return "unknown";
var token = jwt.AsJwt();
return token?.Claims.GetValueOrDefault("department", "unassigned");
}" />
<set-variable name="cost-center" value="@{
var jwt = context.Request.Headers.GetValueOrDefault("Authorization", "")
.Replace("Bearer ", "");
if (string.IsNullOrEmpty(jwt)) return "default";
var token = jwt.AsJwt();
return token?.Claims.GetValueOrDefault("cost_center", "default");
}" />
<set-variable name="project-code" value="@{
return context.Request.Headers.GetValueOrDefault("X-Project-Code", "general");
}" />
</inbound>
<outbound>
<base />
<!-- Emit chargeback-metriker -->
<llm-emit-token-metric namespace="chargeback-metrics">
<dimension name="Department" value="@((string)context.Variables["department"])" />
<dimension name="CostCenter" value="@((string)context.Variables["cost-center"])" />
<dimension name="ProjectCode" value="@((string)context.Variables["project-code"])" />
<dimension name="Subscription" value="@(context.Subscription.Name)" />
<dimension name="Model" value="@{
var body = context.Response.Body.As<JObject>(preserveContent: true);
return body?["model"]?.ToString() ?? "unknown";
}" />
</llm-emit-token-metric>
<!-- Legg til chargeback-headers i respons -->
<set-header name="X-Chargeback-Department" exists-action="override">
<value>@((string)context.Variables["department"])</value>
</set-header>
<set-header name="X-Chargeback-Tokens" exists-action="override">
<value>@{
var body = context.Response.Body.As<JObject>(preserveContent: true);
return body?["usage"]?["total_tokens"]?.ToString() ?? "0";
}</value>
</set-header>
</outbound>
APIM Products for Chargeback
Bruk APIM Products for å gruppere API-tilgang per avdeling:
| Product | Beskrivelse | Rate Limit | Chargeback |
|---|---|---|---|
| AI-Standard | Standard AI-tilgang | 10K TPM | Avdelingsbudsjett |
| AI-Premium | Utvidet AI-tilgang | 50K TPM | Prosjektbudsjett |
| AI-Unlimited | Full tilgang (admin) | Ubegrenset | Sentralt budsjett |
<!-- Product-basert rate limiting -->
<inbound>
<base />
<choose>
<when condition="@(context.Product.Name == "AI-Standard")">
<llm-token-limit counter-key="@(context.Subscription.Id)"
tokens-per-minute="10000" />
</when>
<when condition="@(context.Product.Name == "AI-Premium")">
<llm-token-limit counter-key="@(context.Subscription.Id)"
tokens-per-minute="50000" />
</when>
</choose>
</inbound>
Azure Cost Management Integration
Log Analytics for Kostnadsdata
Token-bruk logges til Azure Monitor via LLM API-logging:
APIM → Diagnostic Settings → "Logs related to generative AI gateway"
→ Log Analytics Workspace → ApiManagementGatewayLlmLog
KQL Query: Daglig Kostnad per Avdeling
ApiManagementGatewayLlmLog
| where TimeGenerated > ago(30d)
| extend Department = tostring(CustomDimensions["Department"])
| extend Model = tostring(ModelDeployment)
| extend PromptTokens = toint(PromptTokens)
| extend CompletionTokens = toint(CompletionTokens)
// Pris-mapping (oppdater etter faktiske priser)
| extend PromptCostNOK = case(
Model contains "gpt-4o-mini", PromptTokens * 0.0000015,
Model contains "gpt-4o", PromptTokens * 0.000025,
Model contains "gpt-4", PromptTokens * 0.0001,
PromptTokens * 0.00001 // Default
)
| extend CompletionCostNOK = case(
Model contains "gpt-4o-mini", CompletionTokens * 0.000006,
Model contains "gpt-4o", CompletionTokens * 0.0001,
Model contains "gpt-4", CompletionTokens * 0.0003,
CompletionTokens * 0.00003 // Default
)
| extend TotalCostNOK = PromptCostNOK + CompletionCostNOK
| summarize
DailyCostNOK = sum(TotalCostNOK),
TotalTokens = sum(toint(TotalTokens)),
RequestCount = count()
by Department, bin(TimeGenerated, 1d)
| order by TimeGenerated desc, DailyCostNOK desc
KQL Query: Månedlig Chargeback-rapport
ApiManagementGatewayLlmLog
| where TimeGenerated > startofmonth(ago(0d))
| extend CostCenter = tostring(CustomDimensions["CostCenter"])
| extend Department = tostring(CustomDimensions["Department"])
| extend Model = tostring(ModelDeployment)
| summarize
TotalPromptTokens = sum(toint(PromptTokens)),
TotalCompletionTokens = sum(toint(CompletionTokens)),
TotalTokens = sum(toint(TotalTokens)),
RequestCount = count(),
UniqueUsers = dcount(tostring(CustomDimensions["UserId"]))
by CostCenter, Department, Model
| extend EstimatedCostNOK =
TotalPromptTokens * 0.000025 + TotalCompletionTokens * 0.0001
| order by EstimatedCostNOK desc
Azure Workbook for Kostnadsdashboard
APIM tilbyr et innebygd Analytics-dashboard for LLM-APIer:
1. APIM → Monitoring → Analytics → Language models
2. Viser: Token consumption, Request count, Modell-fordeling
3. Filtrer etter tidsperiode og API
For egendefinert dashboard:
1. Azure Monitor → Workbooks → New
2. Legg til KQL-queries for chargeback
3. Visualiser med tabeller, grafer, kart
4. Del med stakeholders via Azure RBAC
Custom Metrics
Emit Custom Metriker med Policy Expressions
<outbound>
<base />
<!-- Emit custom metrikk for estimert kostnad -->
<emit-metric name="ai-estimated-cost"
value="@{
var body = context.Response.Body.As<JObject>(preserveContent: true);
var usage = body?["usage"];
if (usage == null) return 0d;
var prompt = usage["prompt_tokens"]?.Value<double>() ?? 0;
var completion = usage["completion_tokens"]?.Value<double>() ?? 0;
return (prompt * 0.000025) + (completion * 0.0001);
}"
namespace="ai-cost">
<dimension name="Department"
value="@((string)context.Variables.GetValueOrDefault("department", "unknown"))" />
<dimension name="CostCenter"
value="@((string)context.Variables.GetValueOrDefault("cost-center", "default"))" />
</emit-metric>
<!-- Standard token-metriker -->
<llm-emit-token-metric namespace="ai-tokens">
<dimension name="Department"
value="@((string)context.Variables.GetValueOrDefault("department", "unknown"))" />
<dimension name="Model" value="@{
var body = context.Response.Body.As<JObject>(preserveContent: true);
return body?["model"]?.ToString() ?? "unknown";
}" />
</llm-emit-token-metric>
</outbound>
Azure Monitor Alerts for Kostnadsoverskridelse
resource costAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: 'ai-cost-threshold-alert'
location: 'global'
properties: {
severity: 2
evaluationFrequency: 'PT1H'
windowSize: 'PT24H'
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: [
{
name: 'DailyTokenBudgetExceeded'
metricNamespace: 'ai-tokens'
metricName: 'Total Tokens'
operator: 'GreaterThan'
threshold: 1000000 // 1M tokens per dag
timeAggregation: 'Total'
}
]
}
actions: [
{ actionGroupId: actionGroup.id }
]
}
}
Eksport til Power BI for Rapportering
// Eksporter data til Power BI via Log Analytics
ApiManagementGatewayLlmLog
| where TimeGenerated > ago(90d)
| project
Timestamp = TimeGenerated,
Department = tostring(CustomDimensions["Department"]),
CostCenter = tostring(CustomDimensions["CostCenter"]),
Model = ModelDeployment,
PromptTokens = toint(PromptTokens),
CompletionTokens = toint(CompletionTokens),
TotalTokens = toint(TotalTokens),
SubscriptionName = tostring(CustomDimensions["Subscription"])
FinOps Integrasjon
Azure Cost Management Tags
Kombiner APIM-metriker med Azure resource tags for helhetlig kostnadsbilde:
| Tag | Formål | Eksempel |
|---|---|---|
Department |
Avdelingstilhørighet | "IT-seksjonen" |
CostCenter |
Kostnadssenter-kode | "KS-4210" |
Environment |
Miljø | "production" |
Project |
Prosjektkode | "AI-chatbot-2026" |
Kostnadsmodeller for AI
| Modell | Fordeler | Ulemper |
|---|---|---|
| Per-token chargeback | Presis, rettferdig | Kompleks å administrere |
| Flat rate per avdeling | Enkelt, forutsigbart | Urettferdig for lavbrukere |
| Tier-basert (freemium) | Balansert, insentiverer effektivitet | Krever grensehåndtering |
| PTU-allokering | Fast kostnad, forutsigbart | Ingen fleksibilitet |
Referanser
- AI gateway in Azure API Management - Observability and governance — Oversikt over token-metriker
- llm-emit-token-metric policy — Policy-referanse for token-metriker
- Log token usage, prompts, and completions — LLM API-logging
- Plan and manage costs for API Management — APIM-kostnader i Cost Management
- Azure Cost Management overview — Helhetlig kostnadsadministrasjon
For arkitekten
- Bruk denne referansen når kunder trenger å implementere kostnadssporing og intern fakturering for AI-tjenester gjennom APIM.
- Start med
llm-emit-token-metricpolicy med Department- og CostCenter-dimensjoner — dette gir umiddelbar synlighet i token-bruk per avdeling. - For norsk offentlig sektor: Anbefal tier-basert chargeback-modell med APIM Products som mapper til avdelingsbudsjetter. Flat rate er for enkelt, per-token er for komplekst for de fleste.
- Husk at PTU-kostnader er faste per time — chargeback for PTU bør baseres på allokert kapasitet, ikke faktisk bruk.
- Kombiner APIM-metriker med Azure resource tags for å gi et helhetlig bilde i Cost Management. APIM-metriker alene viser kun token-bruk, ikke infrastrukturkostnader.