Fare una call API a un LLM in un Jupyter Notebook è semplice. Farlo in modo robusto in un'applicazione in produzione, con migliaia di utenti, timeout, retry, streaming e costi sotto controllo, è un'altra storia. Questa guida copre quello che ho imparato costruendo integrazioni LLM in contesti reali.
Scegliere il modello giusto per il tuo caso
Prima di scrivere una riga di codice, definisci i requisiti del tuo caso d'uso lungo tre assi: latenza accettabile (istantanea, secondi, minuti?), qualità richiesta (è un chatbot interno o un prodotto consumer?), e volume di chiamate (decine o milioni al mese?). La risposta a queste domande determina quale modello scegliere e quanto spenderai.
Setup dell'autenticazione
// Non inserire mai la chiave API nel codice sorgente
import Anthropic from '@anthropic-ai/sdk';
// Leggi sempre da variabili d'ambiente
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Valida la configurazione all'avvio dell'app
if (!process.env.ANTHROPIC_API_KEY) {
throw new Error('ANTHROPIC_API_KEY non configurata');
}
Streaming delle risposte
Lo streaming trasforma l'esperienza utente in modo drammatico. Invece di aspettare 10 secondi e ricevere tutto il testo, l'utente vede le parole apparire progressivamente. Per qualsiasi interfaccia conversazionale, lo streaming non è opzionale.
// Esempio con streaming via Server-Sent Events
async function streamRisposta(req, res) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
const stream = await client.messages.stream({
model: 'claude-opus-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: req.body.messaggio }],
});
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta') {
res.write(`data: ${JSON.stringify({ testo: chunk.delta.text })}\n\n`);
}
}
res.write('data: [DONE]\n\n');
res.end();
}
Gestione degli errori e retry
Le API LLM possono fallire per rate limiting (429), errori di rete, timeout, o overload del provider (529). Un sistema robusto deve gestire tutti questi casi con retry esponenziale e fallback.
async function chiamataConRetry(payload, maxRetry = 3) {
for (let i = 0; i < maxRetry; i++) {
try {
return await client.messages.create(payload);
} catch (err) {
const isRetryable = [429, 529].includes(err.status);
if (!isRetryable || i === maxRetry - 1) throw err;
// Backoff esponenziale: 1s, 2s, 4s...
const wait = Math.pow(2, i) * 1000;
await new Promise(r => setTimeout(r, wait));
}
}
}
Strutturare i prompt in produzione
Il system prompt è il contratto tra te e il modello. Deve essere preciso, stabile tra le versioni, e testato come qualsiasi altra parte del codice. Usa few-shot examples per i task non standard, e richiedi output strutturato (JSON) quando hai bisogno di parsare il risultato.
Prompt Caching (disponibile su claude-opus-4-6): se il tuo system prompt è lungo e stabile, usa il caching dei prompt. Riduce la latenza del 85% e i costi fino al 90% per i token della cache. ROI immediato su qualsiasi app con prompt lunghi.
Monitoraggio in produzione
- Loga ogni chiamata: modello, token input/output, latenza, costo stimato, user ID
- Monitora il tasso di errore per tipo (rate limit vs errori di rete vs timeout)
- Implementa un sistema di valutazione: campiona le risposte e fai valutare la qualità
- Imposta alert sui costi — una regressione nel prompt può triplicare i token consumati
- Versiona i tuoi prompt come codice sorgente e traccia l'impatto delle modifiche