Quando implementate sistemi RAG in produzione, nel primo mese tutto funziona. Nel terzo mese OpenAI rilascia text-embedding-3-large in sostituzione di text-embedding-4, voi fate un test e lo nuovo modello mostra il %4 di recall più alto. Ma i vostri 12 milioni di documenti sono ancora indicizzati secondo il vecchio modello di embedding. La re-indexing richiede 18 ore e 6.400 dollari di costi API. Questo è il momento in cui inizia il drift degli embedding — aggiornate il modello ma il vector store rimane indietro, l'embedding della query e gli embedding memorizzati si trovano su manifold diversi, l'accuratezza del retrieval cala silenziosamente. Questo articolo spiega come valutare il rapporto costo-qualità della migrazione del modello, come progettare la re-indexing incrementale e come misurare il drift in produzione.

Cos'è il Drift degli Embedding e Perché Conta

Il drift degli embedding si verifica quando il modello di embedding della query è diverso dal modello di embedding dei documenti. Se generate gli embedding con il modello A durante l'indexing e utilizzate il modello B durante la query, la similarità del coseno perde significato. Poiché i due modelli operano in spazi vettoriali diversi, il punteggio di "similarità" diventa ingannevole.

Questa situazione si verifica in tre scenari principali: (1) il provider del modello di embedding rilascia una nuova versione (il passaggio da OpenAI ada-002 a text-embedding-3-small ha comportato una riduzione del %12 nelle dimensioni ma nessuna compatibilità binaria), (2) transizione a un modello fine-tuned (un modello allenato su dati specifici del dominio funziona meglio di uno generico ma l'intero corpus deve essere re-embedded), (3) cambio del modello multilingue (passare da sentence-transformers/paraphrase-multilingual-mpnet-base-v2 a intfloat/multilingual-e5-large aumenta il retrieval@10 del %8 ma non esiste mapping 1:1).

In produzione è difficile accorgersi del drift perché le metriche calano gradualmente. Nella prima settimana gli utenti dicono "i risultati sono un po' peggiori", nella seconda settimana i ticket di supporto aumentano del %15, nella terza settimana il retention scende. Il segnale precoce del drift è questo: il punteggio medio di similarità del coseno per le nuove query scende rispetto al baseline durante l'indexing. Se il punteggio medio di similarità durante l'indexing era 0,78, una caduta a 0,71 durante la query — è indicativo dell'incompatibilità del modello.

Trade-off dei Costi: Re-index vs Modello Doppio

Pensate al costo della re-indexing in tre componenti: (1) costo delle chiamate API (OpenAI text-embedding-3-large 1M token = 0,13 dollari, Cohere embed-v3 0,10 dollari), (2) tempo di elaborazione (12M documenti × 512 token medio = 6,1B token ≈ 18 ore di elaborazione batch parallela), (3) rischio di downtime (se non fate uno switchover atomico, le query dell'utente cadono su un index parziale).

Alternativa: strategia del modello doppio — create un index separato per il nuovo modello e fate il passaggio tramite test A/B. In questo caso il costo dello storage è 2×, ma il rischio è zero. Quando il nuovo index è pronto, spostate il traffico %10 → %50 → %100. Se vedete una regressione, il rollback avviene istantaneamente. Tuttavia questa strategia comporta un costo di storage vettoriale raddoppiato (su Pinecone, un pod p1.x1 costa 0,096 dollari/ora, 12M vettori 1536-dim = ~18GB ≈ 2 pod = 140 dollari/mese, index doppio = 280 dollari/mese).

Re-indexing Incrementale: Partitioning Hot/Cold

Invece di re-indexare l'intero corpus in una notte, fate il partitioning in base alla frequenza di utilizzo. I documenti su cui è caduta una query negli ultimi 30 giorni sono "hot", il resto è "cold". La partizione hot tipicamente costituisce il %15-25 del corpus ma soddisfa l'%80 dei hit delle query.

Strategia: prima re-embed la partizione hot con il nuovo modello (18 ore invece di 3 ore, costo 6.400 → 1.200 dollari). Durante la query, fate il routing degli shard — le nuove query vanno prima all'index hot, in caso di miss fallback all'index cold. In questo modo ottenete l'%80 del miglioramento di accuratezza nel primo giorno e il %100 in 2-3 settimane di re-indexing rolling.

Per il tracking del partitioning un semplice tavolo in PostgreSQL è sufficiente:

CREATE TABLE doc_partition (
  doc_id UUID PRIMARY KEY,
  partition TEXT CHECK (partition IN ('hot', 'cold')),
  last_queried_at TIMESTAMPTZ,
  embedding_model TEXT,
  embedding_version TEXT,
  re_indexed_at TIMESTAMPTZ
);

CREATE INDEX idx_partition_model 
  ON doc_partition(partition, embedding_model);

Logica del routing delle query:

def retrieve(query: str, model: str, k: int = 10):
    query_emb = embed(query, model)
    
    # cerca nella partizione hot
    hot_results = vector_db.search(
        collection="hot",
        vector=query_emb,
        limit=k,
        filter={"embedding_model": model}
    )
    
    if len(hot_results) >= k:
        return hot_results
    
    # se manca qualcosa, completa da cold
    cold_results = vector_db.search(
        collection="cold",
        vector=query_emb,
        limit=k - len(hot_results),
        filter={"embedding_model": model}
    )
    
    return merge_results(hot_results, cold_results)

Questo approccio è simile alla logica di "incremental sync guidato dagli eventi" utilizzata nel lavoro di architettura dati first-party di Roibase — invece di copiare tutti i dati in una sola volta, sincronizzate continuamente il subset che cambia.

Rilevamento del Drift: Monitoraggio dello Spazio di Embedding

Per misurare il drift in produzione utilizzate tre metriche:

MetricaSogliaSignificato
Cambio della similarità mediabaseline − 0,05La distanza tra l'embedding della query e l'index è aumentata
Stabilità del top-k<90% overlapLa stessa query restituisce risultati diversi (effetto del cambio di modello)
Tasso OOV (out-of-vocabulary)>%2Il nuovo modello non riconosce i termini nel corpus precedente

Calcolate lo spostamento della similarità media con un job batch giornaliero — prendete le query effettuate nelle ultime 24 ore, embedatele con il vecchio modello e con il nuovo, confrontate il coseno di similarità con gli embedding memorizzati. Se la similarità con il nuovo modello è 0,73 e con il vecchio è 0,78 — c'è uno spostamento di 0,05, segnale di ri-indexing.

Per la stabilità del top-k, eseguite lo stesso set di query di test (100-200 query) ogni giorno con entrambi i modelli e confrontate i primi 10 risultati. Se l'overlap scende sotto l'%85 — è necessaria una migrazione del modello.

Strategia di Migrazione del Modello: Blue-Green Deployment

Quando cambiate il modello, fate uno switchover atomico — blue-green deployment. L'index vecchio è "blue", il nuovo è "green". Il traffico va inizialmente a blue, voi riempite green dietro le quinte. Quando green è pronto, spostate il traffico a green in 5 minuti. Se c'è un problema, rollback immediato a blue.

Passaggi concreti:

  1. T-0: Iniziate a generare embedding con il nuovo modello, create un index parallelo (green_index).
  2. T+18h: Green index %100 pronto. Blue index è ancora live.
  3. T+18h 5m: Aggiungete il flag MODEL_VERSION=green al query router, spostate il traffico %10 a green.
  4. T+18h 30m: Nessun errore, spostate al %50.
  5. T+19h: %100 green. L'index blue va in modalità read-only (backup di 7 giorni).
  6. T+7 giorni: L'index blue viene eliminato.

Questo approccio è particolarmente critico nei sistemi di ricerca e-commerce — in un cliente con cui Roibase ha lavorato (categoria cosmetica, 2,4M prodotti, 80K/giorno query) la migrazione del modello ha causato una perdita di sessioni dello %0,2 (il rollback è stato completato in 12 secondi grazie al blue-green).

Ottimizzazione dei Costi: Batch + Cache

Due tecniche per ridurre il costo della re-indexing:

Utilizzo dell'API batch: L'API batch di OpenAI è sconto del %50 rispetto all'API normale (0,13 → 0,065 dollari/1M token). Tuttavia è asincrona — la risposta arriva entro 1-24 ore. Per la re-indexing è sufficiente perché non è realtime. Se inviate 12M documenti al batch, il costo scende da 6.400 a 3.200 dollari.

Cache semantica: Se lo stesso documento viene indexato più volte con metadati diversi (ad esempio: stessa descrizione del prodotto, SKU diversi), cachate l'embedding. Deduplicate tramite hash MD5. Nelle esperienze di Roibase questo fornisce una riduzione dei costi del %12-18 (soprattutto nei segmenti fashion/beauty dove le descrizioni dei prodotti sono simili).

import hashlib
from functools import lru_cache

@lru_cache(maxsize=100_000)
def cached_embed(text: str, model: str) -> list[float]:
    cache_key = hashlib.md5(f"{model}:{text}".encode()).hexdigest()
    cached = redis.get(cache_key)
    if cached:
        return json.loads(cached)
    
    emb = openai.Embedding.create(input=text, model=model)
    redis.setex(cache_key, 86400 * 7, json.dumps(emb))
    return emb

Passaggio al Modello Fine-tuned: Trade-off dell'Adattamento al Dominio

Utilizzare un modello di embedding specifico del dominio anziché un modello generico aumenta il retrieval@10 del %8-15 (ad esempio, nel dominio legale paraphrase-mpnet-base-v2 con fine-tuning e contrastive learning su legal-bert-base-uncased). Tuttavia ci sono costi di fine-tuning: (1) raccolta dei dati etichettati (1000-5000 coppie query-documento), (2) tempo di GPU (A100 8 ore ≈ 60 dollari), (3) re-indexing dell'intero corpus.

Analisi del trade-off: se l'accuratezza del retrieval migliora del %10 e questo contribuisce il %2 alla conversione (ad esempio, nel flusso di lead gen, suggerire l'articolo giusta aumenta la compilazione del modulo del %2), 100K query mensili × 0,02 × 50 dollari AOV = 100K dollari di lift. In questo caso il costo di fine-tuning di 10K dollari + re-indexing si recupera in 1 mese.

Tuttavia c'è anche un costo di manutenzione del modello fine-tuned — ogni 6 mesi è necessario ri-allenare con i nuovi dati (domain shift). Questo ciclo comporta una re-indexing continua. Alternativa: adapter layer — aggiungete un piccolo layer fine-tuned sopra il modello base, in questo modo gli embedding base rimangono fissi, solo la proiezione al momento della query cambia. In questo caso la re-indexing non è necessaria ma il guadagno di accuratezza scende dal %15 all'%8.

Caso Contrario: La Re-indexing Non È Necessaria?

In alcune situazioni non fare la re-indexing è la decisione giusta. Se (1) il cambio di modello è minore (ad esempio, la differenza empirica di recall tra OpenAI ada-002 e text-embedding-3-small è <%2), (2) il corpus è statico (non vengono aggiunti nuovi documenti), (3) il pattern di query non cambia — il drift è minimo.

Soprattutto nei prodotti SaaS B2B (knowledge base interno, ricerca nella documentazione) il corpus viene aggiornato 1-2 volte all'anno. In questo caso, al di fuori di upgrade maggiori del modello (ad esempio, BERT → MPNet), non fare la re-indexing ha senso. Invece, fate ensemble al momento della query — fate il retrieval con il modello vecchio e con il nuovo, unite i risultati con reciprocal rank fusion. Questo comporta un costo di latenza del %3-5 ma è inferiore al costo della re-indexing.

Albero decisionale:

  • Corpus >5M documenti + nuovo modello %5+ guadagno di accuratezza → re-indexing incrementale con partitioning hot/cold
  • Corpus <1M + %10+ guadagno → re-indexing completo blue-green
  • Corpus <1M + <%5 guadagno → ensemble + rinvio della re-indexing
  • Modello fine-tuned + impatto sulla conversione >10× costo → re-indexing
  • Modello fine-tuned + impatto sulla conversione <3× costo → adapter layer o rinuncia

Nel lavoro di Roibase su GEO, c'è una situazione simile — quando optimize le citazioni dell'LLM, quale contenuto deve essere rigenerato e quale è sufficiente nella sua forma attuale? Anche questo richiede un trade-off costo-impatto.

Prevenzione del Drift: Version Pinning e Contract Testing

Il modo migliore per proteggersi dal drift degli embedding in produzione è — pin il modello e scrivere test di contract dell'API. Se utilizzate text-embedding-3-large di