Il problema: state costruendo un prototipo che non reggerà mai il traffico reale
Avete un LLM che risponde, una chiamata API che funziona in console e un entusiasmo che dura fino al primo caricamento lento di un endpoint. Poi arriva il cliente: “ma quando lo mettiamo online?”. E lì scoprite che il vostro codice Python in un notebook non sa cosa sia un load balancer, che i token costano come il caffè di una settimana e che rispondere a domande su manuali di prodotto significa costruire una pipeline RAG da zero.
Noi, di Meteora Web, ci siamo passati. E abbiamo smesso di contare i prototipi lasciati a metà. Per questo abbiamo scritto questa guida pillar: per dare a voi sviluppatori una mappa chiara del percorso che va dalla prima ChatOpenAI fino a un'app LLM pronta per il mondo reale.
Qui sotto trovate i fondamentali: LangChain, RAG, vettor database, LlamaIndex, LangSmith, embeddings, streaming, fine‑tuning, deployment e gestione dei costi. Ogni sezione finisce con qualcosa che potete mettere in produzione oggi.
LangChain fondamentali: chain, prompt template e output parser
LangChain non è un framework magico: è un layer di astrazione che trasforma chiamate API LLM in catene di operazioni componibili. Il cuore sono tre blocchi.
Prompt template: non scrivete mai stringhe in chiaro
Un prompt hard‑codato è il primo passo verso un codice fragile. I template vi permettono di separare logica e contenuto.
from langchain.prompts import PromptTemplate
template = """Sei un assistente per un negozio di abbigliamento.
Il cliente chiede: {domanda}
Rispondi in modo chiaro e professionale."""
prompt = PromptTemplate.from_template(template)
print(prompt.format(domanda="Quali sono i vostri pantaloni più venduti?"))
Perché conviene: modificate il template senza toccare codice. Gestite variabili dinamiche, traduzioni, toni diversi.
Output parser: uscite strutturate, non testo libero
Un LLM che risponde in JSON è più utile di uno che scrive paragrafi. I parser trasformano il testo in oggetti Python.
from langchain.output_parsers import CommaSeparatedListOutputParser
parser = CommaSeparatedListOutputParser()
# "rosso, verde, blu" -> ["rosso", "verde", "blu"]
Noi usiamo PydanticOutputParser per ottenere oggetti validati. Meno bug, più velocità.
Sponsored Protocol
Chain: il tubo che unisce tutto
from langchain.chains import LLMChain
chain = LLMChain(
llm=ChatOpenAI(model="gpt-4o-mini"),
prompt=prompt,
output_parser=parser
)
response = chain.run(domanda="Quali sono i tre modelli più venduti?")
Azione concreta: sostituite il vostro response = client.chat(...) con una LLMChain e un parser strutturato. In 30 minuti il codice diventa manutenibile e testabile.
RAG con LangChain: recuperare documenti reali, non allucinare
Un LLM da solo non conosce i vostri dati aziendali. La Retrieval Augmented Generation risolve questo: recuperate frammenti di documenti da un database vettoriale e li iniettate nel prompt.
Il flusso RAG in 4 passi
- Caricate un documento (PDF, HTML, testo)
- Lo dividete in chunk significativi
- Ogni chunk viene convertito in embedding e salvato in un vector store
- Alla domanda, trovate i chunk più simili e li unite al prompt
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
loader = TextLoader("manuale_prodotto.txt")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
Poi nella chain:
from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o-mini"),
chain_type="stuff",
retriever=retriever
)
response = qa_chain.run("Come si resetta il modulo WiFi?")
Attenzione: il tipo stuff funziona solo se i chunk non superano la finestra di contesto. Per documenti lunghi usate map_reduce o refine.
Azione concreta: prendete un file PDF di 10 pagine, costruite una Chroma locale e testate le risposte. Poi misurate il tempo di retrieval: sotto 1 secondo è ok. Oltre, serve ottimizzare chunk e embedding.
Vector Database: quando Chroma non basta più
Chroma è perfetto per prototipi e progetti small. Ma se avete milioni di documenti o richiedete latenza sotto i 50 ms, dovrete salire di livello.
- Chroma: leggero, open source, ideale per sviluppo e demo. Si installa con pip e salva i dati su disco.
- Pinecone: serverless, indici automatici, gestito. Pagate per throughput e storage. Ottimo quando non volete amministrare un server.
- Weaviate: containerizzato, ibrido vettoriale + lessicale, scalabile orizzontalmente. Per chi ha esigenze enterprise e un team DevOps.
# Esempio con Weaviate (client v3)
import weaviate
client = weaviate.Client("http://localhost:8080")
client.schema.create_class({
"class": "Prodotto",
"properties": [
{"name": "nome", "dataType": ["string"]},
{"name": "descrizione", "dataType": ["text"]}
]
})
Quando passare da Chroma: quando il vostro dataset supera 100k chunk oppure quando la concorrenza supera 10 richieste al secondo. Noi lo abbiamo fatto per una piattaforma di e‑commerce con 500k prodotti: Weaviate in cluster Docker ha tenuto senza problemi.
Sponsored Protocol
LlamaIndex: un data framework pensato per i knowledge worker
LlamaIndex (ex GPT Index) è alternativa o complemento a LangChain. Mentre LangChain è generico e incentrato sulle chain, LlamaIndex è specializzato nella gestione dei dati: indicizzare, indicizzare, indicizzare.
Vantaggi concreti:
- connettori nativi per oltre 100 formati (Google Drive, Notion, SQL, Slack)
- gestione automatica degli chunk e della gerarchia documentale
- supporto a indici vettoriali, alberi di keyword e indici sintetici per riassunti
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("Quali sono i punti salienti del bilancio 2025?")
Noi lo usiamo quando il cliente ha dati eterogenei (fatture, email, manuali) e vuole un’unica interfaccia di domanda. La curva di apprendimento è dolce, ma attenti: la personalizzazione delle query è meno flessibile di LangChain.
LangSmith: debuggare e monitorare le vostre app LLM
Quando un LLM in produzione risponde male, dovete sapere perché. LangSmith è la piattaforma di osservabilità di LangChain: registra ogni run, mostra i prompt generati, i token spesi, i tempi di risposta.
Noi lo usiamo per:
- tracciare regressioni dopo un cambio di modello
- confrontare risposte di GPT‑4 vs GPT‑4o‑mini su un dataset di test
- trovare allucinazioni analizzando i chunk recuperati
Setup minimo: export LANGCHAIN_API_KEY=... LANGCHAIN_TRACING_V2=true. Poi ogni chain viene tracciata automaticamente. Costo: quasi zero (free tier generoso).
Sponsored Protocol
Azione concreta: attivate il tracing sulla vostra RAG chain e lanciate 20 query di test. Guardate il dashboard: il tempo di retrieval è sotto controllo? I chunk sono pertinenti? Se vedete chunk irrilevanti, cambiate il text splitter o il modello di embedding.
Embeddings: la base della ricerca semantica
Gli embeddings sono vettori numerici che rappresentano il significato di un testo. Più due testi sono simili, più i loro vettori sono vicini (cosine similarity).
Come scegliere il modello di embedding?
- OpenAI Embeddings (text‑embedding‑3‑small/large): ottimi per qualità e semplicità, ma hanno un costo per token e i dati passano su server esterni.
- Sentence‑Transformers (locali): installazione con
pip install sentence-transformers. Gratis, veloci con GPU, privacy totale. - BGE‑M3 (multilingua): se i vostri documenti sono in italiano, funziona meglio di modelli solo inglesi.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-m3")
embedding = model.encode("Il modem non si connette").tolist()
Errore comune: usare embedding di modello diverso tra indicizzazione e query. Il vector store non capisce la differenza di spazio latente. State sempre sullo stesso modello.
Streaming delle risposte LLM: l’UX che fa la differenza
Un utente non aspetta 10 secondi per una risposta completa. Lo streaming manda i token man mano che vengono generati: l’utente vede la risposta mentre nasce. Con FastAPI e LangChain è lineare.
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
llm = ChatOpenAI(
model="gpt-4o-mini",
streaming=True,
callbacks=[StreamingStdOutCallbackHandler()]
)
Per integrarlo in un’app web con FastAPI: usate StreamingResponse e un callback personalizzato che scrive su un generatore.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from langchain.callbacks.base import BaseCallbackHandler
class StreamHandler(BaseCallbackHandler):
def __init__(self): self.queue = []
def on_llm_new_token(self, token, **kwargs):
self.queue.append(token)
app = FastAPI()
@app.post("/chat")
async def chat(prompt: str):
handler = StreamHandler()
chain = LLMChain(
llm=ChatOpenAI(streaming=True, callbacks=[handler]),
prompt=prompt_template
)
chain.run(prompt)
async def generate():
for token in handler.queue:
yield token
return StreamingResponse(generate(), media_type="text/plain")
Azione concreta: aggiungete streaming al vostro chatbot locale. Misurate la latenza percepita: se il primo token arriva entro 500 ms, l’utente resta. Se no, ottimizzate la rete o cambiate modello.
Sponsored Protocol
Fine‑tuning LLM: quando serve e quando no
Il fine‑tuning non è per tutti. Serve quando avete dati strutturati in coppie input‑output e volete specializzare il modello su un dominio stretto (es. linguaggio medico, contratti legali).
Ma attenti:
- il fine‑tuning non corregge allucinazioni su fatti assenti nel training set
- costa: addestramento su GPU + hosting del modello
- richiede migliaia di esempi di qualità
Quando invece non serve: se potete raggiungere lo stesso risultato con prompt engineering + RAG. Il 90% dei casi si risolve senza fine‑tuning.
Se decidete di provarlo, usate Unsloth o Axolotl per QLoRA – riducono la memoria GPU del 70%.
# Con Unsloth
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/llama-3-8b-bnb-4bit",
max_seq_length=2048,
dtype=None,
load_in_4bit=True,
)
Regola empirica: se il vostro dataset ha meno di 500 esempi, non fatelo. Usate RAG. Se ne avete 5000+, valutate il fine‑tuning con QLoRA su un modello piccolo (7B).
Deployment di app AI: Gradio, Streamlit e FastAPI
Il prototipo funziona in locale. Ora va messo online. Tre strade.
- Gradio: perfetto per demo e prototipi veloci. Con poche righe avete un’interfaccia web con slider, chat, file upload.
- Streamlit: più flessibile, ideale per dashboard AI con visualizzazioni. Noi lo usiamo per tool interni di analisi documentale.
- FastAPI: l’unica scelta per API di produzione. Gestisce CORS, rate limiting, autenticazione. Combinate con Docker e un reverse proxy (Nginx/Caddy).
# FastAPI endpoint RAG
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Query(BaseModel):
text: str
@app.post("/rag")
async def rag_endpoint(query: Query):
result = qa_chain.run(query.text)
return {"response": result}
Noi di Meteora Web abbiamo costruito una piattaforma proprietaria di gestione social con Laravel/Livewire, ma per i servizi AI usiamo FastAPI in container Docker su server Linux. Perché? Controllo totale e nessun canone a vita. Lo stesso stack che usiamo per i nostri clienti.
Sponsored Protocol
Costi LLM: come ottimizzare token e budget API
Un progetto che spende 200 € al mese in API OpenAI può diventare 2000 € se non si controllano i token. Ecco le nostre leve.
- Cache delle risposte: se la stessa domanda viene ripetuta, non chiamate l’API. Usate Redis o un dizionario in‑memory per query già viste.
- Modelli più piccoli: GPT‑4o‑mini costa 0.15 $/M input token vs 2.5 $/M di GPT‑4o. Per il 90% delle applicazioni la qualità è sufficiente.
- Chunk ottimizzati: se ogni RAG vi restituisce 4000 token di contesto, il costo si moltiplica. Provate chunk da 500 token.
- Embedding batch: fate embedding di centinaia di documenti in un’unica chiamata API invece che una alla volta.
Strumenti di monitoraggio: LangSmith traccia i token, ma potete anche loggare su un database e calcolare il costo a sessione utente. Noi lo facciamo in ogni progetto: il report mensile mostra costo / domanda. Se supera 0.01 €, interveniamo.
In sintesi — cosa fare adesso
- Costruite una RAG locale con Chroma e un manuale PDF. Non ci vuole un pomeriggio.
- Aggiungete streaming al vostro endpoint e testate la UX dal browser.
- Attivate LangSmith tracing su una chain esistente. Scoprirete cose che non sapevate.
- Calcolate il costo per query con il vostro modello attuale. Se supera 0.005 €, iniziate a ottimizzare.
- Scegliete lo stack di deployment: per API pubbliche, FastAPI + Docker. Per demo clienti, Gradio su una VPS.
Non fermatevi al prototipo. Il divario tra demo e produzione si colma con queste pratiche. Noi lo facciamo tutti i giorni per aziende in Sicilia e in tutta Italia. E se volete un confronto tecnico, contattateci.