Questa guida pratica ed eseguibile accompagna il primo post della serie Portway. L'obiettivo: configurare un singolo modello dietro un endpoint compatibile con OpenAI sul proprio hardware, chiamarlo tramite l'SDK ufficiale OpenAI e interiorizzare il contratto stateless (senza stato). Tutto funziona localmente, a costo zero.

Cosa copre questo articolo

  • Uno script demo.py con due blocchi:
    1. Round-trip — una chiamata chat tramite SDK OpenAI, con stampa del contenuto e dell'oggetto usage.
    2. Prova stateless — la stessa domanda finale inviata come messaggio a 1 turno e come ultimo turno di una cronologia fabbricata a 5 turni; entrambi i valori prompt_tokens vengono stampati insieme a una spiegazione del delta.

Scelta del motore su questa macchina

Mac con Apple Silicon, 48 GB di memoria unificata, Ollama già installato. Il demo utilizza l'endpoint compatibile con OpenAI di Ollama all'indirizzo http://localhost:11434/v1 e il modello gpt-oss:20b (~14 GB).

Opzioni di modello per RAM disponibile

Lo script demo funziona con qualsiasi modello servito da Ollama — basta sostituire il nome del modello in demo.py. La tabella seguente copre macchine con memoria unificata a partire da 9 GB.

ModelloComando di pullDimensione approssimativaRAM minimaNote
llama3.2:3bollama pull llama3.2:3b~2 GB8 GBPiù veloce; ideale per testare il contratto
gemma3:4bollama pull gemma3:4b~3 GB8 GBDi Google; solida nell'esecuzione di istruzioni
mistral:7bollama pull mistral:7b~4,1 GB8 GBClassico baseline a 7B
llama3.1:8bollama pull llama3.1:8b~4,7 GB9 GBMigliore qualità sotto i 10 GB
qwen2.5:7bollama pull qwen2.5:7b~4,4 GB9 GBOttimo per istruzioni e ragionamento
gpt-oss:20bollama pull gpt-oss:20b~14 GB24 GBUtilizzato nell'output di esempio di questo post

Su una macchina con 9 GB, sostituire gpt-oss:20b in demo.py con llama3.1:8b o qwen2.5:7b — la dimostrazione del contratto è identica.

Prerequisiti

  • Ollama in esecuzione locale (curl -s http://localhost:11434/api/tags deve restituire JSON)
  • uv installato (uv --version)
  • Il modello scaricato. Questo post usa gpt-oss:20b (richiede ~24 GB di RAM); vedi Opzioni di modello per RAM disponibile per alternative più leggere su macchine con 9 GB+.
shell
ollama pull llama3.2:3b

Esecuzione

Dalla root del repository:

shell
uv sync # crea .venv alla root, installa dipendenze uv run --project 1-local-first python 1-local-first/demo.py

Output di esempio

Un'esecuzione reale su questa macchina (Mac classe M4, 48 GB, gpt-oss:20b tramite Ollama). I numeri differiranno con modelli più piccoli — prompt_tokens per lo stesso input rimane deterministico indipendentemente dal modello:

plaintext
============================================================ Block 1 — round-trip tramite SDK OpenAI contro localhost ============================================================ content: Toronto, Vancouver, Montreal. usage: CompletionUsage(completion_tokens=43, prompt_tokens=72, total_tokens=115, ...) ============================================================ Block 2 — stessa domanda finale, 1-turn vs cronologia a 5 turni ============================================================ 1-turn response: La capitale del Canada è **Ottawa**. 5-turn response: La capitale del Canada è **Ottawa**, situata nella provincia di Ontario. 1-turn prompt_tokens: 75 5-turn prompt_tokens: 139 delta: 64 Perché esiste il delta: il server NON ha stato conversazionale tra le richieste. I prompt_tokens della chiamata a 5 turni sono superiori solo perché il client ha ri-inviato la cronologia completa nel body della richiesta. Ogni chiamata viene valutata da zero — la cronologia è responsabilità del client.

completion_tokens e il testo della risposta variano da un'esecuzione all'altra (il campionamento è non-deterministico con temperatura predefinita). prompt_tokens per lo stesso input è deterministico — 75 e 139 dovrebbero riprodursi.

Notare come la risposta a 5 turni recepisca il contesto del viaggio su strada ("situata nella provincia di Ontario") mentre la risposta a 1 turno rielabora il semplice "Driving." nel suo prompt — stesso modello, inquadratura diversa nei messaggi forniti dal client.

Cover image for Local-first: a Model on Your Own Machine, Zero Cloud
Copertina dell'articolo su Local-first: un modello sulla propria macchina, zero cloud

Il contratto stateless spiegato

Questo è il concetto più importante della serie. Ogni richiesta a un'API LLM — locale o cloud — viene valutata da zero. Il server non ha memoria dei turni precedenti. Quando invii una conversazione multi-turno, tu sei quello che ri-invia la cronologia completa nel body della richiesta. Il modello vede tutto in una volta.

L'unica "memoria" del server tra le richieste è la prefix cache (un'optimizzazione computazionale che evita di rivalutare i token già visti), mai lo stato conversazionale. La cache è invisibile — dal punto di vista del contratto API, ogni chiamata è stateless.

