La documentazione ufficiale delle policy di Microsoft dice che uno score threshold sopra 0.2 può portare a cache mismatch, che è un modo educato di dire che il gateway risponderà a una domanda che il chiamante non ha fatto. Ogni altro meccanismo di questa serie ha fallito in modo rumoroso: un 429, un 503, un batch job scaduto. Una semantic cache fallisce restituendo una risposta fluente, ben formata e sicura di sé che appartiene al prompt di qualcun altro. La Parte 5 ha reso la spesa attribuibile. Questa parte la rende più piccola, e tratta il meccanismo che lo fa come una superficie di correttezza invece che come una leva di costo.

Cosa serve davvero alla cache prima che funzioni

Il semantic caching in API Management ha una lista di prerequisiti che decide una scelta di infrastruttura non rivedibile in seguito, quindi va discussa prima della policy, non dopo.

  • Un secondo deployment di modello, per gli embeddings. Il deployment di chat completion serve le chiamate dei consumer; un deployment separato per gli embeddings è ciò che la cache usa per trasformare un prompt in un vettore. È una seconda linea di capacità, una seconda quota e una seconda cosa da monitorare.
  • Autenticazione con managed identity da API Management verso le API dei modelli, che la policy richiede: embeddings-backend-auth deve essere impostato a system-assigned. Qui non esiste un'opzione basata su key, ed è un buon vincolo.
  • Azure Managed Redis con il modulo RediSearch abilitato, configurato come external cache sull'istanza di API Management.

L'ultimo punto contiene la trappola. RediSearch può essere abilitato solo alla creazione della cache, e non può essere aggiunto a una cache esistente. Una piattaforma che gestisce già un'istanza Redis per le sessioni o per lo stato dei rate-limit non può semplicemente accendere il semantic caching: serve una nuova cache, provisionata con il modulo, e il Terraform della Parte 2 è il posto dove quella decisione viene registrata. Scoprirlo durante uno sprint che aveva messo a budget un pomeriggio per "abilitare la cache" è l'esito ordinario.

La coppia di policy

La lookup va nella sezione inbound, la store in outbound, e ciascuna delle due può comparire una sola volta per sezione di policy. Entrambe sono disponibili a scope globale, di product, di API e di operation, e sui gateway classic, v2, consumption e self-hosted.

<inbound>
  <base />
  <llm-semantic-cache-lookup
      score-threshold="0.05"
      embeddings-backend-id="embeddings-backend"
      embeddings-backend-auth="system-assigned"
      ignore-system-messages="true"
      max-message-count="6">
    <vary-by>@(context.Subscription.Id)</vary-by>
  </llm-semantic-cache-lookup>
  <rate-limit calls="10" renewal-period="60" />
</inbound>
<outbound>
  <llm-semantic-cache-store duration="60" />
  <base />
</outbound>

Quattro di quegli attributi sono decisioni, non boilerplate.

score-threshold definisce quanto un prompt in arrivo deve somigliare a uno in cache, su una scala da 0.0 a 1.0, dove valori più bassi richiedono una similarità semantica più alta. Questo inverte ciò che quasi tutti assumono alla prima lettura, e sbagliare la direzione trasforma una cache conservativa in una promiscua. La guida di Microsoft è partire bassi, intorno a 0.05, e regolare verso il rapporto hit-to-miss che vuoi, con l'avvertimento esplicito che sopra 0.2 i mismatch diventano probabili e che i casi d'uso sensibili dovrebbero restare più in basso.

ignore-system-messages="true" rimuove i system message prima che la similarità venga valutata, ed è raccomandato. Conta più di quanto sembri: due applicazioni con domande utente identiche e system prompt diversi apparirebbero altrimenti diverse alla cache, e una singola applicazione il cui system prompt viene modificato invaliderebbe l'intera propria cache senza alcuna ragione semantica.

max-message-count salta il caching quando un dialogo supera il numero specificato di messaggi rimanenti. Le conversazioni lunghe sono esattamente il punto in cui un match semantico è meno affidabile, perché il significato dell'ultimo messaggio dipende da turni che l'embedding non ha mai visto.

vary-by è il confine di isolamento, e la prossima sezione spiega perché è un controllo di sicurezza.

vary-by non è una finezza di partizionamento

Senza vary-by, una sola cache è condivisa tra tutti i chiamanti dell'API. Un prompt dell'assistente clienti che contiene i dettagli di un cliente può ricevere risposta da una completion in cache, e un prompt semanticamente simile di un tenant diverso può ricevere risposta da quella stessa entry. La documentazione lo dice senza giri di parole: controlla l'accesso cross-user alle entry di cache specificando vary-by con identificatori specifici di utente o di gruppo di utenti.

Per questa piattaforma il minimo è l'ID della subscription, che allinea il confine della cache esattamente con il confine di tenancy costruito nella Parte 5. Dove un'applicazione serve utenti finali i cui dati non devono mescolarsi, il valore corretto è un claim del token validato, non qualcosa che il chiamante può impostare in un header, ed è la ragione per cui la validate-jwt della Parte 5 scrive il proprio token in output-token-variable-name. Un vary-by che legge un header non validato è un data leak cross-tenant con un file di configurazione come root cause.

