Qualche anno fa “fare ML” aveva una forma chiara. Raccoglievi dati etichettati, li pulivi, allenavi un modello, lo valutavi, lo deployavi dietro un’API. Il giro intero impiegava mesi, e gran parte del budget se ne andava nel lavoro sui dati. Quel mondo esiste ancora, ma ora è un sottoinsieme di un panorama molto più disordinato.
Nel 2026 la prima domanda su un nuovo progetto non è “che modello alleno?” È “ho bisogno di allenare qualcosa, e basta?” Per una classe enorme e crescente di problemi, la risposta giusta è: punta un LLM hosted sull’input e chiedi gentilmente. Per un’altra classe è: fai fine-tuning di un modello open-source su hardware economico. Per una terza, la pipeline classica che hai imparato nel modulo 9 è ancora corretta. Scegliere tra queste è la decisione di design più consequente del campo in questo momento.
Questa lezione è un framework decisionale, con codice. Nessun algoritmo nuovo: solo giudizio sudato su quale tool tirare giù dallo scaffale.
L’albero decisionale
Tre scatole. Scegli quella più a sinistra in cui rientra il tuo problema.
Chiama un LLM hosted (Claude, GPT-5, Gemini) quando:
- Il task è qualcosa che un umano sveglio potrebbe fare avendo l’input come testo, senza addestramento speciale. Stesura, riassunto, estrazione, classificazione su categorie nuove, riformattazione, ragionamento leggero.
- La latenza tollera 1-3 secondi.
- Non ti serve determinismo bit per bit o spiegabilità stretta.
- Il volume è basso o medio: diciamo, meno di 1M chiamate API al giorno, o traffico imprevedibile a raffiche.
- Non hai o non puoi ottenere abbastanza dati di training etichettati per fare fine-tuning in modo significativo.
- Il costo per chiamata (attualmente da qualche parte tra 0,001$ e 0,05$ a seconda del modello e della lunghezza) si adatta alla tua unit economics.
Fai fine-tuning di un modello open-source (un Llama, Mistral, Qwen, o una base di dominio) quando:
- Hai un task specializzato e ricorrente con almeno qualche centinaio di esempi etichettati.
- I costi degli LLM hosted al tuo volume rendono un modello 7B fine-tuned sulla tua GPU ovviamente più economico.
- I requisiti di latenza sono stretti (meno di 200 ms): inferenza locale sul tuo hardware batte il round-trip a un’API.
- Privacy, residenza dei dati o regolazione vietano di mandare i dati fuori.
- Il task è abbastanza stretto da far sì che un piccolo specialista batta un gigantesco generalista.
Allena da zero (o allena un modello ML non-LLM da zero) quando:
- Stai costruendo modelli di embedding per il retrieval: un encoder più piccolo, più veloce, calibrato sul dominio di solito vince.
- Il task è time-series forecasting, raccomandazione, rilevamento di anomalie, o un altro problema tabellare/sequenziale dove gli LLM non sono affatto della forma giusta. Il modulo 9 si applica ancora qui, intatto.
- Stai lavorando in una modalità nuova (dati di sensori proprietari, immagini specifiche di dominio, sequenze biologiche) dove non esiste un modello pre-addestrato utile.
La grande maggioranza dei “progetti ML” nel 2026 atterra nelle prime due scatole. Una frazione significativa ma più piccola resta nella terza, e riconoscere quando l’ML classico vince ancora fa parte della competenza.
Lo schema ibrido: LLM come adapter
Le architetture di produzione più robuste non trattano l’LLM come l’intero sistema. Lo usano come un adapter cambia-forma che siede al confine, prendendo input non strutturato e disordinato (email a testo libero, trascrizioni vocali, documenti scansionati) ed emettendo dati strutturati e ordinati che una pipeline classica a valle può gestire.
from anthropic import Anthropic
import json
client = Anthropic()
PROMPT = """You will receive a customer support email. Extract:
- intent: one of [refund, technical_issue, account_question, other]
- urgency: one of [low, medium, high]
- mentions_competitor: boolean
- contains_legal_threat: boolean
Reply with ONLY a JSON object, no prose."""
def classify_email(body: str) -> dict:
msg = client.messages.create(
model="claude-opus-4-7",
max_tokens=200,
system=PROMPT,
messages=[{"role": "user", "content": body}],
)
return json.loads(msg.content[0].text)
Quell’output poi alimenta un sistema di routing deterministico, un data warehouse SQL, dashboard, alert. La parte imprevedibile, la comprensione del linguaggio naturale, è contenuta dietro un confine tipato. Il valle resta il codice noioso, testabile, auditabile su cui sei diventato bravo in decenni.
Questo è lo schema che sta davvero vincendo in produzione: LLM ai bordi, ingegneria classica al centro. Ottieni la magia dove ti serve e la prevedibilità dove ti serve quella.
RAG: lo schema dominante per “rispondi a domande sui miei dati”
Quasi ogni richiesta del tipo “costruiscimi un chatbot per la nostra documentazione” è in realtà una richiesta di retrieval-augmented generation. L’LLM non conosce i tuoi dati privati; non puoi infilare tutti i tuoi dati nella context window; il fine-tuning comunque non è un buon modo per aggiungere conoscenza. La soluzione:
- Spezza i tuoi documenti in chunk.
- Calcola un embedding per ogni chunk.
- Memorizza gli embedding in un vector index.
- Al momento della query, fai l’embedding della domanda dell’utente, trova i top-k chunk più simili e infilali nel prompt.
Tutto qui. L’LLM risponde dal contesto recuperato. Cinquanta righe di Python:
import os
from pathlib import Path
from sentence_transformers import SentenceTransformer
import chromadb
from anthropic import Anthropic
# 1. Modello di embedding e vector store
embedder = SentenceTransformer("BAAI/bge-small-en-v1.5")
chroma = chromadb.PersistentClient(path="./rag_store")
coll = chroma.get_or_create_collection("docs")
# 2. Indicizza i tuoi documenti
def chunk(text: str, size: int = 600, overlap: int = 100) -> list[str]:
chunks = []
i = 0
while i < len(text):
chunks.append(text[i:i + size])
i += size - overlap
return chunks
def index_folder(folder: str) -> None:
docs, ids, metas = [], [], []
for path in Path(folder).rglob("*.md"):
text = path.read_text(encoding="utf-8")
for j, c in enumerate(chunk(text)):
docs.append(c)
ids.append(f"{path.stem}-{j}")
metas.append({"source": str(path)})
embeddings = embedder.encode(docs, normalize_embeddings=True).tolist()
coll.upsert(ids=ids, documents=docs, embeddings=embeddings, metadatas=metas)
# 3. Query
client = Anthropic()
def answer(question: str, k: int = 4) -> str:
q_emb = embedder.encode([question], normalize_embeddings=True).tolist()
hits = coll.query(query_embeddings=q_emb, n_results=k)
context = "\n\n---\n\n".join(hits["documents"][0])
sources = [m["source"] for m in hits["metadatas"][0]]
msg = client.messages.create(
model="claude-opus-4-7",
max_tokens=600,
system=(
"Answer the question using ONLY the provided context. "
"If the answer isn't in the context, say so."
),
messages=[{
"role": "user",
"content": f"<context>\n{context}\n</context>\n\nQuestion: {question}",
}],
)
return msg.content[0].text + f"\n\nSources: {sorted(set(sources))}"
if __name__ == "__main__":
index_folder("./my_docs")
print(answer("How does our refund policy handle digital goods?"))
Quello è un sistema RAG funzionante. Tre componenti: un modello di embedding (bge-small-en-v1.5 è eccellente e minuscolo), un vector store (ChromaDB va bene fino a circa 1M chunk; per più grossi, guarda Qdrant, Weaviate o pgvector) e una chiamata LLM che consuma il contesto recuperato.
Framework come LangChain e LlamaIndex impacchettano tutto questo e aggiungono streaming, agent, riscrittura di query, ricerca ibrida. Sono utili ma pesanti: per una prima versione l’approccio diretto da 50 righe qui sopra è spesso più chiaro da debuggare. Aggiungi un framework quando ti servono davvero le sue feature, non per default.
Cosa sintonizzi in un sistema RAG vero, in ordine grossolano di impatto:
- Strategia di chunking. Split a dimensione fissa naive funzionano, ma splitter ricorsivi che rispettano i confini di frase e paragrafo funzionano meglio. Splitter consapevoli del markdown per il markdown.
- Retrieval. Top-k pure-cosine retrieval è la baseline. La ricerca ibrida (BM25 + dense) la batte. Il reranking con un cross-encoder (es.
bge-reranker-v2-m3) sui top 20-50 candidati è un altro grosso salto. - Qualità del modello di embedding. Un embedder inglese moderno va bene; per corpus multilingue o specifici di dominio, sostituisci con un modello allenato sui dati giusti.
- Template del prompt. Di’ al modello cosa conta come contesto, cosa fare quando la risposta non c’è, che formato vuoi.
- L’LLM stesso. Passa a un modello più grosso solo dopo aver sintonizzato i punti precedenti: il retrieval di solito è il collo di bottiglia, non il generatore.
La matematica del costo
Una domanda comune che guida la decisione: a quale volume il fine-tuning del tuo modello batte il pagare per chiamata?
Un calcolo grossolano. Supponiamo che il tuo task abbia in media 800 token di input e 300 token di output per chiamata. Con un modello hosted di fascia frontiera a, diciamo, 5$/M input + 25$/M output token, ogni chiamata costa:
(800 / 1_000_000) * 5 + (300 / 1_000_000) * 25
= 0.0040 + 0.0075
= $0.0115 per chiamata
A 1M chiamate/mese: 11.500$/mese. A 10M chiamate/mese: 115.000$/mese.
Un modello 7B fine-tuned self-hosted su una singola istanza A100 (attualmente circa 1,50$/ora riservata) costa circa 1.100$/mese. Può fare comodamente milioni di chiamate se il tuo budget di latenza lo permette. Il break-even è da qualche parte intorno alle 100K-200K chiamate al mese, se un 7B fine-tuned è abbastanza buono sul tuo task.
Quindi per lavoro a basso o medio volume, l’hosted è più economico e di qualità migliore. Per lavoro specializzato ad alto volume, possedere il modello vince. Il punto di incrocio continua a spostarsi: i prezzi hosted scendono di circa il 30% all’anno mentre i pesi open migliorano di circa il 30% alla stessa dimensione, quindi rifai il calcolo ogni sei mesi. La decisione non è appiccicosa.
Quando l’ML classico vince ancora, e succede più spesso di quanto si pensi
Una parte rumorosa dell’industria si comporta come se ormai tutto fosse un LLM. Non farlo. I problemi numerici, tabellari e di time-series battono ancora gli LLM su accuratezza, latenza, costo e spiegabilità, di solito di ordini di grandezza su ogni asse.
Nello specifico:
- Predire il churn da una tabella di feature dei clienti. Gradient-boosted tree, ogni volta. (Vedi lezione 54.)
- Forecast della domanda / time series. Prophet, ARIMA, o un modello deep allenato specificamente su time series. Un LLM è una scarpa da clown per questo.
- Rilevamento di anomalie su metriche. Isolation forest, control chart statistici, detector dedicati.
- Sistemi di raccomandazione. Modelli two-tower, factorization di matrici, learned-to-rank. Gli LLM a volte vengono usati come re-ranker, mai come retriever centrale.
- Computer vision su una tassonomia fissa. Il fine-tuning di un ConvNeXt o ViT batte il prompting di un VLM su costo e accuratezza.
La regola del pollice: quando l’input è naturalmente un vettore di numeri o una riga strutturata, l’ML classico di solito vince. Quando l’input è naturalmente un paragrafo di testo a forma libera o un’immagine che descriveresti a parole, un LLM ha il vantaggio. La maggior parte del lavoro di data engineering vive nella prima categoria.
Il principio: gli LLM come moltiplicatore di forza, non come sostituto
I team che stanno tirando fuori più valore dall’AI nel 2026 non sono quelli che sostituiscono ogni componente con un LLM. Sono quelli che prima impiegavano tre settimane per costruire una pipeline e ora lo fanno in tre giorni perché l’LLM si è preso cura dei pezzi disordinati a forma di testo (il parsing, la classificazione, il riassunto) che prima richiedevano sintonizzazione manuale.
L’ingegneria non è andata via. Il lavoro sui dati non è andato via. Il bisogno di test, monitoring, schema e version control non è andato via. Quello che è andato via è una specifica categoria di attrito al confine tra input umano non strutturato ed elaborazione macchina strutturata. Quella categoria era enorme. Rimuoverla sposta cosa è possibile. Non rimuove il bisogno di software engineering.
Se prendi una cosa sola da questa lezione: la domanda su ogni nuovo progetto non è più “che modello costruiamo?” È “dove vive davvero questo problema sullo spettro da prompt a fine-tune ad allenato-da-zero?” Sceglierla correttamente ti porta da “non si può fare nel nostro budget” a “esce il prossimo trimestre.” Sceglierla male brucia soldi che non avevi bisogno di spendere, in entrambe le direzioni.
Cosa viene dopo
Questa è la penultima lezione del corso. Il modulo 10 ha coperto i fondamenti del deep learning (lezioni 56-57), il workflow transfer-learning + Hugging Face (lezione 58) e la decisione AI vs ML (questa). La prossima lezione è il capstone: uno sguardo indietro a quello che hai costruito attraverso le 60 lezioni e uno sguardo avanti a dove andare poi.
Riferimenti: Anthropic Python SDK (https://docs.anthropic.com/en/api/client-sdks), OpenAI Python SDK (https://platform.openai.com/docs/libraries), sentence-transformers (https://www.sbert.net/), documentazione di ChromaDB (https://docs.trychroma.com/), modelli di embedding BAAI BGE su Hugging Face, documentazione di LlamaIndex (https://docs.llamaindex.ai/). Recupero 2026-05-01.