Il design del chargeback è deciso da un limite di Azure Monitor, non da una preferenza contabile: una metrica custom consente 10 chiavi di dimensione, API Management ne spende già 5 sui default, e le time series attive che una policy produce sono il prodotto dei valori unici di ogni dimensione che aggiungi. Tre dimensioni con dieci valori ciascuna sono mille time series, contro un tetto di 50.000 time series attive per region per subscription in una finestra di dodici ore. Metti uno user ID o un request ID su quella metrica e la telemetria della piattaforma stessa diventa l'outage. La Parte 4 ha completato i tre percorsi delle richieste. Questa parte risponde alla domanda per cui l'intera serie esiste: quale team ha speso cosa, e come il gateway impedisce a uno di loro di spendere la quota di tutti gli altri.

Due credenziali, due lavori diversi

API Management offre due meccanismi che vengono abitualmente trattati come alternative quando sono complementari.

Una subscription key identifica un consumer ai fini di product, quota e analytics. Arriva nell'header Ocp-Apim-Subscription-Key, o in un query parameter subscription-key che viene controllato solo quando l'header è assente, ed entrambi i nomi sono configurabili per API. Le key vengono emesse in coppia, così un'applicazione può passare dalla key A alla key B e rigenerare la A con il minimo disservizio.

Due cose sulle key vanno progettate invece che date per risolte. Primo, API Management non ha alcun ciclo di vita integrato per le subscription key: nessuna data di scadenza, nessuna rotazione automatica. La rotazione è un workflow che costruisci tu, con Azure PowerShell o gli SDK, e se non lo costruisci le key vivono per sempre. Secondo, la key viene passata al backend per default, dove può finire nei log di monitoring del backend e ovunque quei log vengano spediti. Un set-header alla fine della sezione inbound che la rimuove è una fix di due righe per un finding di credenziale nei log che altrimenti viene scoperto da un auditor.

Un JWT emesso da Microsoft Entra ID risponde a una domanda diversa: questo chiamante è autorizzato in questo momento. Scade nativamente, può essere revocato centralmente, e porta claim. Le applicazioni ne ottengono uno tramite il flow OAuth2 client credentials, e il gateway lo valida prima che qualsiasi altra cosa giri:

<validate-jwt header-name="Authorization" require-scheme="Bearer"
              require-expiration-time="true" require-signed-tokens="true"
              clock-skew="60"
              output-token-variable-name="jwt"
              failed-validation-httpcode="401"
              failed-validation-error-message="Invalid or missing token.">
  <openid-config url="https://login.microsoftonline.com/{{tenant-id}}/v2.0/.well-known/openid-configuration" />
  <audiences>
    <audience>api://genai-gateway</audience>
  </audiences>
  <required-claims>
    <claim name="roles" match="any">
      <value>model.invoke</value>
    </claim>
  </required-claims>
</validate-jwt>

output-token-variable-name è la parte che si guadagna il posto: il token validato finisce in una variabile di policy, così le policy successive possono leggerne un claim invece di fidarsi di un header che il chiamante controlla. E conta subito, perché la prossima decisione è quale valore fa da chiave alla quota.

La trappola dell'accesso anonimo

Vale la pena dirlo chiaramente perché la configurazione che lo produce sembra innocua. Che un'API possa essere chiamata senza alcuna subscription key dipende da due impostazioni indipendenti: se l'API stessa richiede una subscription, e se ogni product a cui è assegnata ne richiede una. Se l'API non richiede una subscription, le chiamate senza key sono permesse nel contesto dell'API anche quando una key con scope di product funzionerebbe comunque. Il gateway sta facendo esattamente ciò che gli è stato detto; nessuno gli ha detto l'intento.

Per un gateway davanti a capacità di modelli a pagamento, la regola è che ogni API richiede una subscription e ogni product richiede una subscription, e la policy validate-jwt gira comunque, così una chiamata non autenticata fallisce due volte. Anche gli scope delle key contano: una key con scope di servizio o su tutte le API apre ogni API dell'istanza, il che è giusto per il tooling del team di piattaforma e sbagliato per un consumer. I consumer ricevono key con scope di product.

La quota, e contro cosa contarla

La policy di token limit della Parte 1 prende una counter-key, e quella scelta è l'intero modello di tenancy. Usare come chiave l'ID della subscription dà un bucket per consumer, che è ciò di cui l'incidente della Parte 1 aveva bisogno: il batch job del marketing non può prosciugare la quota dell'assistente clienti perché sono subscription diverse con counter diversi.

<llm-token-limit counter-key="@(context.Subscription.Id)"
                 tokens-per-minute="{{tpm_tier}}"
                 estimate-prompt-tokens="true"
                 remaining-tokens-variable-name="remainingTokens"
                 remaining-tokens-header-name="x-remaining-tokens" />

Restituire il margine residuo in un header di risposta vale i due attributi in più. Un client che vede il proprio margine può rallentare prima di ricevere un 429, e una conversazione di supporto sul throttling parte da un numero che entrambe le parti vedono.

I product diventano i tier: un product interattivo con un limite al minuto alto e una quota giornaliera modesta, un product batch con l'inverso, e un product sandbox con limiti abbastanza bassi che un loop impazzito in un notebook sia una seccatura invece che un incidente. Un tenant è un'applicazione Entra ID più una o più subscription a product, provisionato dal Terraform del layer 30-tenants della Parte 2, mai da un ticket.

Il budget delle dimensioni