Comprendere questo è il fondamento per tutto ciò che segue nella serie:

  • Perché la gestione delle conversazioni appartiene al client, non al server
  • Perché le finestre di contesto contano per costo e latenza
  • Perché lo streaming di usage richiede un'opt-in esplicita (stream_options.include_usage)

Definizione di completato

  • Round-trip SDK OpenAI contro localhost — il Blocco 1 stampa un content reale e un oggetto usage.
  • Sa spiegare perché 5 turni vs 1 turno cambiano prompt_tokens mentre il server non ricorda nulla — il Blocco 2 stampa entrambi i numeri e la spiegazione di un paragrafo.

Cose da notare ora

La dimensione del contesto consuma RAM/VRAM. La finestra di contesto predefinita di Ollama è conservativa per la maggior parte dei modelli; aumentarla (es. ollama run llama3.2:3b/set parameter num_ctx 32768) costa memoria unificata. Non è stata modificata per questo post.

gpt-oss emette un canale di ragionamento (formato Harmony). Il motore applica il template; ottieni comunque un normale message.content. Il canale di ragionamento sarà segregato al gateway nel Post 3.

Nessuno streaming ancora. Il Post 5 copre la trappola dello streaming usage — devi optare tramite stream_options.include_usage, altrimenti usage è null nelle risposte in streaming.

Cosa viene dopo

Il Post 2 passa da un singolo modello all'esecuzione simultanea di più modelli e al routing delle richieste tra di essi — il primo passo verso un gateway locale reale.

La serie completa e tutto il codice demo si trovano nel repository Portway.

Il nostro verdetto
4.5/ 5

Local-first con Ollama

Guida pratica ed essenziale per chi vuole sperimentare con LLM locali usando l SDK OpenAI. Il contratto stateless è spiegato chiaramente attraverso l output reale. Ideale come punto di partenza per costruire un infrastruttura AI completamente locale.

Posso usare questa guida su un PC Windows o Linux?
Sì, Ollama è disponibile anche per Windows e Linux. I comandi e lo script demo funzionano identicamente su tutti i sistemi operativi.
Quale modello scegliere per iniziare con 8 GB di RAM?
Consigliamo llama3.2:3b (2 GB) o mistral:7b (4,1 GB). Entrambi funzionano su 8 GB e permettono di testare il contratto stateless senza problemi.
Perché i prompt_tokens sono deterministici?
Perché il server valuta ogni richiesta da zero senza stato conversazionale. Se invii gli stessi messaggi, il numero di token di input sarà sempre identico.
Cosa succede se aumento la finestra di contesto?
Aumentare il contesto (es. num_ctx 32768) consuma più RAM/VRAM. Consigliato solo se hai memoria sufficiente e necessiti di conversazioni più lunghe.
Posso usare modelli diversi da quelli elencati?
Sì, qualsiasi modello supportato da Ollama funziona. Devi solo sostituire il nome del modello in demo.py e assicurarti di avere RAM sufficiente.

Fonti e riferimenti

  1. Repository Portway su GitHub
  2. Sito ufficiale Ollama
  3. Documentazione uv