3. Crea la tua prima chat CLI in streaming
Nel Capitolo 1 hai fatto la tua prima chiamata a un LLM e hai visto comparire una risposta completa tutta insieme. Nel Capitolo 2 hai imparato le basi concettuali dell'IA agentica e perché esiste LangChain. Ora è il momento di costruire qualcosa di pratico: un'applicazione di chat in streaming che risulti reattiva e professionale.
Perché lo streaming conta: quando fai a un LLM una domanda complessa, aspettare 10-30 secondi per una risposta completa sembra un malfunzionamento. Lo streaming consente ai token di apparire mentre vengono generati, creando un flusso conversazionale naturale. Questo capitolo costruisce un'applicazione di chat CLI con output in streaming, una corretta gestione della configurazione, capacità di debug e una robusta gestione degli errori.
Cosa costruirai: alla fine di questo capitolo, avrai uno script chat.py funzionante che:
- Esegue lo streaming delle risposte dell'LLM token per token nel terminale
- Carica le chiavi API in modo sicuro dalle variabili d'ambiente
- Gestisce diversi tipi di modelli (modelli chat vs modelli di ragionamento) con parametri appropriati
- Fornisce strumenti di debug per ispezionare ciò che viene effettivamente inviato all'LLM
- Gestisce con eleganza gli errori comuni (chiavi API mancanti, guasti di rete, input non validi)
3.1) Crea una cartella di lavoro e installa i pacchetti
Prima di scrivere qualsiasi codice, ti serve una struttura di progetto pulita e le dipendenze corrette. Questa sezione stabilisce le basi per un progetto Python facile da mantenere.
Struttura del progetto
Crea una nuova directory per la tua applicazione di chat:
mkdir langchain-chat
cd langchain-chatConfigurazione dell'ambiente Python
Crea un ambiente virtuale per isolare le dipendenze:
# Crea il virtual environment
python -m venv venv
# Attivalo (macOS/Linux)
source venv/bin/activate
# Attivalo (Windows)
venv\Scripts\activatePerché gli ambienti virtuali? LangChain ha molte dipendenze (ad es. OpenAI SDK, Pydantic, librerie async). Un ambiente virtuale garantisce:
- Il Python di sistema rimane pulito
- Progetti diversi possono usare versioni diverse di LangChain
- Le dipendenze sono riproducibili (tramite
requirements.txt)
Quando è attivato, vedrai (venv) nel prompt del terminale.
Installare LangChain
Installa i pacchetti core di LangChain:
pip install langchain-core==1.2.7 langchain-openai==1.1.7 python-dotenvDettaglio dei pacchetti:
langchain-core: Astrazioni core (messaggi, prompt, catene, runnable)langchain-openai: Implementazioni specifiche OpenAI (ChatOpenAI, embedding)python-dotenv: Carica variabili d'ambiente da file.env
Nota sulle versioni: questo libro usa LangChain 1.2.x a gennaio 2026. Se stai leggendo in futuro, controlla la documentazione di LangChain per l'ultima versione.
Verifica l'installazione
Crea un test semplice per confermare che tutto funzioni:
# test_install.py
try:
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
print("✓ langchain-core: OK")
print("✓ langchain-openai: OK")
print("\nInstallazione riuscita!")
except ImportError as e:
print(f"✗ Import fallito: {e}")
print("Assicurati che il virtual environment sia attivato.")Eseguilo:
python test_install.pyOutput previsto:
✓ langchain-core: OK
✓ langchain-openai: OK
Installazione riuscita!Se vedi "Installazione riuscita!", sei pronto a procedere. Se ottieni un errore di importazione, ricontrolla che:
- Il virtual environment sia attivato (cerca
(venv)nel prompt) - I pacchetti siano stati installati correttamente (prova a eseguire
pip list)
Creare requirements.txt
Hai appena installato pacchetti con comandi pip install. Anche se va bene per imparare, c'è un modo migliore: i file requirements.txt. È una pratica standard nei progetti Python per diverse ragioni:
Perché usare requirements.txt?
- Riproducibilità: Altri (o tu tra 6 mesi) possono installare esattamente le stesse versioni dei pacchetti
- Gestione chiara delle dipendenze: Vedi a colpo d’occhio quali pacchetti servono al progetto
- Collaborazione in team: I membri del team usano versioni identiche, evitando problemi del tipo "funziona sulla mia macchina"
- Automazione: Server o pipeline CI/CD possono configurare l'ambiente con una sola riga:
pip install -r requirements.txt
Crea un file requirements.txt nella root del progetto:
# requirements.txt
langchain-core==1.2.7
langchain-openai==1.1.7
python-dotenvNota la sintassi:
==1.2.7fissa una versione esatta (consigliato per la riproducibilità)- Nessuno specificatore di versione (come
python-dotenv) installa l'ultima versione stabile - Le righe che iniziano con
#sono commenti
Ora chiunque può installare tutte le dipendenze con un singolo comando:
pip install -r requirements.txtÈ molto meglio che digitare ogni pacchetto singolarmente. Se un compagno di team clona il tuo progetto, deve solo:
- Creare un virtual environment
- Eseguire
pip install -r requirements.txt
Non serve ricordare nomi o versioni dei pacchetti: è tutto nel file.
La struttura del tuo progetto
Dopo aver completato questa sezione, la tua cartella dovrebbe apparire così:
langchain-chat/
├── venv/ # Virtual environment (non committare su git)
├── requirements.txt # Dependency list
└── test_install.py # Installation verification scriptProssimo: la sezione 3.2 mostra come caricare in modo sicuro le chiavi API usando file .env.
3.2) Variabili d'ambiente con .env
Le chiavi API sono segreti. Scriverle direttamente nel codice è un rischio di sicurezza (specialmente se fai commit su git). Questa sezione mostra l'approccio standard: variabili d'ambiente caricate da un file .env.
Perché le variabili d'ambiente?
Il problema delle chiavi hardcoded:
# ❌ NON FARE MAI QUESTO
llm = ChatOpenAI(api_key="sk-proj-abc123...")Se fai commit di questo codice su GitHub, la tua chiave API è pubblica. Chiunque può usarla, accumulare costi sul tuo account o farsi revocare la chiave.
La soluzione: conserva i segreti in variabili d'ambiente e caricali a runtime.
Creare il file .env
Crea un file .env nella root del progetto:
# .env
OPENAI_API_KEY=sk-proj-your-actual-key-hereOttieni la tua chiave API:
- Vai su platform.openai.com/api-keys
- Crea una nuova chiave segreta (secret key)
- Copiala immediatamente (non potrai più visualizzarla)
- Incollala nel file
.env, sostituendosk-proj-your-actual-key-here
Passaggio critico di sicurezza: prima di fare qualsiasi altra cosa, proteggi la tua chiave API dall'essere committata su git.
Crea un file .gitignore nella root del progetto e aggiungi queste righe:
# .gitignore
venv/
__pycache__/
*.pyc
.envLa riga .env dice a git di ignorare il file della tua chiave API. Questo evita di committare accidentalmente segreti nel controllo versione.
La struttura del tuo progetto ora:
langchain-chat/
├── venv/
├── .env # La tua chiave API (ignorata da git)
├── .gitignore # Contiene: .env, venv/, ecc.
├── requirements.txt
└── test_install.pyCaricare le variabili d'ambiente
Il pacchetto python-dotenv carica i file .env in os.environ:
# chat.py
import os
from dotenv import load_dotenv
# Carica il file .env
load_dotenv()
# Accedi alle variabili d'ambiente
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
raise ValueError("OPENAI_API_KEY non trovato nell'ambiente")
print(f"Chiave API caricata: {api_key[:8]}...") # Mostra solo i primi 8 caratteriCome funziona load_dotenv():
- Cerca un file
.envpartendo da dove esegui lo script - Legge ogni riga nel formato
KEY=value - Aggiunge ogni variabile a
os.environ - Se una variabile è già impostata (ad es. dalla tua piattaforma di hosting), non viene sovrascritta: rimane il valore esistente
Usare la chiave API con LangChain
Le implementazioni OpenAI di LangChain (ChatOpenAI, ecc.) cercano automaticamente OPENAI_API_KEY in os.environ:
from langchain_openai import ChatOpenAI
load_dotenv()
# Questo usa automaticamente os.environ["OPENAI_API_KEY"]
llm = ChatOpenAI(model="gpt-4o-mini")La convenzione di LangChain: quando crei ChatOpenAI() senza un parametro api_key, cerca automaticamente OPENAI_API_KEY nell'ambiente. È un pattern standard in tutte le integrazioni LangChain.
Chiave API esplicita (per test o chiavi multiple):
llm = ChatOpenAI(
model="gpt-4o-mini",
api_key=os.environ.get("OPENAI_API_KEY")
)È utile quando hai più chiavi API (sviluppo vs produzione) o vuoi essere esplicito su quale chiave viene usata.
Variabili d'ambiente in produzione
Negli ambienti di produzione (piattaforme cloud, container Docker), non usi file .env. Invece, configuri le variabili d'ambiente dalle impostazioni della piattaforma:
- Docker: usa il flag
-equando esegui i container - Piattaforme cloud: imposta le variabili d'ambiente nei dashboard di configurazione
- CI/CD: usa strumenti di gestione dei segreti
La parte importante: il tuo codice non cambia. os.environ.get("OPENAI_API_KEY") funziona allo stesso modo sia che la variabile arrivi da un file .env sia da una piattaforma cloud. Tratteremo la distribuzione (deploy) in dettaglio nei capitoli successivi.
Verifica la configurazione
Per confermare che tutto funzioni, puoi testare il codice di caricamento delle variabili d'ambiente mostrato prima. Se il tuo file .env è configurato correttamente, os.environ.get("OPENAI_API_KEY") restituirà la tua chiave API.
Se os.environ.get("OPENAI_API_KEY") restituisce None, verifica che:
- Hai chiamato
load_dotenv()prima di accedere alla variabile d'ambiente .envesista nella root del progettoOPENAI_API_KEY=sk-proj-...sia scritto correttamente in.env- Stai eseguendo dalla directory root del progetto
Prossimo: la sezione 3.3 implementa il vero ciclo di chat con output in streaming.
3.3) Implementare il ciclo di chat con output in streaming
Ora costruirai il ciclo di chat principale. Questa sezione introduce lo streaming: la differenza chiave tra un chatbot lento e uno reattivo.
Comprendere lo streaming
Senza streaming (approccio del Capitolo 1):
response = llm.invoke("Scrivi un tema di 500 parole sull'IA")
print(response.content) # Aspetta 20 secondi, poi compare l'intero temaCon streaming:
for chunk in llm.stream("Scrivi un tema di 500 parole sull'IA"):
print(chunk.content, end="", flush=True) # I token appaiono mentre vengono generatiPerché lo streaming conta:
- Feedback immediato: invece di fissare uno schermo vuoto per 20 secondi, vedi comparire parole subito
- Sensazione di conversazione naturale: proprio come parlare con una persona: le risposte arrivano progressivamente, non tutte in una volta
- Risparmio di tempo e denaro: se l'LLM inizia a dare la risposta sbagliata, puoi interromperlo presto invece di aspettare una risposta completa (inutile)
- Debug migliore: quando costruisci applicazioni, puoi individuare problemi (come errori di formattazione) mentre accadono, non dopo una lunga attesa
Che cos'è davvero lo streaming: lo streaming è la consegna incrementale dello stesso testo di risposta. Non espone ragionamenti nascosti o processi interni del modello: ti mostra solo un output parziale man mano che diventa disponibile dall'API. Pensalo come scaricare un file: vedi l'avanzamento mentre arrivano i chunk, ma il contenuto del file è lo stesso sia che lo scarichi tutto in una volta sia a pezzi.
Nota sui confini dei chunk: non è garantito che i chunk si allineino con parole o frasi. L'API invia token in piccoli batch per efficienza, quindi un chunk potrebbe essere "Hel", "lo! How", " can I", " help you", "?". È normale e previsto: non cercare di interpretare il significato dei singoli chunk.
Il ciclo di chat di base
Ecco un ciclo di chat minimale in streaming:
# chat.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
def main():
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
print("Chat avviata. Digita 'quit' o 'exit' per fermarti.\n")
while True:
user_input = input("Tu: ")
if user_input.lower() in ["quit", "exit"]:
print("Arrivederci!")
break
print("Assistente: ", end="", flush=True)
for chunk in llm.stream([HumanMessage(content=user_input)]):
print(chunk.content, end="", flush=True)
print("\n")
if __name__ == "__main__":
main()Come funziona:
while True:: ciclo infinito per una conversazione continuainput("Tu: "): ottiene l'input dell'utente dal terminalellm.stream([HumanMessage(...)]): esegue lo streaming della risposta dell'LLM- Output in streaming con parametri speciali:
end="": non aggiunge una nuova riga dopo ogni chunk (mantiene l'output sulla stessa riga)flush=True: forza l'output immediato sul terminale senza buffering
Perché [HumanMessage(content=user_input)]?
I modelli chat di LangChain si aspettano una lista di messaggi, non una stringa grezza. Ogni messaggio ha un ruolo:
- HumanMessage: input dell'utente
- AIMessage: risposta dell'LLM
- SystemMessage: istruzioni per l'LLM (trattate nel Capitolo 4)
Anche per un singolo messaggio utente, passi una lista: [HumanMessage(content="Hello")].
Limite chiave - Conversazioni single-turn: questo ciclo di chat è intenzionalmente senza stato. Ogni richiesta invia solo il messaggio corrente, non la cronologia precedente. Questo significa:
- L'LLM non ricorderà cosa hai chiesto prima
- Domande di follow-up come "E la sua popolazione?" non funzioneranno dopo aver chiesto "Qual è la capitale della Francia?"
- Questa è una caratteristica fondamentale degli LLM: non hanno memoria a meno che tu non fornisca esplicitamente contesto
Esempio del limite:
Tu: Qual è la capitale della Francia?
Assistente: Parigi.
Tu: Qual è la sua popolazione?
Assistente: Non ho abbastanza contesto. Di quale città stai parlando?Il ciclo while True fornisce continuità UX (puoi continuare a chattare), ma ogni turno è indipendente. In arrivo nel Capitolo 8: implementeremo la memoria conversazionale memorizzando e reinviando la cronologia dei messaggi ad ogni richiesta.
Eseguire il ciclo di chat
python chat.pyEsempio di interazione:
Chat avviata. Digita 'quit' o 'exit' per fermarti.
Tu: Cos'è LangChain?
Assistente: LangChain è un framework per sviluppare applicazioni basate su modelli linguistici. Fornisce strumenti per la gestione dei prompt, catene, agenti e memoria.
Tu: Fammi un esempio semplice
Assistente: Ecco un esempio di base: ...
Tu: quit
Arrivederci!Comprendere la Streaming API
Che cos'è un "chunk"?
Ogni chunk è un oggetto AIMessageChunk con:
content: i token di testo generatiresponse_metadata: info del modello, conteggio token, ecc.
for chunk in llm.stream([HumanMessage(content="Ciao")]):
print(f"Chunk: {chunk}")
print(f"Content: {chunk.content}")
print(f"Type: {type(chunk)}")Output:
Chunk: content='Ciao' response_metadata={'model_provider': 'openai', ...}
Content: Ciao
Type: <class 'langchain_core.messages.ai.AIMessageChunk'>
Chunk: content='!' response_metadata={...}
Content: !
Type: <class 'langchain_core.messages.ai.AIMessageChunk'>
Chunk: content=' Come' response_metadata={...}
Content: Come
Type: <class 'langchain_core.messages.ai.AIMessageChunk'>Accumulare la risposta completa
A volte ti serve la risposta completa (per logging, test o ulteriore elaborazione):
def chat_with_accumulation():
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
user_input = input("Tu: ")
full_response = ""
print("Assistente: ", end="", flush=True)
for chunk in llm.stream([HumanMessage(content=user_input)]):
print(chunk.content, end="", flush=True)
full_response += chunk.content
print("\n")
# Ora hai la risposta completa
print(f"[DEBUG] Lunghezza risposta completa: {len(full_response)} caratteri")
return full_responseQuesto pattern è comune quando devi:
- Salvare la conversazione in un database
- Effettuare il parsing della risposta per dati strutturati
- Calcolare l'uso dei token o i costi
La struttura del tuo progetto dopo questa sezione:
langchain-chat/
├── venv/
├── .env
├── .gitignore
├── requirements.txt
├── test_install.py
└── chat.py # Streaming chat loop (new!)Prossimo: la sezione 3.4 mostra come gestire diversi tipi di modello con una configurazione intelligente dei parametri.
3.4) Configurazione intelligente: gestire i parametri per modelli di ragionamento vs modelli chat
OpenAI offre due tipi di modelli con capacità e meccanismi di controllo differenti:
Modelli chat (gpt-4o, gpt-4o-mini):
- Veloci e conversazionali
- Supportano
temperatureper controllare casualità e creatività - Migliori per compiti generali, scrittura creativa, coding di routine
Modelli di ragionamento (o1, o3, GPT-5):
- Più lenti ma più logici e consistenti
- NON supportano
temperature(usano invece il ragionamento interno) - Migliori per matematica complessa, pianificazione multi-step, analisi formale
La differenza chiave: i modelli chat usano campionamento probabilistico (controlli la casualità), mentre i modelli di ragionamento usano logica interna deterministica (il modello controlla il proprio processo di ragionamento).
Comprendere la temperature (solo modelli chat)
Che cos'è la temperature?
La temperature è un numero tra 0.0 e 2.0 che controlla quanto creative siano le risposte del modello. A valori bassi (vicino a 0), ottieni risposte consistenti e prevedibili. A valori alti (vicino a 2.0), ottieni risposte creative e variate. Pensala come una "manopola della creatività".
Come funziona: quando genera ogni parola, il modello vede molte possibili parole successive con probabilità diverse. La temperature influenza come il modello sceglie:
- Temperature bassa (0.0): sceglie quasi sempre la parola con probabilità più alta → risposte consistenti e focalizzate
- Temperature alta (2.0): più probabile che scelga parole con probabilità più bassa → risposte diverse e creative
Importante: la temperature funziona solo con i modelli chat (gpt-4o, gpt-4o-mini). Non si applica ai modelli di ragionamento (GPT-5, o1, o3), che usano logica interna invece del campionamento probabilistico.
Guida ai valori della temperature:
-
0.0: altamente deterministico, focalizzato e consistente
- Usalo per: Q&A fattuale, generazione di codice di routine, output strutturato
- Stesso input → output quasi identico ogni volta
- Esempio: "Quanto fa 2+2?" → Sempre "4"
-
0.7–1.0: comportamento standard di campionamento (default è 1.0)
- Usalo per: conversazione generale, spiegazioni, risposte bilanciate
- Variazione moderata in formulazioni ed esempi
- Esempio: "Spiega la fotosintesi" → formulazione diversa ogni volta, stesse info di base
-
1.2–2.0: più creativo e vario, meno prevedibile
- Usalo per: scrittura creativa, brainstorming, ideazione
- Alta variazione in tono, struttura e formulazione
- Esempio: "Scrivi una poesia sulla luna" → stili molto diversi ogni volta
Nota: valori sopra 1.0 aumentano la creatività ma possono ridurre accuratezza fattuale e coerenza. Il valore massimo è 2.0.
Esempio: impatto della temperature (solo modelli chat)
# Temperature 0.0 - deterministico, stessa risposta ogni volta
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
response = llm.invoke([HumanMessage(content="Quanto fa 2+2?")])
print(response.content) # Output: 4
# Temperature 1.0 - comportamento di default, possibile una leggera variazione
llm = ChatOpenAI(model="gpt-4o-mini", temperature=1.0)
response = llm.invoke([HumanMessage(content="Quanto fa 2+2?")])
print(response.content) # Output: 4 (può includere una breve spiegazione)Per domande chiuse e fattuali, la temperature ha poco effetto sulla correttezza.
Per compiti open-ended o creativi, la temperature influenza significativamente varietà, tono e stile.
Cosa succede se usi parametri dei modelli chat sui modelli di ragionamento?
Dipende dal modello: alcuni lo rifiutano, altri lo ignorano silenziosamente:
# ❌ Questo fallirà con i modelli o3
llm = ChatOpenAI(model="o3-mini", temperature=0.7)Errore:
BadRequestError: Temperature is not supported with this modelModelli diversi, criteri diversi:
- Modelli o1 / o3: rifiutano esplicitamente i parametri non supportati. Se la temperature è inclusa, l'API restituisce subito un errore 400 BadRequest.
- Modelli GPT-5: più permissivi: il parametro è accettato ma viene ignorato silenziosamente. La richiesta va a buon fine, ma la temperature non ha effetto.
Perché conta: controlla sempre quale modello stai usando e configura i parametri di conseguenza. Usare i parametri sbagliati può causare errori o fallire silenziosamente, sprecando tempo di debug.
Come controllare il comportamento dei modelli di ragionamento
Ora sai che i modelli chat usano temperature e i modelli di ragionamento no. Quindi come controlli i modelli di ragionamento?
I modelli di ragionamento vengono regolati tramite progettazione del prompt, non tramite parametri:
- I modelli di ragionamento non espongono
temperatureo controlli simili - Invece, guidi il comportamento con come scrivi il prompt:
- Istruzioni esplicite: "Pensa passo dopo passo", "Mostra i passaggi"
- Vincoli come regole: "Non devi assumere...", "Verifica sempre..."
- Requisiti strutturati: "Output in formato JSON", "Includi il ragionamento prima della risposta"
- Logica decisionale: "Se condizione A, allora fai X, altrimenti fai Y"
Esempio: parametri chat vs prompt di ragionamento
# ❌ Approccio chat - non funziona con i modelli di ragionamento
llm = ChatOpenAI(model="o3-mini", temperature=0.5)
# Error: BadRequestError: Temperature is not supported
# ✅ Approccio di ragionamento - guida attraverso la struttura del prompt
prompt = """
Risolvi questo problema passo dopo passo:
1. Indica cosa sai
2. Mostra i calcoli
3. Verifica la risposta
Problema: Se x + 5 = 12, quanto vale x?
"""
llm = ChatOpenAI(model="o3-mini")
response = llm.invoke([HumanMessage(content=prompt)])
print(response.content)Output:
1. Cosa so: x + 5 = 12
2. Calcoli: x = 12 - 5 = 7
3. Verifica: 7 + 5 = 12 ✓
Risposta: x = 7Intuizione chiave: i modelli chat si controllano con parametri, i modelli di ragionamento si controllano con prompt.
Tabella decisionale per la selezione del modello
Ora che capisci come controllare entrambi i tipi di modelli, ecco quando usare ciascuno:
| Tipo di attività | Modello consigliato | Perché |
|---|---|---|
| Conversazione generale | gpt-4o-mini | Veloce, economico, conversazionale |
| Q&A semplice | gpt-4o-mini | Sufficiente per consultazione fattuale |
| Scrittura creativa | gpt-4o-mini (temp 0.8–1.0) | La temperature abilita la creatività |
| Generazione di codice | GPT-5 | Pianificazione logica migliore |
| Ragionamento complesso | GPT-5 | Ottimizzato per logica multi-step |
| Problemi di matematica | o3 / o1 | Modelli di ragionamento dedicati |
| Pianificazione multi-step | GPT-5 | Forte nella pianificazione a lungo orizzonte |
| Analisi formale (legale/policy) | o3 | Rigorosamente deterministico |
Compromessi tra costo e latenza
Comprendere i compromessi pratici ti aiuta a scegliere il modello giusto per il tuo caso d'uso:
| Tipo di modello | Velocità (latenza tipica) | Costo (relativo) | Ideale per |
|---|---|---|---|
| gpt-4o-mini | Molto veloce (<2s) | Molto basso | Conversazione generale, compiti semplici |
| gpt-4o | Veloce (1–4s) | Medio | Chat di qualità superiore, compiti multimodali |
| GPT-5 | Moderato (3–8s) | Alto | Ragionamento complesso, pianificazione |
| o1 / o3 | Il più lento (5–15s+) | Il più alto | Ragionamento deterministico, logica formale |
Note:
- La velocità riflette la latenza tipica della risposta (varia in base a lunghezza e complessità del prompt)
- Il costo è un confronto relativo: controlla i prezzi attuali sul sito di OpenAI
- I modelli di ragionamento scambiano velocità e costo per consistenza e correttezza
- I modelli chat privilegiano reattività ed efficienza
Quando usare modelli di ragionamento (GPT-5, o1, o3):
- Problemi di matematica e STEM(Science, Technology, Engineering, Mathematics) multi-step che richiedono passaggi intermedi corretti
- Analisi logiche complesse con dipendenze e vincoli
- Debug di codice con molteplici cause interagenti
- Attività di pianificazione con molte regole, casi limite o compromessi
- Workflow di agenti che richiedono consistenza e pensiero a lungo orizzonte
Quando usare modelli chat (gpt-4o, gpt-4o-mini):
- Conversazione generale e chat interattiva
- Q&A semplice con profondità di ragionamento limitata
- Generazione di contenuti (blog, riassunti, scrittura creativa)
- Generazione di codice di routine e compiti di boilerplate
- Applicazioni in cui velocità e costo contano più del ragionamento profondo
Prossimo: la sezione 3.5 mostra tecniche di debug per ispezionare ciò che viene effettivamente inviato all'LLM.
3.5) Debugging: ispezionare risposte e uso dei token
Quando il tuo LLM si comporta in modo inaspettato, devi vedere esattamente cosa è stato inviato e ricevuto. Questa sezione mostra come ispezionare le chiamate all'LLM e fare debug dei problemi.
Perché il debugging conta
Scenari comuni di debugging:
- "Perché l'LLM ha dato questa risposta?" → controlla il prompt esatto
- "Quanto è costata questa richiesta?" → controlla l'uso dei token
- "Perché è così lento?" → misura la latenza
- "La mia formattazione dei messaggi è corretta?" → ispeziona la struttura del messaggio
La sfida: quando chiami llm.invoke(), ottieni un oggetto response. Ma cosa c'è davvero dentro? Quali informazioni sono disponibili per il debugging?
Comprendere l'oggetto response
Prima di fare debugging, devi capire cosa restituisce llm.invoke().
Struttura di base:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])
# Cosa c'è nella response?
print(type(response)) # AIMessage
print(response.content) # Il testo vero e proprio
print(response.response_metadata) # Uso token, info modello, ecc.Output:
<class 'langchain_core.messages.ai.AIMessage'>
Ciao! Come posso aiutarti oggi?
{
'token_usage': {
'completion_tokens': 9,
'prompt_tokens': 8,
'total_tokens': 17
},
'model_name': 'gpt-4o-mini-2024-07-18',
'finish_reason': 'stop',
...
}Parti chiave della response:
response.content: il testo generato dall'LLMresponse.response_metadata: dizionario con:token_usage: quanti token sono stati usati (per il calcolo dei costi)model_name: versione esatta del modello che ha rispostofinish_reason: perché la generazione si è fermata (vedi sezione Modalità debug per dettagli)
Accedere all'uso dei token:
token_usage = response.response_metadata['token_usage']
print(f"Token del prompt: {token_usage['prompt_tokens']}")
print(f"Token della risposta: {token_usage['completion_tokens']}")
print(f"Totale: {token_usage['total_tokens']}")Output:
Token del prompt: 8
Token della risposta: 9
Totale: 17Perché conta: ti servono questi valori per debugging, tracciamento dei costi e ottimizzazione dei prompt.
Calcolare i costi a partire dall'uso dei token
L'uso dei token determina il costo. Ogni modello ha un prezzo diverso:
gpt-4o-mini (a gennaio 2026):
- Input: $0.15 per 1M token
- Output: $0.60 per 1M token
GPT-4o:
- Input: $2.50 per 1M token
- Output: $10.00 per 1M token
Funzione per il calcolo dei costi:
def calculate_cost(token_usage, model_name):
"""Calcola il costo in base all'uso dei token."""
prompt_tokens = token_usage.get('prompt_tokens', 0)
completion_tokens = token_usage.get('completion_tokens', 0)
# Prezzi per 1M token (a gennaio 2026)
pricing = {
'gpt-4o-mini': {'input': 0.15, 'output': 0.60},
'gpt-4o': {'input': 2.50, 'output': 10.00},
'gpt-5': {'input': 1.25, 'output': 10.00},
}
if model_name not in pricing:
return None
input_cost = (prompt_tokens / 1_000_000) * pricing[model_name]['input']
output_cost = (completion_tokens / 1_000_000) * pricing[model_name]['output']
return input_cost + output_cost
# Esempio
response = llm.invoke([HumanMessage(content="Spiega il quantum computing")])
token_usage = response.response_metadata['token_usage']
cost = calculate_cost(token_usage, "gpt-4o-mini")
print(f"Costo: ${cost:.6f}")Output:
Costo: $0.000123Perché conta: le app in produzione possono gestire 50.000+ richieste/giorno. A $0.002 per richiesta, sono $3.000/mese. Usa il modello sbagliato o prompt gonfiati, e i costi saltano a $30.000/mese. Un bug nel loop di retry può bruciare migliaia di dollari in una notte. Traccia l'uso dei token dal primo giorno.
Abilitare la modalità debug (quando ti servono i dettagli grezzi dell'API)
L'oggetto response e un wrapper personalizzato coprono la maggior parte delle esigenze di debug. Ma a volte ti serve vedere esattamente cosa LangChain invia a OpenAI: la request e la response JSON grezze.
Quando potrebbe servirti:
- Debug della formattazione dei messaggi di LangChain
- Verificare che i parametri dell'API siano impostati correttamente
- Investigare errori inattesi dell'API
- Capire il payload esatto dell'API
LangChain ha logging di debug integrato tramite langchain_core.globals:
from langchain_core.globals import set_debug
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
set_debug(True)
# Ora tutte le chiamate LLM stamperanno info di debug
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])Output:
[llm/start] [llm:ChatOpenAI] Entering LLM run with input:
{
"prompts": [
"Human: Hello"
]
}
[llm/end] [llm:ChatOpenAI] [1.45s] Exiting LLM run with output:
{
"generations": [
[
{
"text": "Ciao! Come posso aiutarti oggi?",
"generation_info": {
"finish_reason": "stop",
"logprobs": null
},
"type": "ChatGeneration",
...
}
]
],
"llm_output": {
"token_usage": {
"completion_tokens": 9,
"prompt_tokens": 8,
"total_tokens": 17,
...
},
"model_provider": "openai",
"model_name": "gpt-4o-mini-2024-07-18",
...
},
}Nota: il formato dell'output varia in base al provider LLM. Questo esempio mostra la struttura di OpenAI.
Cosa rivela l'output di debug:
La modalità debug mostra il flusso completo di comunicazione LangChain → OpenAI:
1. Trasformazione del formato dei messaggi:
# Il tuo codice
[HumanMessage(content="Hello")]
# Quello che vedi nell'output di debug
{
"prompts": ["Human: Hello"]
}La modalità debug mostra come LangChain rappresenta internamente il tuo messaggio prima di inviarlo all'LLM.
2. Stato di completamento della generazione:
"finish_reason": "stop"Perché la generazione è terminata:
"stop": il modello ha completato la risposta naturalmente"length": la risposta è stata troncata perché ha raggiunto il limite max_tokens"tool_calls": il modello ha terminato la generazione producendo istruzioni di chiamata a strumenti invece di una risposta finale testuale (Capitolo 12)"content_filter": la risposta è stata bloccata o soppressa a causa di regole di sicurezza o moderazione dei contenuti
Se vedi "length", aumenta max_tokens per ottenere la risposta completa.
3. Dettaglio dell'uso dei token:
"token_usage": {
"completion_tokens": 9,
"prompt_tokens": 8,
"total_tokens": 17,
"completion_tokens_details": {
"reasoning_tokens": 0 # Per i modelli di ragionamento (o1/o3, ecc.)
},
"prompt_tokens_details": {
"cached_tokens": 0 # Prompt caching (riduce i costi)
}
}Oltre ai conteggi base, puoi vedere:
- reasoning_tokens: passaggi di ragionamento interno (solo per modelli di ragionamento)
- cached_tokens: quanti token del prompt sono stati serviti da cache (riduce il costo)
4. Versione del modello e fingerprint:
"model_name": "gpt-4o-mini-2024-07-18",
"system_fingerprint": "fp_8bbc38b4db"- model_name: versione snapshot esatta (spiega perché le risposte cambiano nel tempo)
- system_fingerprint: ID di configurazione backend di OpenAI (cambia quando aggiornano i sistemi)
5. Timing della richiesta:
[llm/end] [llm:ChatOpenAI] [1.56s]Il [1.45s] mostra la durata totale della richiesta: utile per identificare query lente.
Prossimo: la sezione 3.6 mostra come gestire con eleganza gli errori comuni.
3.6) Gestire i fallimenti (simulare e risolvere errori comuni)
Le applicazioni LLM in produzione affrontano modalità di fallimento prevedibili: credenziali mancanti, timeout di rete, rate limit e input non validi. Questa sezione mostra come gestire questi errori con eleganza e costruire applicazioni robuste fin dal primo giorno.
I sei errori comuni
1. Chiave API mancante
Quando succede: provi a creare un'istanza ChatOpenAI, ma OPENAI_API_KEY non è impostata nel tuo ambiente.
Esempio:
# Il file .env non esiste, oppure OPENAI_API_KEY non è definita
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])Errore che vedrai:
OpenAIError: The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variableCome risolvere:
- Controlla che il file
.envesista nella root del progetto - Verifica che il nome della chiave sia esattamente
OPENAI_API_KEY(errore comune:OPENAPI_KEY) - Assicurati che
load_dotenv()venga chiamato prima di creare l'LLM
2. Chiave API sbagliata
Quando succede: il file .env contiene una chiave API non valida, scaduta o copiata male.
Esempio:
# .env contiene: OPENAI_API_KEY=sk-invalid-key-12345
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])Errore che vedrai:
AuthenticationError: Incorrect API key providedCome risolvere:
- Vai su https://platform.openai.com/api-keys
- Verifica che la chiave sia ancora attiva (non revocata o scaduta)
- Genera una nuova chiave se necessario
- Copia con attenzione l'intera chiave (errore comune: mancano i primi/ultimi caratteri)
- Incolla in
.envsenza spazi extra:
OPENAI_API_KEY=sk-proj-exactkeyhere3. Guasti di rete
Quando succede: la connessione internet cade, oppure i server di OpenAI sono temporaneamente irraggiungibili durante una richiesta.
Esempio:
# Il WiFi si disconnette a metà richiesta, oppure l'API di OpenAI è down
response = llm.invoke([HumanMessage(content="Hello")])Errore che vedrai:
APIConnectionError: Connection errorCome risolvere:
- Controlla la connessione internet
- Verifica lo stato di OpenAI su https://status.openai.com
4. Rate limit
Quando succede: invii troppe richieste in poco tempo e superi la quota API.
Esempio:
# Invio di 1000 richieste istantaneamente
for i in range(1000):
llm.invoke([HumanMessage(content=f"Request {i}")])Errore che vedrai:
RateLimitError: Rate limit reached for requestsCome risolvere:
- Controlla i tuoi rate limit su https://platform.openai.com/account/limits
- Fai upgrade del piano se ti servono limiti più alti
- Usa elaborazione batch per carichi grandi (trattato nel Capitolo 6)
5. Nome modello non valido
Quando succede: specifichi un nome modello che non esiste o non è disponibile nel tuo piano.
Esempio:
llm = ChatOpenAI(model="gpt-99-ultra") # Non esiste
response = llm.invoke([HumanMessage(content="Hello")])Errore che vedrai:
NotFoundError: The model `gpt-99-ultra` does not exist or you do not have access to itCome risolvere:
- Controlla i modelli disponibili nel tuo piano su https://platform.openai.com/docs/models
6. Limite token superato
Quando succede: il prompt è troppo lungo e supera la finestra di contesto massima del modello.
Esempio:
# Creazione di un prompt da 1 milione di caratteri
huge_prompt = "x" * 1_000_000
response = llm.invoke([HumanMessage(content=huge_prompt)])Errore che vedrai:
BadRequestError: This model's maximum context length is 128000 tokens. However, your messages resulted in 250000 tokens.Come risolvere:
- Controlla la lunghezza dell'input prima di inviare
- Conosci i limiti del modello:
- gpt-4o-mini: 128K token
- gpt-4o: 128K token
- gpt-5: 400K token
- Per documenti lunghi, usa chunking o summarization (trattato nel Capitolo 9)
Prossimi passi: il Capitolo 4 mostra come progettare template di prompt riutilizzabili che separano il prompt engineering dal codice applicativo.