Il costo di un partizionamento stretto è un hit rate più basso, e quel trade-off è reale. È anche il default corretto: una cache che non fa mai leak e risparmia il 20 percento batte una che risparmia il 45 percento e prima o poi restituisce la risposta del cliente sbagliato.

La dipendenza che hai appena aggiunto

La cache sta nel percorso di richiesta di ogni chiamata, il che significa che Redis è ora sul percorso critico del gateway. La raccomandazione di Microsoft è specifica e vale la pena seguirla alla lettera: metti una policy rate-limit o rate-limit-by-key immediatamente dopo la cache lookup, per evitare che il backend venga travolto se la cache non è disponibile.

Ragiona su quel fallimento. In un giorno normale una parte significativa del traffico non raggiunge mai un modello. Se Redis diventa indisponibile, ognuna di quelle richieste diventa una chiamata di completion reale, all'istante, contro una quota dimensionata per lo stato stazionario con cache. L'outage della cache non degrada la piattaforma, ne moltiplica il carico, e il rate limit è ciò che converte tutto questo da outage a throttling. È la stessa lezione di qualsiasi cache davanti a un database, ed è più facile dimenticarla qui perché la cache è stata introdotta come ottimizzazione di costo e non come dipendenza di capacità.

Il deployment di embeddings merita lo stesso ragionamento. Ogni lookup calcola l'embedding del prompt in arrivo, quindi il modello di embeddings ha bisogno di capacità e di context size sufficienti per il volume e la lunghezza dei prompt in produzione. Un deployment di embeddings dimensionato per un proof of concept diventa il collo di bottiglia di tutto il traffico, in cache o no, perché la lookup avviene prima di sapere se è hit o miss.

Cosa fa un cache hit ai numeri della Parte 5

Un hit non è gratis e non è una completion. Costa una chiamata di embeddings, un po' di tempo Redis e nessun token di completion, il che significa che il modello di chargeback della Parte 5 ha bisogno di tre piccole modifiche, non di una riscrittura.

  • La spesa di embeddings diventa una voce di costo di piattaforma. Viene sostenuta a ogni richiesta, miss inclusi, e non è attribuibile a una completion. O viene addebitata ai tenant pro rata sul conteggio delle richieste, o viene assorbita dalla piattaforma. Scegli una delle due e mettila per iscritto; non lasciarla fuori dalla riconciliazione.
  • L'hit rate è una dimensione di metrica legittima. È a bassa cardinalità e sta bene sulla dashboard accanto alla spesa, perché un hit rate che cala è di solito una modifica al prompt template che nessuno ha annunciato.
  • I risparmi vanno riportati come costo evitato, non come spesa. Un tenant la cui bolletta è scesa del 30 percento grazie al caching assumerà che il numero più basso sia il nuovo baseline. Mostrare i token evitati accanto ai token fatturati tiene onesta quella conversazione, e sostiene il caso dell'istanza Redis che la piattaforma sta pagando.

Quando non fare caching affatto

Il semantic caching si adatta a prompt ad alto volume, bassa varianza e non personalizzati. La knowledge search retail dell'azienda sulla documentazione prodotto è vicina all'ideale. Gli altri casi no, e la policy va applicata con scope per API invece che globalmente, che è esattamente il motivo per cui supporta lo scope di product e di API.

Saltala dove la risposta dipende dall'ora corrente o da stato live, perché una duration di 60 secondi è una finestra di correttezza, non solo una preferenza di freschezza. Saltala per i flow di tool-calling, dove l'output del modello è un'istruzione da eseguire e non testo da leggere, e un'istruzione stantia è un'azione sbagliata. Saltala per qualsiasi cosa personalizzata oltre ciò che vary-by può partizionare. E tratta i dialoghi lunghi multi-turno come fuori scope impostando max-message-count, invece di sperare che lo similarity score se ne accorga.

Modalità di fallimento da tenere d'occhio

  • La soglia impostata al contrario. Più alto vuol dire più permissivo. Un team che ottimizza per l'hit rate la alza a 0.4, il rapporto migliora, e i mismatch restano invisibili finché un utente non segnala una risposta che non c'entra nulla con la sua domanda.
  • Un vary-by mancante. Nessun errore, nessun warning, e una cache condivisa tra tutti i tenant dell'API.
  • RediSearch scoperto tardi. Il modulo non può essere aggiunto a una cache esistente, quindi la risposta è una nuova cache e una migrazione, nel mezzo di un lavoro che presumeva un cambio di config.
  • Redis giù, backend inondato. Senza il rate limit subito dopo la lookup, un outage della cache arriva al modello come un picco di traffico.
  • Un deployment di embeddings sottodimensionato. Viene chiamato a ogni richiesta, quindi limita hit e miss allo stesso modo, e il sintomo sembra la cache che rende tutto più lento.
  • Una modifica al prompt template che svuota la cache in silenzio. Vale un alert sull'hit rate, invece di scoprirlo in bolletta.

Cosa eredita la Parte 7

La cache risponde alle ripetizioni. Non fa nulla per il caso molto più comune in cui il modello ha bisogno di informazioni su cui non è mai stato addestrato, che è il problema del retrieval, e la knowledge search retail dell'azienda lo sta già aspettando. Prossima puntata: Azure AI Search contro un vector database dedicato, confrontati sui termini che decidono davvero la scelta.

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