Modelli LLM e API
Verboo, Venice AI e Drael vs NotHoly per la sicurezza
Verboo, Venice AI e Drael a confronto con NotHoly, novembre 2026: fatturazione, API, licenza, basi dei modelli e dove girano gli strumenti, con fonti.
Come è fatta una richiesta a un'API di chat: modello, messaggi, token, streaming, chiavi API, limiti di richieste ed errori HTTP, con esempi commentati.
Un'API LLM è un endpoint web che riceve testo e restituisce la risposta di un modello. Il tuo programma manda una richiesta HTTP con il nome di un modello, un elenco di messaggi e un limite di token. Il servizio rimanda il testo generato, il motivo per cui si è fermato e il conteggio dei token letti e scritti. Un'app di chat fa lo stesso lavoro dietro una casella di testo. L'API dà al tuo codice i pezzi grezzi, così è il codice a decidere cosa entra e cosa succede alla risposta.
In un'app di chat il modello sembra un interlocutore. Dietro le quinte, ogni messaggio che mandi è una richiesta HTTP completa. L'app raccoglie i messaggi precedenti, aggiunge quello nuovo, manda tutto a un server e mostra il testo che torna. Il modello non ricorda il tuo ultimo messaggio a meno che l'app non rimandi la cronologia.
Un'API fa a meno dell'interfaccia. Il tuo codice costruisce l'elenco dei messaggi, imposta il limite di token e gestisce la risposta. Al servizio non interessa se chi chiama è un browser, uno script o un job pianificato. Controlla il formato della richiesta e la chiave.
Tre termini contano fin dall'inizio:
Una finestra di chat permette a una persona di fare domande. Il codice ha bisogno di input ripetibili, output che può analizzare ed errori che può intercettare. Appena uno script chiama un modello, la chiave, il conto e i modi in cui può fallire diventano responsabilità tua. La maggior parte dei problemi delle prime integrazioni viene da un corpo malformato, una chiave mancante o sbagliata, un limite che nessuno ha controllato o una risposta gestita come se andasse sempre a buon fine.
Quasi ogni API LLM che incontrerai è un'API web su HTTP. La richiesta ha un metodo, un URL, degli header e di solito un corpo JSON. La chiamata al modello è quasi sempre una POST. RFC 9110, lo standard attuale sulla semantica HTTP, definisce cosa significano queste parti e come deve rispondere un server. I codici di stato e gli header che vedrai più avanti vengono da lì.
Il corpo è un oggetto JSON. Come minimo indica il modello e manda la conversazione fino a quel punto. Questo è il formato Messages, uno dei due stili di richiesta confrontati più avanti. I valori sono segnaposto e qui non viene eseguito niente.
{
"model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
"max_tokens": 300,
"system": "You explain security concepts in plain English. Keep answers under 120 words.",
"messages": [
{
"role": "user",
"content": "What is the difference between hashing and encryption?"
}
],
"stream": false
}Note:
model è l'ID del modello che genera la risposta. Copialo dall'elenco attuale del fornitore, non da un vecchio tutorial.max_tokens fissa il massimo di token che il modello può scrivere. Non limita il tuo prompt.system contiene le istruzioni permanenti. In questo formato sta fuori dall'elenco dei messaggi.messages contiene la conversazione. Qui ha un solo turno dell'utente.stream è false, quindi il server aspetta di avere la risposta completa prima di inviarla.Ogni messaggio ha un ruolo che dice chi sta parlando:
user: testo del tuo utente finale o del tuo programma.assistant: una risposta scritta prima dal modello. La rimandi per continuare la conversazione.system o developer: istruzioni permanenti. Il nome dipende dal formato.tool: il risultato di una chiamata a un tool, di cui parla un articolo successivo.Per continuare una conversazione, aggiungi l'ultima risposta del modello come messaggio assistant, poi il nuovo messaggio user. Se dimentichi i turni precedenti, il modello risponde alla nuova domanda senza contesto e non compare nessun errore. È uno dei bug più comuni.
max_tokens (o il suo equivalente) limita quanto scrive il modello. Se il testo arriva al limite, si ferma a metà frase e la risposta lo segnala. Controlla il motivo di arresto, descritto al passo 6, invece di dare per scontato che ogni risposta sia completa. Il limite non riduce quanto paghi per il prompt, perché i token di input contano comunque nell'utilizzo.
Con "stream": true, il server manda la risposta a pezzi man mano che la genera, usando i server-sent event. Gli utenti vedono comparire le parole una dopo l'altra. Il tuo codice legge gli eventi e unisce i pezzi, e i dati di utilizzo e il motivo di arresto arrivano negli eventi di chiusura. Usa lo streaming quando c'è una persona che guarda. Per un job batch che salva la risposta, una richiesta normale è più semplice da scrivere e da provare.
Una risposta tipica contiene:
choices.Leggi il motivo di arresto prima di fidarti del testo. Una risposta fermata dal limite di token è incompleta. Una risposta che finisce con una chiamata a un tool non è la risposta definitiva: il tuo codice deve eseguire il tool e rimandare il risultato.
L'oggetto usage determina il tuo conto. Registra i conteggi, l'ID del modello, l'ora, il motivo di arresto e il codice di stato. Per impostazione predefinita non registrare prompt né risposte, perché possono contenere dati dei clienti, codice privato o segreti. I metadati bastano per trovare picchi di costo ed errori.
Nella documentazione pubblica sono diffusi due stili di richiesta, e molti servizi ne copiano uno così il codice client esistente continua a funzionare cambiando solo l'URL di base. Conoscerli entrambi ti aiuta a leggere esempi di qualsiasi provenienza.
Il primo è il formato Messages, documentato nel primo riferimento in Fonti. Accetta system come campo di primo livello, richiede max_tokens, restituisce blocchi di contenuto con uno stop_reason e riporta input_tokens e output_tokens.
Il secondo è il formato Chat Completions, documentato nel secondo riferimento in Fonti. Mette le istruzioni system o developer dentro messages, accetta max_completion_tokens e restituisce un array choices con un finish_reason e un message in ogni elemento. Il suo oggetto usage riporta prompt_tokens, completion_tokens e total_tokens.
| Argomento | Formato Messages | Formato Chat Completions |
|---|---|---|
| Istruzioni permanenti | Campo system di primo livello |
Messaggio system o developer in messages |
| Campi obbligatori | model, messages, max_tokens |
model, messages |
| Limite di output | max_tokens (obbligatorio) |
max_completion_tokens (facoltativo) |
| Dove sta la risposta | Elenco di blocchi content |
choices[0].message |
| Perché si è fermato | stop_reason |
choices[0].finish_reason |
| Conteggi di token | usage.input_tokens, usage.output_tokens |
usage.prompt_tokens, usage.completion_tokens, usage.total_tokens |
| Streaming | stream: true, server-sent event |
stream: true, chunk di server-sent event |
Le differenze riguardano la forma, il significato è lo stesso. Entrambi mandano un modello, una conversazione e un limite, ed entrambi restituiscono testo, un motivo e dei conteggi. Quando un esempio usa l'altro formato, fai corrispondere i campi in base al loro ruolo in questa tabella invece di copiare il JSON.
La stessa domanda nel formato Chat Completions:
{
"model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
"max_completion_tokens": 300,
"messages": [
{
"role": "developer",
"content": "You explain security concepts in plain English. Keep answers under 120 words."
},
{
"role": "user",
"content": "What is the difference between hashing and encryption?"
}
],
"stream": false
}Una risposta in questo formato ha la forma seguente. I valori sono segnaposto, non una risposta osservata:
{
"id": "<RESPONSE_ID>",
"object": "chat.completion",
"model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "<GENERATED_TEXT>" },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}
}Il tuo codice legge choices[0].message.content, choices[0].finish_reason e poi l'oggetto usage. Il formato Messages tiene le stesse informazioni in content, stop_reason e usage, con nomi diversi.
Una chiave API identifica l'account che paga una chiamata e decide quali modelli e limiti si applicano. I due formati la inviano in modo diverso. Il formato Chat Completions usa un header Authorization con lo schema Bearer. Il formato Messages usa un header dedicato per la chiave e richiede anche un header con la versione dell'API, così il fornitore può mantenere stabile il comportamento precedente.
I nomi degli header cambiano con il fornitore e con la versione, quindi copiali dalla documentazione attuale. Le forme sono queste:
# Placeholder shape only. Do not run this with a real key in a shared shell history.
curl https://<API_HOST>/<MESSAGES_PATH> \
-H "x-api-key: <YOUR_API_KEY>" \
-H "anthropic-version: <VERSION_FROM_CURRENT_DOCS>" \
-H "content-type: application/json" \
-d '{"model":"<MODEL_ID>","max_tokens":300,"messages":[{"role":"user","content":"Hello"}]}'# Placeholder shape only. Same caution as above.
curl https://<API_HOST>/<CHAT_COMPLETIONS_PATH> \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"model":"<MODEL_ID>","messages":[{"role":"user","content":"Hello"}]}'Note:
Authorization: Bearer è lo schema HTTP standard per i token. RFC 9110 definisce l'header Authorization nella sezione sull'autenticazione.Qui nasce la maggior parte dei costi e delle fughe che si potrebbero evitare. Le regole seguenti vengono dall'OWASP Secrets Management Cheat Sheet.
Tieni le chiavi fuori dal codice sorgente. Una chiave scritta in un file Python esiste in ogni copia di quel file: il tuo portatile, le macchine dei colleghi, il sistema di CI e ogni commit che l'ha toccato. OWASP indica i segreti scritti nei file sorgente e di configurazione come pratica rischiosa.
Non fare mai commit di una chiave. Git conserva la cronologia. Cancellare la chiave in un commit successivo non la toglie da quello vecchio. OWASP nota che togliere un segreto dalla cronologia può richiedere una riscrittura, che ha effetti collaterali. Considera esposta una chiave arrivata in un repository condiviso. Revocala, creane una nuova e controlla se sull'account c'è un utilizzo che non hai fatto tu.
Tieni le chiavi fuori da log, screenshot e codice del browser. Tutto quello che sta in una riga di log, in un messaggio di errore o nello script di una pagina web può essere letto da altri. Le chiamate che hanno bisogno di una chiave dovrebbero girare su un server che controlli tu.
Non includere le chiavi nelle immagini dei container. OWASP mette in guardia dai segreti nelle istruzioni Docker ENV o ARG, perché il valore finisce nei metadati dell'immagine e nella cronologia di build.
Preferisci un archivio di segreti. OWASP dice che le variabili d'ambiente sono in genere accessibili a tutti i processi e possono comparire in log o dump, quindi le consiglia solo quando non sono possibili opzioni migliori. Su un portatile va bene un gestore di password o l'archivio credenziali del sistema operativo. Sui server usa un servizio gestito per i segreti o file montati con permessi ristretti.
Limita e ruota. Dai a ogni chiave solo l'accesso che le serve e sostituisci le chiavi a intervalli regolari. OWASP dice che ai segreti vanno assegnati i privilegi minimi e consiglia la rotazione automatica dove possibile.
Uno schema locale sicuro tiene il valore nell'ambiente e lo legge per nome:
# Read the key from the environment. Fail early if it is missing.
import os
api_key = os.environ.get("LLM_API_KEY")
if not api_key:
raise SystemExit("LLM_API_KEY is not set. Set it in your shell or secret store.")Aggiungi i file di impostazioni locali a .gitignore prima di crearli. Fai commit di un template come .env.example che elenca i nomi delle variabili con valori vuoti, e non fare mai commit del file reale.
I fornitori limitano quante richieste e quanti token un account può usare in una finestra di tempo. Spesso si chiamano rate limit, e alcuni account hanno anche tetti di spesa. I numeri cambiano in base ad account, modello e momento, quindi controlla la console del tuo fornitore e la documentazione attuale. Questo articolo non dà cifre.
Quando raggiungi un limite, il server di solito restituisce 429 Too Many Requests. RFC 6585 definisce questo codice, che estende l'insieme HTTP di base. La risposta può includere Retry-After, definito da RFC 9110 nella sezione sui campi di risposta, che dice quanto aspettare.
Il tuo codice dovrebbe leggere lo stato prima del corpo, aspettare prima di riprovare (usando Retry-After quando c'è, altrimenti un ritardo crescente con un po' di jitter casuale) e fermarsi dopo pochi tentativi invece di ciclare all'infinito. Conta i token prima di mandare prompt molto grandi, perché una richiesta troppo grande fallisce qualunque sia il tuo saldo.
La prima cifra di un codice di stato indica la classe: un problema lato client, un guasto del server o altro. RFC 9110 definisce le classi e i singoli codici. Questi sono gli errori che le nuove integrazioni incontrano più spesso.
| Stato | Nome | Cosa significa di solito | Cosa controllare |
|---|---|---|---|
| 400 | Bad Request | Il corpo è malformato o un campo non è valido | Sintassi JSON, nomi dei campi, campi obbligatori, intervalli di valori |
| 401 | Unauthorized | La chiave manca, è malformata o è stata revocata | Nome dell'header, prefisso Bearer, valore della chiave nel tuo ambiente |
| 403 | Forbidden | La chiave è valida, ma questo account non può usare quella risorsa | Accesso al modello, permessi dell'account, stato della fatturazione |
| 404 | Not Found | Il percorso è sbagliato o l'ID del modello non esiste per il tuo account | URL di base, percorso, ID esatto del modello dal catalogo attuale |
| 429 | Too Many Requests | Limite di richieste o quota raggiunti | Retry-After, pagina di utilizzo, volume di richieste |
| 500 | Internal Server Error | Errore lato server | Riprova con backoff, poi controlla la pagina di stato del fornitore |
| 503 | Service Unavailable | Il servizio è sovraccarico o in manutenzione | Riprova con backoff e un numero massimo di tentativi |
Un errore 4xx significa che la richiesta va cambiata, quindi rimandare la stessa richiesta non serve. Un 5xx o un 429 possono andare a buon fine più tardi, quindi lì il backoff ha senso. Registra il codice di stato, l'ID del modello e l'ID della richiesta. Non registrare mai né la chiave né il prompt.
Fidarsi della prima risposta. Una risposta può essere tagliata dal limite di token o fermarsi per un altro motivo. Controlla ogni volta il motivo di arresto.
Dimenticare i turni precedenti. Un'API senza stato non sa nulla della richiesta precedente a meno che tu non la rimandi. Costruisci l'elenco dei messaggi dal tuo archivio.
Scrivere l'ID del modello nel codice. Gli ID dei modelli vengono ritirati e rinominati. Tieni l'ID nella configurazione, così una modifica è una riga sola.
Mescolare i due formati. Mandare un corpo Chat Completions a un endpoint Messages, o il contrario, produce errori 400. Verifica quale formato documenta l'endpoint prima di copiare un esempio.
Leggi da cima a fondo la documentazione di riferimento di un fornitore. Le sezioni su richiesta, risposta ed errori sono brevi e rispondono alla maggior parte delle domande della prima settimana. Poi leggi di streaming e finestre di contesto, che decidono quanta conversazione puoi mandare prima che una richiesta fallisca.
Il gateway di NotHoly documenta i propri campi e codici di errore. Confrontali con le due forme viste sopra, e considera la sua documentazione il riferimento su come si comporta.
Se vuoi che il modello chiami il tuo codice, il passo successivo è l'uso dei tool: un turno dell'assistente il cui motivo di arresto chiede al tuo programma di eseguire qualcosa e rimandare il risultato. L'articolo successivo tratta questo ciclo.
Retry-After.429 Too Many Requests.Modelli LLM e API
Verboo, Venice AI e Drael a confronto con NotHoly, novembre 2026: fatturazione, API, licenza, basi dei modelli e dove girano gli strumenti, con fonti.
Modelli LLM e API
I piani illimitati di solito limitano la velocità, non i token. Verboo, Venice.ai, Drael e il credito prepagato a token, confrontati su termini e fatture.
Modelli LLM e API
Come spostare un flusso compatibile con OpenAI da Verboo, Venice AI o Drael a NotHoly: base URL, chiave, ID dei modelli, fatturazione, licenza e strumenti.