Perché i progetti IA falliscono in fase di produzione (e cosa fare prima di iniziare)
Il prototipo funzionava. La demo è andata bene. Gli stakeholder sono entusiasti. Sei mesi dopo il progetto è morto o sopravvive grazie alla buona volontà del team.
Questa è la traiettoria più comune nello sviluppo di soluzioni IA, ed è quasi interamente prevedibile. Per capire perché i progetti IA falliscono in fase di produzione bisogna comprendere un concetto fondamentale: il prototipo e il sistema in produzione sono due progetti diversi, e la maggior parte dei team pianifica solo il primo.
Il divario tra prototipo e produzione è dove muoiono i progetti
Perché la demo funziona e il sistema in produzione no
La demo funziona perché è stata costruita in condizioni controllate, con input rappresentativi. Chi la eseguiva sceglieva gli input. I dati erano puliti. I casi limite erano assenti. Le modalità di errore non venivano mai attivate.
Il sistema in produzione affronta dati reali dal primo giorno. I dati reali hanno campi mancanti, artefatti di encoding, inconsistenze di formato e casi limite che rappresentano il 15-30% del volume effettivo. Nessuno di questi compariva nella demo. Tutti compaiono in produzione.
Il divario non è tecnico. È un problema di perimetro e sequenziamento che emerge dal trattare il successo del prototipo come prova che il lavoro di produzione è quasi completato.
La fase specifica in cui si concentrano i fallimenti
I fallimenti si concentrano in due momenti.
Il primo è quando i dati reali di produzione colpiscono il sistema per la prima volta. Un sistema che ha gestito senza problemi 500 record di test accuratamente selezionati incontrerà la prima inconsistenza di formato nel giro di poche ore dal lancio in produzione. Il modo in cui il sistema gestisce quell'inconsistenza — se si blocca completamente, degrada con grazia o instrada verso la revisione umana — determina se il fallimento è catastrofico o gestibile. La maggior parte dei prototipi non ha una risposta a questa domanda perché non è mai stata posta.
Il secondo è quando un operatore deve agire su un output dell'IA senza prima verificarlo. I prototipi vengono rivisti. I sistemi in produzione che richiedono la revisione umana di ogni output non stanno producendo l'efficienza promessa. Il momento in cui si elimina il passaggio di revisione è il momento in cui iniziano gli errori a catena.
Cosa sbagliano i team riguardo al significato di "completato"
"Completato" inteso come "deployato in produzione" e "completato" inteso come "produce risultati entro le tolleranze definite per 60 giorni consecutivi" sono traguardi diversi, separati da mesi di lavoro.
La maggior parte dei progetti definisce "completato" come il primo traguardo e festeggia di conseguenza. Il secondo traguardo, quello che significa davvero che il progetto ha avuto successo, non riceve la stessa cerimonia e spesso non viene nemmeno monitorato.
I vendor e i champion interni hanno incentivi per dichiarare il prototipo completato. Gli ingegneri che dovranno mantenere il sistema in produzione hanno informazioni migliori su ciò che resta da fare e raramente sono quelli che definiscono le tempistiche.
La checklist pre-produzione che la maggior parte dei team salta
Due giorni di lavoro prima dell'inizio dello sviluppo prevengono la maggior parte dei fallimenti in produzione. La maggior parte dei team li salta perché sembra un ritardo. È l'esatto contrario.
Audit dei dati: cosa fare prima di selezionare qualsiasi strumento
Campionate 200-300 record reali di produzione. Devono essere record di produzione effettivi — non export ripuliti, non dati sintetici, non i migliori esempi che riuscite a trovare. Classificate ogni record per livello di qualità e tipo di caso limite. Contate quanti escono dal caso pulito.
L'output è un documento scritto che risponde a questa domanda: quale percentuale degli input reali corrisponde al caso pulito che il sistema presuppone, e quali sono le categorie e le frequenze di tutto il resto?
La scelta degli strumenti avviene dopo che questo documento esiste. Sono i dati a determinare cosa è costruibile. Una demo di un vendor valutata su dati puliti, confrontata con un ambiente di produzione dove il 25% degli input ha formati sconosciuti, produrrà un sistema che fallisce sul 25% del volume reale dal primo giorno.
Inventario del perimetro di produzione: i componenti che vengono sempre sottostimati
Scrivete una lista di ogni componente necessario per un sistema in produzione che era assente dal prototipo. Ogni elemento di questa lista è un task ingegneristico concreto:
- Gestione strutturata degli errori con eccezioni tipizzate
- Logica di retry con backoff esponenziale per le chiamate API esterne
- Validazione degli input prima che qualsiasi dato raggiunga il modello
- Validazione degli output prima che qualsiasi risultato raggiunga i sistemi a valle
- Logging strutturato con contesto sufficiente a ricostruire qualsiasi errore
- Dashboard di monitoraggio costruite sulle metriche di risultato
- Alerting con runbook per la reperibilità
- Pipeline di deployment con health check
- Capacità di rollback che funzioni sotto pressione
Stimate ogni elemento separatamente. Sommate le stime. Questo è il perimetro dello sviluppo in produzione. Sarà più ampio di quanto il piano progettuale originale prevedesse. Questo è esattamente il punto.
Definizione dei risultati: mettere per iscritto cosa significa successo prima di costruire qualsiasi cosa
Mettete per iscritto la metrica di successo, l'intervallo di tolleranza e il costo di un output errato prima di scrivere qualsiasi riga di codice. Ottenete l'accordo di due stakeholder su cosa sia un output corretto.
Se due stakeholder non riescono a concordare su cosa sia un output corretto, il caso d'uso non è pronto per essere dimensionato. Quel disaccordo emergerà al sesto mese, dopo che una parte significativa del budget è stata spesa — il peggior momento possibile per scoprirlo.
Errori di qualità dei dati: la causa più prevenibile
I quattro problemi di dati che emergono solo in produzione
Campi obbligatori mancanti a tassi superiori al previsto. Un sistema progettato per record con dati completi si rompe quando il 20% dei record reali è privo di un campo che il sistema tratta come obbligatorio.
Formati inconsistenti all'interno di una singola fonte. Formati di data, rappresentazioni di valute e strutture degli indirizzi variano all'interno di una stessa fonte dati in modi invisibili senza campionamento.
Rumore nelle label dei dataset etichettati manualmente. Qualsiasi dataset etichettato da esseri umani ha label inconsistenti. Un classificatore addestrato su dati dove due categorie sono state etichettate in modo intercambiabile da annotatori diversi eredita quell'inconsistenza.
Distribuzione che cambia tra dati storici e dati live. Un sistema calibrato su dati di 18 mesi fa potrebbe non riflettere l'aspetto degli input di oggi. Il linguaggio dei clienti cambia. I formati dei fornitori cambiano.
Come eseguire un audit dei dati in un giorno
from collections import defaultdict
from dataclasses import dataclass, field
import re
@dataclass
class AuditResult:
total: int = 0
clean: int = 0
missing_by_field: dict = field(default_factory=lambda: defaultdict(int))
format_variants: dict = field(default_factory=lambda: defaultdict(set))
edge_cases: dict = field(default_factory=lambda: defaultdict(int))
def run_audit(records: list[dict], required_fields: list[str]) -> AuditResult:
result = AuditResult(total=len(records))
for rec in records:
is_clean = True
for f in required_fields:
if not rec.get(f):
result.missing_by_field[f] += 1
result.edge_cases["missing_required_field"] += 1
is_clean = False
if date_val := rec.get("date"):
fmt = classify_date(date_val)
result.format_variants["date"].add(fmt)
if fmt == "unknown":
result.edge_cases["unknown_date_format"] += 1
is_clean = False
if is_clean:
result.clean += 1
return result
def classify_date(val: str) -> str:
patterns = [
(r"\d{4}-\d{2}-\d{2}", "ISO8601"),
(r"\d{2}/\d{2}/\d{4}", "US_SLASH"),
(r"\d{2}\.\d{2}\.\d{4}", "EU_DOT"),
]
for pattern, label in patterns:
if re.match(pattern, val.strip()):
return label
return "unknown"
def print_audit_summary(result: AuditResult) -> None:
clean_pct = (result.clean / result.total * 100) if result.total else 0
print(f"Total records: {result.total}")
print(f"Clean records: {result.clean} ({clean_pct:.1f}%)")
print(f"Edge case types: {dict(result.edge_cases)}")
print(f"Missing field rates: {dict(result.missing_by_field)}")
print(f"Date format variants found: {result.format_variants.get('date', set())}")
Eseguite questo codice sui record reali prima di aprire la documentazione di qualsiasi vendor. Il valore clean_pct vi indica il tetto massimo realistico di successo per qualsiasi sistema costruiate senza affrontare i casi limite. Se è 72%, pianificate che il 28% del volume di produzione richiederà una gestione speciale dal primo giorno.
Cosa vi dice l'audit sulla scelta degli strumenti e sul perimetro di sviluppo
L'output dell'audit determina due cose.
Scelta degli strumenti: gli strumenti dei vendor vengono tipicamente valutati su input dove il 95%+ è pulito. Se il vostro audit mostra il 25% di formati di data sconosciuti, testate qualsiasi strumento candidato su un campione che rifletta la vostra distribuzione reale, non i dati demo del vendor.
Perimetro di sviluppo: ogni categoria di caso limite nell'audit è una decisione di gestione che il sistema in produzione deve prendere. Alcune saranno gestite con regole di validazione. Alcune verranno instradate verso la revisione umana. Alcune richiederanno preprocessing. Ciascuna è un task ingegneristico che non era nel prototipo e deve essere incluso nella stima del perimetro di produzione.
Il distribution shift è il problema più difficile da individuare prima del lancio. La mitigazione consiste nel registrare le caratteristiche degli input dal primo giorno:
import logging
logger = logging.getLogger(__name__)
def log_input_characteristics(record_id: str, rec: dict) -> None:
logger.info("input_received", extra={
"record_id": record_id,
"has_required_fields": all(rec.get(f) for f in ["vendor", "date", "amount"]),
"date_format": classify_date(rec.get("date", "")),
"amount_type": type(rec.get("amount")).__name__,
"field_count": len([k for k, v in rec.items() if v]),
})
Quando la distribuzione cambia, questo log produce un segnale rilevabile prima che gli utenti inizino a segnalare output errati.
Le tre decisioni strutturali che determinano i risultati in produzione
Separare il perimetro del prototipo dal perimetro di produzione nel piano progettuale
Scrivete due brief di progetto. Uno per il prototipo: dimostrare che l'approccio funziona su input rappresentativi, consegnare una demo funzionante, stima da due a quattro settimane. Uno per lo sviluppo in produzione: consegnare un sistema che soddisfa la definizione dei risultati entro le tolleranze per 60 giorni, include tutti gli elementi dall'inventario del perimetro di produzione, stima separata dopo il completamento dell'audit dei dati.
I team che trattano queste fasi come un unico progetto sottostimano lo sviluppo in produzione di 3-5x in modo sistematico. La correzione strutturale non costa nulla e fa risparmiare mesi.
Definire e strumentare le metriche di risultato prima del lancio
import logging
import time
from dataclasses import dataclass, asdict
logger = logging.getLogger(__name__)
@dataclass
class OutcomeRecord:
record_id: str
validation_passed: bool
confidence: float
routed_to_human: bool
error_type: str | None
cost_usd: float
duration_ms: int
def process(record_id: str, content: str) -> dict:
t0 = time.monotonic()
try:
result, usage = run_pipeline(content)
passed = validate_output(result)
logger.info("outcome", extra=asdict(OutcomeRecord(
record_id=record_id,
validation_passed=passed,
confidence=result.confidence,
routed_to_human=not passed or result.confidence < 0.75,
error_type=None,
cost_usd=estimate_cost(usage),
duration_ms=int((time.monotonic() - t0) * 1000)
)))
return result
except Exception as exc:
logger.error("outcome", extra=asdict(OutcomeRecord(
record_id=record_id,
validation_passed=False,
confidence=0.0,
routed_to_human=True,
error_type=type(exc).__name__,
cost_usd=0.0,
duration_ms=int((time.monotonic() - t0) * 1000)
)))
raise
Attivate un alert quando il tasso di routed_to_human supera il 15% per sette giorni consecutivi. Quella soglia, mantenuta per due settimane, significa che il personale ha concluso che la revisione manuale è più veloce che fidarsi del sistema.
Assegnare la responsabilità dei risultati prima che avvenga il passaggio di consegne
La responsabilità dei risultati ha quattro componenti: una persona designata, una metrica definita con un intervallo di tolleranza, una cadenza di revisione mensile e l'autorità esplicita di ritirare il sistema dalla produzione se la metrica è fuori tolleranza per due revisioni consecutive.
Il responsabile dei risultati definito prima dello sviluppo chiede l'infrastruttura di monitoraggio e la capacità di rollback durante la revisione architetturale. Il responsabile dei risultati assegnato al momento del passaggio di consegne eredita un sistema privo di queste cose e non può aggiungerle facilmente.
Cosa succede quando saltate la checklist: quattro timeline di fallimento
Fallimento per qualità dei dati: mese uno
Il sistema viene deployato. Arrivano i dati reali di produzione. Il sistema fallisce sul 18% degli input perché il formato della data è uno che il prototipo non ha mai visto. Il team passa quattro settimane a scoprire e classificare problemi di dati che l'audit pre-produzione avrebbe fatto emergere in due giorni. Le tempistiche slittano. La fiducia degli stakeholder cala. La data di consegna originale è ora chiaramente sbagliata.
Fallimento per sottostima del perimetro: mese tre
Il prototipo è stato consegnato nei tempi. Lo sviluppo in produzione è in ritardo di sei settimane. Il piano originale presupponeva che la produzione fosse quasi completata quando il prototipo era finito. Quella supposizione è ora visibilmente errata e troppo tardi per ripianificare onestamente senza una conversazione difficile. Il team comprime il perimetro per rispettare una scadenza. Il perimetro compresso omette il monitoraggio. Il monitoraggio omesso fa sì che il fallimento del sesto mese passi inosservato fino al nono.
Fallimento di misurazione: mese sei
Il sistema è in esecuzione. La dashboard di attività mostra 50.000 task elaborati. Uno stakeholder di business chiede quale sia il ROI. Nessuno riesce a rispondere perché le metriche di risultato non sono mai state definite e strumentate. Il team si affretta a definire le metriche retroattivamente e scopre che le prestazioni attuali del sistema non avrebbero soddisfatto il business case originale.
Fallimento di ownership: mese nove
Il team di ingegneria è passato ad altro al mese due. Il sistema sta derivando dal mese quattro con lo spostamento delle distribuzioni degli input. Il personale ha scoperto workaround al mese sei e ha smesso di usare il sistema per qualsiasi cosa importante. Una revisione trimestrale chiede perché il processo che il sistema avrebbe dovuto migliorare non è migliorato. Nessuno ha una risposta chiara. Chi l'ha costruito è tre progetti più avanti.
Il percorso di recupero quando un progetto è già in difficoltà
Diagnosticare quale modalità di fallimento è attiva
Quattro domande, in ordine:
- Il problema di qualità dei dati è troppo grande per essere risolto con un fix al prompt o alla configurazione?
- Lo sviluppo in produzione è stato dimensionato separatamente dal prototipo, con tutti i componenti di produzione stimati?
- Le metriche di risultato sono definite, strumentate e riviste con cadenza regolare?
- Esiste un responsabile dei risultati designato, con una metrica, una tolleranza e l'autorità di ritirare il sistema?
La prima domanda con risposta "no" è la modalità di fallimento primaria. Partite da lì.
La sequenza di fix per ogni tipo di fallimento
Fallimento per qualità dei dati: eseguite un audit reale sui record di produzione attuali. Non il set di test originale. I record attuali elaborati negli ultimi 30 giorni. Dimensionate una ricostruzione che affronti ciò che l'audit rivela. Non esiste un fix di prompt engineering per un sistema che incontra distribuzioni per cui non è mai stato costruito.
Fallimento per sottostima del perimetro: scrivete l'inventario del perimetro di produzione onestamente, contro la lista completa dei componenti. Stimate ogni componente. Presentate la stima. Non comprimete per farla rientrare nella timeline originale. Comprimerla produce lo stesso fallimento su una timeline più breve.
Fallimento di misurazione: definite le quattro metriche di risultato, strumentatele con logging strutturato, eseguite la prima revisione. La maggior parte dei team resta sorpresa dai numeri. Quella sorpresa è informazione. Agite di conseguenza prima di decidere se il sistema è recuperabile.
Fallimento di ownership: designate il responsabile, assegnate la metrica e la tolleranza, fissate la cadenza di revisione, confermate l'autorità. Poi eseguite la prima revisione con quel responsabile presente.
Quando fermarsi è la decisione giusta e come farlo in modo pulito
Fermatevi quando i problemi di dati superano ciò che una ricostruzione può assorbire, quando la definizione del caso d'uso resta contestata dopo tentativi genuini di allineare gli stakeholder, o quando il costo ingegneristico rimanente supera il valore di business realistico.
Una decisione di stop pulita è un documento scritto. Identifica la modalità di fallimento primaria. Documenta cosa si è appreso. Fornisce una raccomandazione su se un caso d'uso diverso, un approccio diverso o un tentativo successivo con una migliore infrastruttura dati potrebbe avere successo. Non è un post-mortem scritto per attribuire colpe. È un artefatto ingegneristico che rende più probabile il successo del progetto successivo.
Continuare oltre il punto in cui fermarsi è la decisione corretta è ragionamento da costo sommerso. Estende la timeline verso lo stesso risultato.
La checklist che previene la maggior parte di questi fallimenti richiede due giorni prima della prima settimana: audit dei dati, inventario del perimetro di produzione, definizione dei risultati, assegnazione della responsabilità. Ogni elemento è disponibile prima dell'inizio dello sviluppo ed è costoso o impossibile da integrare a posteriori dopo il lancio.
Ulteriori pattern di implementazione e codice funzionante su claw.zip.