Ora il vincolo del primo paragrafo, enunciato come la regola di design che produce. Azure Monitor limita le metriche custom a 10 chiavi di dimensione, e API Management ne usa 5 per i default, tra cui Region, Service ID, Service Name e Service Type. Restano al massimo 5 dimensioni custom per policy. Il limite più grande è combinatorio: le time series attive sono il prodotto dei valori unici delle tue dimensioni nel periodo, quindi tre dimensioni con dieci valori ciascuna ne contribuiscono mille, e il tetto regionale è di 50.000 time series attive per subscription in dodici ore. Più istanze di API Management nella stessa region contribuiscono allo stesso totale regionale.

La conseguenza è una separazione netta che vale la pena scrivere nella documentazione della piattaforma stessa:

  • Le metriche portano solo dimensioni a bassa cardinalità. Tenant, alias di modello, environment. Tre dimensioni, insiemi di valori limitati, dashboard che caricano in fretta e alert che scattano sulla cosa giusta.
  • I log portano tutto il resto. Request ID, user ID se mai viene catturato, dimensioni di prompt e completion, latenza, quale backend ha servito la richiesta. Application Insights e Log Analytics sono il posto dove vive l'attribuzione per richiesta, e il job mensile di chargeback li interroga.
<llm-emit-token-metric namespace="llm-metrics">
  <dimension name="Tenant"      value="@(context.Subscription.Name)" />
  <dimension name="ModelAlias"  value="@(context.Request.MatchedParameters.GetValueOrDefault("model","unknown"))" />
  <dimension name="Environment" value="{{env}}" />
</llm-emit-token-metric>

Tre dimensioni, deliberatamente. La quarta che qualcuno chiederà è lo user ID, e la risposta è no: appartiene al record di log, dove la cardinalità costa storage invece del budget di metriche di un'intera region.

Un chargeback che regge una discussione con la finanza

Il job mensile è normale data engineering, e la sua credibilità poggia sull'essere esplicito sull'accuratezza più che sull'essere preciso. Le Parti 3 e 4 hanno stabilito tre classi, e il documento di chargeback le nomina:

  • Sincrono non in streaming: misurato dallo usage del modello stesso. Esatto.
  • Sincrono in streaming: token di prompt e completion stimati al gateway, perché lo streaming impone la stima a prescindere dalla configurazione della policy. Approssimato, con la deriva tracciata come metrica a sé.
  • Batch: esatto, dai conteggi di richieste del job stesso e dallo usage per risposta nel file di output, perché lo ha inviato il control plane.

Il job produce una cifra per tenant e una riga di riconciliazione: la somma della spesa attribuita contro il costo reale delle risorse nel periodo. Un residuo di pochi punti percentuali, stabile mese dopo mese, è il costo onesto della stima. Un residuo che cresce è un bug, il più delle volte un chiamante che ha trovato un percorso attorno al gateway. Pubblicare il residuo invece di nasconderlo è ciò che rende credibile il resto del numero, e fa anche da rilevatore per il percorso di bypass da cui la Parte 1 metteva in guardia.

Quando un team ha bisogno del proprio gateway

Occasionalmente i requisiti di un consumer non stanno nell'infrastruttura condivisa: un workload regolamentato che richiede il proprio isolamento di rete, o un team che deve gestire le proprie API senza toccare quelle degli altri. I workspace di API Management esistono per questo, e la scelta è tra il managed gateway di default del servizio, disponibile nei tier v2 senza costo aggiuntivo di gateway e con accesso alle capacità integrate, e un workspace gateway separato, disponibile su Basic v2, Standard v2, Premium e Premium v2, che compra un forte isolamento a runtime e scaling, hostname e configurazione di rete indipendenti al prezzo di costo extra, deployment più lungo e supporto in meno region.

Un dettaglio decide se la scelta è reversibile: la configurazione di virtual network di un workspace gateway può essere impostata solo alla creazione del gateway, e non può essere cambiata dopo. La sua configurazione di rete è anche indipendente da quella dell'istanza di API Management. Un workspace gateway creato senza isolamento, da un team che più avanti ne avrà bisogno, è un rebuild.

Modalità di fallimento da tenere d'occhio

  • Una dimensione ad alta cardinalità in una policy. Non darà errore. Consumerà in silenzio il budget di time series attive della region, e il primo sintomo è che le metriche custom di altri team smettono di comparire.
  • Key che non scadono mai. Non esiste scadenza o rotazione integrata. Senza un workflow di rotazione, la credenziale emessa per un'applicazione dismessa funziona ancora, e nulla lo segnala.
  • La subscription key nei log del backend. Il comportamento di default la inoltra. Rimuovila in inbound, e controlla una volta i log del backend per confermare invece di assumere.
  • Un'API che non richiede una subscription. Accesso anonimo nel contesto dell'API, raggiunto con una configurazione che si legge come permissiva invece che come aperta.
  • Un chargeback pubblicato senza il suo residuo. La prima volta che un product owner trova una discrepanza non dichiarata, ogni numero futuro diventa negoziabile.

Cosa eredita la Parte 6

Tenant con identità reali, quote che tengono, e una cifra mensile per team con un'accuratezza dichiarata. Il che prepara con precisione la prossima domanda: il token più economico è quello mai inviato, e il semantic caching è il primo meccanismo di questa serie che abbassa la bolletta invece di limitarsi ad attribuirla. Cambia anche, in silenzio, il significato dei numeri qui sopra, perché un cache hit è una richiesta che non è costata nulla e deve comunque comparire nel report di qualcuno.

Leggi questo dopo

Per il lato infrastruttura e piattaforma di come far girare tutto questo su scala, gli appunti tecnici sono su ercan.cloud, e l'hub è su ercanermis.com.

Riferimenti