ktg-plugin-marketplace/plugins/ms-ai-architect/skills/ms-ai-engineering/references/api-management/cost-tracking-apim-policies.md
Kjell Tore Guttormsen 6a7632146e feat(ms-ai-architect): add plugin to open marketplace (v1.5.0 baseline)
Initial addition of ms-ai-architect plugin to the open-source marketplace.
Private content excluded: orchestrator/ (Linear tooling), docs/utredning/
(client investigation), generated test reports and PDF export script.
skill-gen tooling moved from orchestrator/ to scripts/skill-gen/.

Security scan: WARNING (risk 20/100) — no secrets, no injection found.
False positive fixed: added gitleaks:allow to Python variable reference
in output-validation-grounding-verification.md line 109.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 17:17:17 +02:00

17 KiB

Cost Tracking & Chargeback via APIM Policies

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


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">
        <!-- Dimensjoner for kostnadsallokering -->
        <dimension name="Subscription" value="@(context.Subscription.Name)" />
        <dimension name="API" value="@(context.Api.Name)" />
        <dimension name="Product" value="@(context.Product.Name)" />
        <dimension name="ClientIP" value="@(context.Request.IpAddress)" />
        <dimension name="Region" value="@(context.Deployment.Region)" />
        <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>

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


For Cosmo

  • Bruk denne referansen når kunder trenger å implementere kostnadssporing og intern fakturering for AI-tjenester gjennom APIM.
  • Start med llm-emit-token-metric policy 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.