Lab 04-02: Client compatibili OpenAI¶
Obiettivo¶
Parlare con llama serve da un programma tuo, senza curl: prima con l'SDK ufficiale openai in Python
(risposta intera e streaming), poi con fetch nativo di Node.js senza alcuna dipendenza. Alla fine sai
dove finiscono i campi "extra" di llama.cpp (timings, chat_template_kwargs) quando passi da un SDK
pensato per un altro fornitore, e sai misurare il tempo al primo token.
Prerequisiti¶
- Lab 04-01 completato (sai avviare il server e leggere
timings). - Python 3.10+ con
pip; Node.js 18 o superiore (perfetcheTextDecoderStreamnativi; gli esempi usano la 22). - Ubuntu con binario dell'installer:
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt.
Materiale¶
| Cosa | Dove | Dimensione |
|---|---|---|
ggml-org/Qwen3.5-0.8B-GGUF (file Qwen3.5-0.8B-Q8_0.gguf, Apache 2.0) |
Hugging Face, via -hf |
785 MiB |
chat.py: client Python con SDK openai (--stream per lo streaming) |
cartella del lab | 3 KB |
chat.mjs: client Node.js con fetch, parser SSE a mano (--stream) |
cartella del lab | 2 KB |
pacchetto openai (PyPI) |
pip install openai (versione 3.18.0) |
~10 MB con dipendenze |
Procedura¶
-
Avvia il server in un terminale e lascialo aperto (un solo slot basta):
Aspetta
srv llama_server: listening on http://127.0.0.1:8080(ocurl -s localhost:8080/health). -
Prepara l'ambiente Python in un secondo terminale, dentro la cartella del lab:
-
Leggi
chat.pyprima di lanciarlo. Le tre righe che contano:client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="no-key-required") # (1)! EXTRA = {"chat_template_kwargs": {"enable_thinking": False}} # (2)! r = client.chat.completions.create(model="local", messages=..., extra_body=EXTRA) # (3)!- Rispetto al codice "vero" per OpenAI cambia solo
base_url;api_keydeve avere un valore qualsiasi (l'SDK rifiuta la stringa vuota), oppure la chiave passata al server con--api-key. - I parametri che l'API OpenAI non conosce (qui: spegnere il thinking di Qwen3.5) si passano in
extra_body: l'SDK li copia nel JSON così come sono. modelè ignorato dal server a modello singolo (vale l'alias-a); diventa obbligatorio in router mode.
- Rispetto al codice "vero" per OpenAI cambia solo
-
Risposta intera:
Output (b11046, macchina a 6 vCPU senza GPU):
Llamà.cpp è una libreria C++ per la gestione di chiamate di sistema, progettata per semplificare l'uso … [finish_reason=stop prompt=41 tok, completion=39 tok, 12.7s] [server: cache_n=0 prompt 12.4 t/s, generazione 4.2 t/s]La seconda riga viene da
r.usage; la terza dal campotimings, che non è nel modello dati dell'SDK: Pydantic lo conserva inr.model_extra["timings"]. (La risposta è inventata: un modello da 0,8 B non conosce llama.cpp; qui conta il trasporto dei dati, non il contenuto.) -
Streaming:
Llama.cpp è un framework Python per la generazione di modelli di linguaggio … [39 chunk, primo token dopo 1.11s, totale 15.4s]Con
stream=Truel'SDK restituisce un iteratore: ogni elemento è un chunk SSE già decodificato, conchunk.choices[0].delta.content(spesso una parola o un pezzo di parola). Lo script misura il tempo al primo token (TTFT): è il tempo di prompt processing più il primo passo di generazione, ed è ciò che l'utente percepisce come "reattività". Il primo chunk hadelta.role = "assistant"econtentnullo: per questo il codice controllaif delta.content. -
Node.js senza dipendenze. Leggi
chat.mjs: la richiesta è unafetchconJSON.stringify(body), e lo streaming è un parser SSE scritto a mano di dieci righe (accumula il testo in un buffer, taglia sulle righe, considera solo quelle che iniziano condata:, si ferma su[DONE]). Provalo:Ecco tre usi principali di un server locale per modelli di linguaggio (LLM): 1. **Analisi e Generazione di Contenuti**: … [80 token, 4.3 t/s]Con
--streamil testo compare pezzo per pezzo, poi[80 token, 4.5 t/s](daltimingsdell'ultimo chunk) e[fine]quando arrivadata: [DONE].finish_reasonqui èlength: 80 token non bastano per tre punti. -
Cambia server senza toccare il codice: entrambi gli script leggono
LLAMA_URL. -
Con chiave API. Riavvia il server con
--api-key segreta123e rilanciapython chat.py: ottieniopenai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API Key', 'type': 'authentication_error', 'code': 401}}. MettiAPI_KEY = "segreta123"inchat.pye inchat.mjsscommenta l'headerAuthorization: Bearer …. Se togli l'argomentoapi_keydal costruttore, l'SDK legge da solo la variabile d'ambienteOPENAI_API_KEY:OPENAI_API_KEY=segreta123 python chat.pypassa. -
Ferma il server con Ctrl+C.
sequenceDiagram
participant P as chat.py (SDK openai)
participant S as llama serve
P->>S: POST /v1/chat/completions {messages, stream:true, chat_template_kwargs}
S-->>P: data: {"delta":{"role":"assistant","content":null}}
S-->>P: data: {"delta":{"content":"Llama"}} ← TTFT misurato qui
S-->>P: data: {"delta":{"content":".cpp"}} … (un chunk per token)
S-->>P: data: {"finish_reason":"stop","timings":{…}}
S-->>P: data: [DONE]Verifica del risultato¶
-
python chat.pystampa la risposta, poiusagee la riga[server: cache_n=… t/s]presa damodel_extra. -
python chat.py --streammostra il testo che cresce e un TTFT di 1-3 s su CPU (dipende dalla macchina). -
node chat.mjs --streamtermina con[fine]dopodata: [DONE]. - Con
--api-keysul server, il client senza chiave riceve401e con la chiave funziona.
Domande e varianti¶
- Lancia
python chat.pydue volte di seguito:cache_npassa da 0 a un valore vicino aprompttoken. Perché? (rileggi § 6 del modulo 04). - Alza
max_tokensa 300 e confrontafinish_reason: quando èlengthe quandostop? - Nel client Node manda
"stream_options": {"include_usage": true}: nell'ultimo chunk compare ancheusage. - Togli
chat_template_kwargse rilancia conmax_tokens: 80: il modello "pensa" econtentpuò restare vuoto, mentre l'SDK Python espone il ragionamento indelta.model_extra["reasoning_content"]. - Variante Anthropic: lo stesso server risponde su
/v1/messages; se hai l'SDKanthropic, provaAnthropic(base_url="http://127.0.0.1:8080", api_key="nokey")conmax_tokensobbligatorio. - Variante browser:
chat.mjsgira anche in una pagina HTML (stessofetch); se la pagina è servita da un'altra origine ricorda--cors-origins.
Consegna¶
Un file lab-04-02.md con: gli output di chat.py (intero e stream, con il TTFT), quelli di chat.mjs, la riga
di errore ottenuta al punto 8 e una risposta di tre righe alla domanda: "quali righe cambieresti per passare lo
stesso script dal server locale all'API OpenAI vera?".