One Door to the Models, Parte 3: L'Astrazione dei Provider e lo Streaming
Con stream impostato a true il conteggio dei token diventa stima. Con un WebSocket il load balancing smette di esistere. La Parte 3 mappa garanzie e trasporti.

Le garanzie del gateway non sono proprietà del gateway. Sono proprietà del trasporto che il chiamante ha scelto, e si indeboliscono a ogni passo verso il real-time. Una normale chiamata request-response viene misurata dal blocco usage che il modello ha effettivamente restituito. La stessa chiamata con stream: true ha i prompt token stimati qualunque cosa dica la policy, e anche i completion token stimati. Una connessione WebSocket non può essere bilanciata tra i backend, perché una volta stabilita resta fissata uno-a-uno tra client e backend. La Parte 2 ha costruito l'infrastruttura. Questa parte ci mette sopra un'API, e la versione onesta di quell'API è una che dice a ogni chiamante quali garanzie ha appena ceduto.
Cosa dovrebbe dover sapere un team consumer
Esattamente tre cose: una base URL, una client credential e un nome di modello che significhi qualcosa per loro invece che per Azure. Niente su deployment, region, provider, o su quale di questi è cambiato martedì scorso. Questo è l'intero contratto, e tutto in questa parte esiste per mantenerlo vero mentre le cose dietro si muovono.
La unified model API di API Management è costruita esattamente per questo. I client parlano il formato OpenAI Chat Completions, e il gateway traduce verso il formato di backend che il modello di destinazione usa davvero. Supporta due formati di backend API, OpenAI Chat Completions e la Anthropic Messages API, e fa quattro cose che vale la pena nominare separatamente: standardizza il formato lato client indipendentemente dai backend, applica un unico set di policy di governance tra i provider, configura il failover tra provider, e disaccoppia i nomi dei modelli lato client dai nomi dei modelli di backend usando gli alias.
L'alias è la parte che conta di più qui, ed è la continuazione diretta della Parte 2. Un model deployment fissato a gpt-4.1 versione 2025-04-14 con NoAutoUpgrade è utile solo se nessuna applicazione lo nomina mai. Le applicazioni chiamano chat-default. L'alias mappa chat-default su un deployment, e il giorno in cui quella mappatura cambia è una modifica alla configurazione del gateway revisionata in una pull request, non un rilascio coordinato tra cinque team.
Due avvertenze stanno meglio qui che in un ticket di supporto. La unified model API è in preview e in fase di rollout; nei tier classici l'accesso anticipato passa dal canale AI Gateway Early release, che è un'impostazione di aggiornamento per istanza. E i suoi due formati di backend supportati non coprono tutto quello che l'azienda fa girare. I deployment Mistral e Llama in Microsoft Foundry si raggiungono attraverso la loro superficie chat completions compatibile con OpenAI, che la unified API può esporre come backend in formato OpenAI, ma è un percorso di integrazione diverso dalla traduzione nativa Anthropic, e vale la pena scrivere quali modelli arrivano per quale via prima che qualcuno debugghi un mismatch di formato alle 2 di notte.
Routing: prima i gruppi di priorità, poi i pesi
Dietro un alias c'è un backend pool, non un backend. I pool di API Management supportano distribuzione round-robin, pesata, per priorità e session-aware, e il pattern utile per il traffico dei modelli combina le prime due: un gruppo di priorità di deployment Provisioned Throughput che dovrebbe assorbire tutto quello che può, e un gruppo a priorità inferiore di deployment pay-as-you-go che vede traffico solo quando il primo gruppo non è disponibile. All'interno di un gruppo, i pesi dividono il carico.
I pool si configurano dal portale, dalla REST API, o da un template ARM o Bicep. La Parte 2 ha scelto Terraform, e questo è precisamente il caso che quella parte descriveva come legittimo per la via di fuga azapi: una risorsa, un corpo ARM grezzo, accanto a risorse azurerm che referenzia per ID.
resource "azapi_resource" "chat_pool" {
type = "Microsoft.ApiManagement/service/backends@2023-09-01-preview"
name = "chat-pool"
parent_id = azurerm_api_management.gw.id
# azurerm has no backend-pool schema yet. Revisit when it does.
body = {
properties = {
description = "PTU first, PAYG overflow"
type = "Pool"
pool = {
services = [
{ id = azapi_resource.be_ptu_west.id, priority = "1", weight = "1" },
{ id = azapi_resource.be_payg_west.id, priority = "2", weight = "3" },
{ id = azapi_resource.be_payg_north.id, priority = "2", weight = "1" },
]
}
}
}
}
Priorità 1 è il deployment PTU: è già pagato a ore, quindi ogni token che riesce a servire è un token che non costa nulla in più. La priorità 2 riceve traffico solo quando la priorità 1 non può prenderlo, e il peso 3 a 1 tra le due region riflette la quota, non una preferenza. È una topologia di costo espressa come routing, il genere di cosa invisibile in un diagramma di architettura e ovvia su una fattura.
Il circuit breaker, e quanto fidarsene
Un membro del pool che sta fallendo dovrebbe smettere di ricevere traffico senza che un umano lo decida. API Management espone un circuit breaker come proprietà del backend: le regole definiscono un conteggio o una percentuale di errori in un intervallo e i range di status code che contano come errore, e quando il breaker scatta il gateway smette di chiamare quel backend per la durata del trip e restituisce 503 Service Unavailable al client. Se il backend ha inviato un header Retry-After, il breaker può accettare quel valore e aspettare esattamente quel tempo, che per un endpoint di modello sottoposto a rate limit è molto meglio di una stima fissa.
circuitBreaker = {
rules = [{
name = "ptu-5xx"
failureCondition = {
count = 3
interval = "PT1H"
statusCodeRanges = [{ min = 500, max = 599 }]
}
tripDuration = "PT1H"
acceptRetryAfter = true
}]
}
Due proprietà di questa funzionalità decidono come ti è permesso ragionarci sopra. Primo, le regole di trip sono approssimative: il gateway è distribuito, le istanze non sincronizzano lo stato del breaker, e ognuna applica la regola sulle informazioni che ha. Un breaker configurato per scattare dopo tre errori può in pratica lasciarne passare parecchi di più su un'istanza scalata orizzontalmente. È un meccanismo di protezione, non un contatore, e nessun calcolo di SLO va costruito sulla sua soglia esatta. Secondo, non è disponibile nel tier Consumption, una ragione in più per cui la decisione sul tier nella Parte 2 non riguardava il costo.
Il 503 merita un design deliberato. Un chiamante che riceve 503 dal gateway non può capire se il modello è giù, se il breaker è aperto o se l'intera istanza è malmessa, e i tre casi meritano comportamenti client diversi. Aggiungere nella policy outbound un header di risposta che nomina quale backend è stato tentato, e loggare lo stato del breaker, trasforma un 503 opaco in qualcosa su cui un team consumer può agire senza aprire un ticket.
Streaming, e la contabilità che ti costa
Ogni team consumer chiede lo streaming, e ha ragione. Il time to first token è il numero che un umano percepisce; la latenza totale è il numero che mostra una dashboard. Server-sent events sullo stesso endpoint HTTP è la risposta standard, il client imposta stream: true, e nulla nel routing del gateway cambia.
Quello che cambia è il contatore. La policy di token limit normalmente lavora sull'uso reale: con estimate-prompt-tokens="false" legge la sezione usage della risposta del modello, il che significa che una richiesta può superare il limite ed essere rilevata dopo, bloccando le richieste successive finché la finestra non si resetta. Con la stima abilitata conta i prompt token dallo schema della definizione API prima della chiamata, scambiando un po' di accuratezza con il non bruciare quota di backend per scoprire che eri oltre.
Lo streaming rimuove la scelta. Quando stream: true è impostato, i prompt token vengono sempre stimati a prescindere dall'impostazione della policy, e anche i completion token vengono stimati. Non esiste configurazione che faccia misurare al gateway una richiesta in streaming dal dato reale. Per i modelli che accettano immagini la deriva si somma: con lo streaming abilitato, o con la stima attiva, la policy sovraconta gli image token invece di prendere il conteggio del backend.
Questo non è un motivo per rifiutare lo streaming. È un motivo per essere precisi su cosa significano i numeri, perché la Parte 5 li trasforma in denaro:
- HTTP non in streaming: misurato dallo
usagedel modello stesso. Esatto. - SSE in streaming: prompt e completion token stimati al gateway. Abbastanza buono per il rate limiting, approssimativo per la fatturazione.
- WebSocket: vedi sotto. Di fatto fuori banda.
La risposta del control plane è la riconciliazione, non una stima migliore. Le metriche del gateway restano il segnale in tempo reale che applica le quote, e il job mensile di attribuzione che la Parte 5 costruisce corregge rispetto all'uso autoritativo che il provider riporta, con il delta tracciato come metrica a sé. Una deriva che cresce è un bug; una deriva che resta piatta a qualche punto percentuale è il costo dello streaming, e va scritta nel documento di chargeback invece di essere scoperta da un product owner.
WebSocket, dove finisce l'astrazione
Il traffico voce in tempo reale e speech-to-speech non sta nel request-response, e API Management supporta le API WebSocket. Le limitazioni sono abbastanza nette da cambiare l'architettura invece di decorarla.
Le connessioni WebSocket non possono essere distribuite o bilanciate su più backend. Una volta stabilita, ogni connessione è mantenuta uno-a-uno tra client e backend. Ogni decisione di routing in questo post, i gruppi di priorità, la topologia PTU-first, lo split regionale pesato, si applica all'handshake iniziale e a niente dopo. Un backend che degrada a metà sessione non può essere sottoposto a failover dal gateway; la sessione deve finire e il client deve riconnettersi. Qualsiasi client su questo percorso ha bisogno di logica di riconnessione con backoff, e qualsiasi modello di capacità deve trattare una sessione WebSocket come una prenotazione fissata invece che come un flusso di richieste instradabili in modo indipendente.
Seguono tre vincoli più piccoli. Una lunga lista di policy non può essere applicata all'operazione onHandshake, incluse caching, CORS, manipolazione del body e ogni policy validate-*, e le policy ereditate da uno scope globale o di prodotto vengono saltate a runtime invece di fallire rumorosamente. La policy set-header non può cambiare certi header ben noti, incluso Host, sulle richieste di handshake. E la validazione TLS è più severa che per le API HTTP: per un backend WebSocket il gateway verifica che il certificato del server sia attendibile e che il suo subject name corrisponda all'hostname, mentre un'API HTTP richiede solo l'attendibilità. Un backend con un subject di certificato non corrispondente che ha funzionato per anni via HTTP fallirà nel momento in cui viene esposto come API WebSocket.
La conseguenza pratica: il percorso real-time riceve la sua API, il suo product e la sua quota, e la documentazione consumer dice chiaramente che non è coperto dalle garanzie di failover che ha l'endpoint chat. Fingere il contrario è il modo in cui una incident review scopre la differenza.
Session affinity, e perché evitare di averne bisogno
I pool supportano la session awareness tramite un cookie: il client conserva un valore Set-Cookie e lo restituisce, così che le richieste successive raggiungano lo stesso backend. Esiste per le API server-side stateful, con la Assistants API come esempio canonico, dove il client deve tenere un session ID, estrarre un thread ID dal body della risposta e mandare il cookie giusto sulla chiamata giusta.
Funziona, ed è un vincolo che vale la pena evitare in fase di design. Una chiamata con session affinity è una chiamata che non può essere reinstradata, il che la mette nella stessa categoria del percorso WebSocket: nessun failover, e un modello di capacità dove il carico di un backend dipende da quali sessioni ci sono atterrate invece che da quante richieste sono arrivate. Dove un'API stateful serve davvero, tienila su un alias separato, così le sue garanzie più deboli non vengono ereditate in silenzio dal traffico stateless che costituisce il resto del volume.
Modalità di fallimento da tenere d'occhio
- Il 503 che significa quattro cose diverse. Breaker aperto, tutti i membri del pool esauriti, gateway malmesso, o un backend che fallisce davvero. Senza un header o una riga di log che dica quale, ognuno di questi diventa un ticket.
- La deriva dello streaming trattata come un bug. È comportamento documentato. Il fallimento non è la deriva, è pubblicare un numero stimato in un posto che dice fattura.
- Una funzionalità in preview nel percorso critico. La unified model API è in preview. È accettabile per il layer di traduzione solo se il fallback, un'API diretta per provider dietro lo stesso product e le stesse policy, esiste già ed è testato, invece di essere progettato durante l'incidente.
- Policy saltate in silenzio sulle API WebSocket. Le policy ereditate non supportate su
onHandshakevengono saltate a runtime. Un controllo di sicurezza dato per globale qui non è globale, e niente te lo dirà.
Cosa eredita la Parte 4
Alla fine di questa parte un team consumer ha un endpoint, un formato e alias di modello stabili, con routing e failover dietro di essi e una dichiarazione documentata di quali garanzie valgono per quale trasporto. Quello che nulla di tutto ciò gestisce è il lavoro che non ha bisogno di una risposta adesso: la summarization notturna dei ticket di supporto che altrimenti siederebbe sullo stesso percorso sincrono di un cliente in attesa del primo token. Quella è una coda, ed è la prossima.
Leggi questo dopo
- Parte 4, Il Lavoro Asincrono Fuori dal Percorso della Richiesta, dove il lavoro batch e in coda lascia il percorso sincrono, e la batch API si rivela una seconda porta senza nessuna policy davanti.
- Le Risposte in Streaming Sono una Decisione UX, Non di Performance, l'altra metà dell'argomento streaming: cosa compra per la persona che aspetta.
- App LLM multi-tenant: isolare i clienti su un modello condiviso, la domanda di tenancy a cui questo gateway risponde con product e quote invece che con stack separati.
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
Altro da Ercan
Altri due siti, stesso autore, terreno diverso.
Cloud, AWS, EKS, Terraform, platform engineering.
Note sul campo da sistemi in produzione. EKS, IAM, Terraform su scala organizzativa, observability, ottimizzazione dei costi.
Visita ercan.cloud →L'hub. Chi sono, consulenza, contatti.
Hub personale per entrambe le tracce di scrittura. Chi sono, come funziona la consulenza, come contattarmi.
Visita ercanermis.com →