Un laboratorio didattico offline per far vivere ai ragazzi (scout, campo estivo) i concetti fondamenti dell'ingegneria dei prompt: contesto e memoria, system prompt, skill, workflow — con un modello linguistico locale, in LAN, senza Internet e senza account.
Il percorso guidato è una pagina web a quattro tappe — ① Context Injection,
② System Prompt, ③ Skills, ④ Workflow — più il laboratorio codice su
pagina dedicata (code.html): il modello genera una pagina HTML completa e
il ragazzo se la porta via. Il laboratorio codice è aperto solo dalle
postazioni che l'educatore abilita.
browser dei ragazzi ──▶ nginx (statici + /api/*) ──▶ gateway (business logic)
├── skill service
├── llama-server (modello, tappe ①–④)
└── llama-coder (coder dedicato, laboratorio codice)
Tutto gira su un mini PC (o un Raspberry Pi 3 host con modello 0.5B) in LAN
cablata; i ragazzi usano browser kiosk su Raspberry Pi 3. Zero dipendenze
esterne a runtime: solo Python stdlib. Il coder dedicato (qwen2.5-coder-1.5b,
~1,9 GB di picco) esiste solo sul mini PC: sul Pi 3 non si attiva e la
generazione ricade sul modello principale (fallback dichiarato in pagina).
Tutti i comandi partono dalla cartella laboratory/ e usano make
(vedi make help). Requisito: Python 3.11+ per i test; Docker + compose per
lo stack.
cd laboratory
make demo # skill ON con backend mock, chat OFF (nessun modello)Apri http://localhost:8090 : il percorso si naviga, la tappa ③ (skill) funziona col backend simulato. È il modo più rapido per vedere tutto senza scaricare nulla. Il pannello educatore sta su http://localhost:8090/admin.
Le dipendenze da scaricare sono l'immagine Docker di llama.cpp e i modelli (file GGUF).
cd laboratory
make pull # immagine llama.cpp (server CPU, compatibile OpenAI)
# i modelli: GGUF quantizzati q4_k_m da HuggingFace, in laboratory/models/
# 1.5B (consigliato, ~1 GB) — target del mini PC
curl -L -o models/qwen2.5-1.5b-instruct-q4_k_m.gguf \
"https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf"
# 0.5B (più leggero, per Raspberry Pi 3)
curl -L -o models/qwen2.5-0.5b-instruct-q4_k_m.gguf \
"https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q4_k_m.gguf"
# coder 1.5B (~1 GB) — per il laboratorio codice, richiesto da make up
curl -L -o models/qwen2.5-coder-1.5b-instruct-q4_k_m.gguf \
"https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF/resolve/main/qwen2.5-coder-1.5b-instruct-q4_k_m.gguf"
make up # UN comando, DUE llama: main (tappe ①–④) + coder (laboratorio codice)Apri http://localhost:8090 e rifai le tappe col modello vero; il banner in
pagina dice se il modello è attivo. (MODEL_FILE=.../CODER_FILE=... per
cambiare GGUF; senza il GGUF del coder make up non parte — il fallback
senza coder resta make demo.)
| Comando | Cosa fa |
|---|---|
make demo |
stack senza modello (mock) |
make up / make down |
stack col modello / ferma tutto |
make rebuild |
ricostruisce le immagini e riparte col modello |
make status |
il modello risponde? quanti ragazzi collegati? |
make admin |
URL del pannello educatore (/admin) |
make logs |
log live (grep '#<cid>' segue un ragazzo) |
make test |
suite di test (unittest) |
make loadtest N=8 TURNS=4 |
simula 8 ragazzi in chat (vedi sotto) |
make clean-sessions |
azzera lo storico delle sessioni |
make loadtest N=8 TURNS=4 lancia 8 "ragazzi" sintetici, ognuno con una
conversazione a più turni sulle chat del gateway (sessioni load-*,
visibili nel pannello educatore come quelle vere). Utile per vedere come
lo stack regge il carico: nel pannello, il grafico token/secondo mostra
il ritmo di generazione nel tempo — se degrada con tanti ragazzi
contemporanei, si vede.
La pagina del laboratorio mostra a ogni ragazzo, sotto il banner, il costo
della propria sessione: i consumi stimati qui, sul mini PC (energia,
costo, acqua ≈ 0) accanto a quelli della stessa sessione su un modello di
frontiera (costo e acqua). Sono stime didattiche semplificate — i dati
di partenza (watt, €/kWh, prezzo e acqua del modello di frontiera) stanno in
un unico file modificabile, laboratory/backend/costi.py. La lezione: in
locale non hai modelli di frontiera, ma ogni domanda ti costa quasi niente;
la frontiera la paghi in acqua e denaro anche per le domande semplici.
La vecchia tappa ⑤ Prompt Engineering è un laboratorio autonomo su pagina
dedicata: il modello scrive una pagina HTML completa — CSS incluso, in un
file unico — e il ragazzo se la porta via con Copia, Scarica .html
o Apri, con anteprima in sandbox. Dalla pagina del percorso (①–④) non ci
sono link: l'accesso lo dà l'educatore, che abilita gli IP delle postazioni
dal pannello /admin (allowlist persistente, vale da subito senza riavvii).
Policy nel gateway: tetto dedicato di 4096 token, una generazione alla volta
(429 con retry alle concorrenti), niente backpressure su questa tappa. I
dettagli stanno in laboratory/README.md.
Quando una classe intera fa accodare le generazioni su un unico nodo, si aggiungono mini PC solo-inferenza alla flotta: pagina, CLI e pannello non cambiano indirizzo né contratto — il gateway instrada ogni chat sul nodo con meno generazioni in corso, evita i nodi spenti e devia quelli lenti; il 429 di sovraccarico arriva solo quando TUTTI i nodi sono in affanno.
LLAMA_URL accetta una lista di endpoint separati da virgola (gateway e skill
leggono la stessa variabile); il valore singolo di sempre resta identico:
# laboratory/.env
LLAMA_URL=http://192.168.1.50:8081,http://192.168.1.51:8081,http://localhost:8081Poi un riavvio di gateway e skill (make rebuild, oppure
docker compose restart gateway skill). Nel pannello /admin compare la
sezione flotta (per nodo: stato sano / affanno / giù, generazioni in
corso, modello servito), il badge della pagina dichiara «Modello locale
(2/3 nodi)» e il grafico token/s guadagna un pannello per nodo accanto
all'aggregato.
Sul mini PC serve SOLO il servizio modello, col GGUF condiviso e la porta :8081 pubblicata sulla LAN:
docker run -d --name llama \
-p 8081:8081 \
-v /home/lab/models:/models \
ghcr.io/ggml-org/llama.cpp:server \
-m /models/qwen2.5-1.5b-instruct-q4_k_m.gguf -c 2048 --host 0.0.0.0La flotta è omogenea: stesso GGUF e stesso -c del compose principale su
tutti i nodi — nome del modello e contesto dichiarati ai ragazzi non devono
dipendere da quale nodo risponde. Le porte dirette :8081 dei nodi restano
esposte a consumer fidati, stessa convenzione del llama locale. Per aggiungere
o togliere un node basta aggiornare la lista negli ambienti server.
L'host del laboratorio resta la fonte: i modelli si allineano con rsync.
cd laboratory
rsync -av --progress models/*.gguf lab@192.168.1.50:/home/lab/models/
rsync -av --progress models/*.gguf lab@192.168.1.51:/home/lab/models/Un nodo con GGUF diverso o mancante si vede subito nel pannello (nome modello per nodo): meglio ri-sincronizzare prima dell'aula.
Rimettere il valore singolo precedente (o togliere la variabile) e riavviare gateway e skill: la flotta si svuota e tutto torna monolite.
LLAMA_URL=http://localhost:8081 # com'era prima della flottaSenza hardware extra, la procedura si prova anche con un nodo finto (indirizzo di una macchina spenta):
# 1) config a due nodi, uno SPENTO: nessun errore all'avvio
LLAMA_URL=http://localhost:8081,http://192.168.1.99:8081 make rebuild
# 2) una chat: parte sul nodo vivo, risposta regolare
curl -s localhost:8090/api/chat -H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"ciao"}]}'
# 3) sotto carico (make loadtest N=4) il meno caro vince: i pannelli per nodo
# del /admin mostrano serie distinte e il nodo spento risulta «giù»
# 4) stato della flotta coerente (healthy/total, stati per nodo)
curl -s localhost:8090/api/model-status | python3 -m json.toolLa suite automatizzata copre gli stessi casi (make test: instradamento sul
nodo meno caro, nodo spento aggirato, nodo in affanno deviato, model-status).
cd laboratory && make testVedi LICENSE.