Ollama 101 - Usarlo tramite REST API

banner

Recap

Nei primi due articoli abbiamo visto cos’è Ollama e come usarlo tramite CLI. Dobbiamo però spingerci sempre un po’ più in là: il vero potenziale di Ollama infatti si esprime quando viene integrato nelle applicazioni attraverso la sua API REST.

Quando avvii Ollama (automaticamente o con ollama serve), viene esposto automaticamente un server HTTP sulla porta 11434 (di default). Questo significa che puoi fare richieste HTTP da qualsiasi linguaggio di programmazione: JavaScript, Python, Go, Java, ecc.

In questo articolo vedremo tutti gli endpoint principali con esempi pratici e pronti all’uso.

Setup iniziale

Prima di iniziare, assicurati che Ollama sia in esecuzione:

# Su Windows/macOS parte automaticamente
# Su Linux potresti dover fare:
ollama serve

Verifica che il server risponda:

curl http://localhost:11434/api/version

Dovresti ricevere una risposta JSON:

{
  "version": "0.1.x"
}

Ollama endpoints

L’API di Ollama espone diversi endpoint, ognuno con uno scopo specifico. Tutti gli endpoint accettano e restituiscono JSON. Come riferimento anche per l’analisi dei prossimi endpoint, teniamo a mente che ci sarà sempre un URL di base da utilizzare, che è il seguente:

Base URL: http://localhost:11434/api

Endpoint /generate

L’endpoint /generate permette di generare testo da un prompt senza mantenere lo storico della conversazione. È perfetto per task singoli e indipendenti.

Metodo: POST
URL: /api/generate

Esempio con curl:

curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Perché il cielo è blu?",
  "stream": false
}'

Risposta:

{
  "model": "llama3.2",
  "created_at": "2024-02-20T10:30:00.000Z",
  "response": "Il cielo appare blu a causa di un fenomeno chiamato diffusione di Rayleigh. Quando la luce solare attraversa l'atmosfera, le molecole d'aria e le particelle diffondono la luce blu in tutte le direzioni più efficacemente rispetto ad altri colori dello spettro visibile.",
  "done": true,
  "total_duration": 2500000000,
  "load_duration": 1000000000,
  "prompt_eval_count": 12,
  "prompt_eval_duration": 500000000,
  "eval_count": 85,
  "eval_duration": 1000000000
}

Analizziamo la request: nel body JSON abbiamo specificato il modello da usare (llama3.2), il prompt di input e se vogliamo la risposta in streaming (stream: false significa che aspettiamo la risposta completa). Se questo parametro non viene specificato, il default è true e quindi otterremo tanti stream quanti sono i token generati.

Ma come possiamo usarlo all’interno delle nostre applicazioni? Niente di più facile. Di seguito, vediamo un esempio utilizzando Node.js:

Esempio con JavaScript (Node.js):

const response = await fetch('http://localhost:11434/api/generate', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'llama3.2',
    prompt: 'Scrivi una funzione JavaScript per validare un email',
    stream: false
  })
});

const data = await response.json();
console.log(data.response);

In quest’altro snippet, vediamo un esempio utilizzando Python:

Esempio con Python:

import requests
import json

response = requests.post('http://localhost:11434/api/generate',
  json={
    "model": "llama3.2",
    "prompt": "Spiega cosa sono le list comprehension in Python",
    "stream": False
  }
)

result = response.json()
print(result['response'])

Come vedete, la forma è sempre la stessa, e la semplicità d’uso è incredibile.

Giusto per analizzare meglio le opzioni disponibili per questo endpoint, vediamo quali parametri possiamo passare nel body JSON:

ParametroTipoDescrizioneDefault
modelstringNome del modello da usare(obbligatorio)
promptstringIl testo del prompt(obbligatorio)
streambooleanSe true, risposta in streamingtrue
temperaturefloatCreatività (0-2)0.8
top_pfloatNucleus sampling (0-1)0.9
top_kintTop-k sampling40
max_tokensintMax token generati-1 (illimitato)

Se alcuni di questi parametri dovrebbero essere chiari, proviamo a dare una spiegazione veloce a quelli più complessi:

  • temperature: Controlla la casualità della generazione. Valori più bassi (es. 0.2) rendono il testo più prevedibile, mentre valori più alti (es. 1.0 o 1.5) aumentano la creatività e la varietà delle risposte.
  • top_p: Nucleus sampling. Limita la scelta dei token al più piccolo insieme di token la cui somma di probabilità è almeno p. Questo si traduce in risposte più varie e meno ripetitive.
  • top_k: Limita la scelta dei token ai k token più probabili. Questo parametro serve a controllare la diversità delle risposte.

Quindi, provando ad utilizzare alcuni dei parametri descritti in precedenza:

Esempio con parametri avanzati:

curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Scrivi una breve storia di fantascienza",
  "stream": false,
  "options": {
    "temperature": 1.2,
    "top_p": 0.95,
    "max_tokens": 200
  }
}'

Perché limitare il numero di token? Perché in alcuni casi potresti voler evitare risposte troppo lunghe o verbose, specialmente se stai generando contenuti per applicazioni con limiti di spazio o tempo, che quindi potrebbero richiedere delle risorse computazionali più elevate.

In questo caso, stiamo chiedendo al modello di generare una storia di fantascienza, con una temperatura più alta (più creatività), un top_p di 0.95 per maggiore varietà, e limitando la risposta a 200 token.

Endpoint /chat

L’endpoint /chat mantiene il contesto della conversazione, permettendo interazioni più complesse come in una vera chat. Giusto per chiarire, quando parliamo di contesto in questo dominio, si intende la capacità del modello di “ricordare” i messaggi precedenti in una conversazione, in modo da poter rispondere in modo coerente e pertinente. Questo endpoint è ovviamente utile nel momento in cui vogliamo creare chatbot o assistenti virtuali.

Metodo: POST
URL: /api/chat

Esempio base:

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    {
      "role": "user",
      "content": "Ciao! Come ti chiami?"
    }
  ],
  "stream": false
}'

Risposta:

{
  "model": "llama3.2",
  "created_at": "2024-02-20T10:30:00.000Z",
  "message": {
    "role": "assistant",
    "content": "Ciao! Sono un assistente AI basato su Llama. Come posso aiutarti oggi?"
  },
  "done": true
}

Conversazione con contesto:

Per mantenere il contesto, devi includere tutti i messaggi precedenti. Per fare questo, puoi prendere spunto da questo esempio in Javascript, dove ogni interazione tra utente e assistente viene salvata in un array conversationHistory:

let conversationHistory = [];

async function chat(userMessage) {
  // Aggiungi il messaggio dell'utente
  conversationHistory.push({
    role: "user",
    content: userMessage
  });

  const response = await fetch('http://localhost:11434/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'llama3.2',
      messages: conversationHistory,
      stream: false
    })
  });

  const data = await response.json();
  
  // Aggiungi la risposta dell'assistente allo storico
  conversationHistory.push(data.message);
  
  return data.message.content;
}

// Uso
await chat("Qual è la capitale dell'Italia?");
// Risposta: "La capitale dell'Italia è Roma."

await chat("E quanti abitanti ha?");
// Risposta: "Roma ha circa 2,8 milioni di abitanti..."

Utilizzo del system prompt:

Puoi definire il comportamento del modello con una configurazione ad hoc del sistema, indicando al modello il tipo di linguaggio da adottare:

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    {
      "role": "system",
      "content": "Sei un esperto di cucina italiana. Rispondi sempre con ricette dettagliate."
    },
    {
      "role": "user",
      "content": "Come si fa la carbonara?"
    }
  ],
  "stream": false
}'

Questo permetterà al modello di rispondere secondo il contesto specificato, fornendo risposte più pertinenti e specializzate. Si tratta di un parametro particolarmente utile quando vogliamo personalizzare il comportamento del modello per specifici domini o stili di conversazione.

Endpoint /api/tags (list)

Questo endpoint elenca tutti i modelli disponibili localmente. Equivale ad eseguire tramite terminale ollama list.

Metodo: GET
URL: /api/tags

Esempio:

curl http://localhost:11434/api/tags

Risposta:

{
  "models": [
    {
      "name": "llama3.2:latest",
      "modified_at": "2024-02-20T10:00:00.000Z",
      "size": 2038972924,
      "digest": "sha256:a1b2c3d4e5f6...",
      "details": {
        "format": "gguf",
        "family": "llama",
        "families": ["llama"],
        "parameter_size": "3B",
        "quantization_level": "Q4_0"
      }
    },
    {
      "name": "mistral:latest",
      "modified_at": "2024-02-19T15:30:00.000Z",
      "size": 4109846451,
      "digest": "sha256:b2c3d4e5f6g7...",
      "details": {
        "format": "gguf",
        "family": "llama",
        "parameter_size": "7B",
        "quantization_level": "Q4_0"
      }
    }
  ]
}

Endpoint /pull

Scarica un modello dalla libreria di Ollama. Equivale a ollama pull.

Metodo: POST
URL: /api/pull

Esempio:

curl http://localhost:11434/api/pull -d '{
  "name": "llama3.2",
  "stream": true
}'

Risposta in streaming:

{"status":"pulling manifest"}
{"status":"pulling 8934d96d3f08","digest":"sha256:8934d96d3f08...","total":1889549312,"completed":0}
{"status":"pulling 8934d96d3f08","digest":"sha256:8934d96d3f08...","total":1889549312,"completed":1889549312}
{"status":"verifying sha256 digest"}
{"status":"writing manifest"}
{"status":"success"}

Esempio con Python (con progress bar):

import requests
from tqdm import tqdm

def download_model(model_name):
    response = requests.post('http://localhost:11434/api/pull',
        json={"name": model_name, "stream": True},
        stream=True
    )
    
    pbar = None
    for line in response.iter_lines():
        if line:
            data = json.loads(line)
            status = data.get('status', '')
            
            if 'total' in data and 'completed' in data:
                if pbar is None:
                    pbar = tqdm(total=data['total'], unit='B', unit_scale=True)
                pbar.update(data['completed'] - pbar.n)
            else:
                print(status)
    
    if pbar:
        pbar.close()
    print(f"✓ {model_name} scaricato con successo!")

download_model('llama3.2')

Endpoint /show

Mostra informazioni dettagliate su un modello, come il modelfile utilizzato, i parametri e dettagli sulla famiglia di modelli utilizzata. Equivale al comando ollama show.

Metodo: POST
URL: /api/show

Esempio:

curl http://localhost:11434/api/show -d '{
  "name": "llama3.2"
}'

Risposta:

{
  "modelfile": "FROM llama3.2\nSYSTEM You are a helpful assistant.",
  "parameters": "stop \"<|start_header_id|>\"\nstop \"<|end_header_id|>\"\nstop \"<|eot_id|>\"",
  "template": "{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}...",
  "details": {
    "format": "gguf",
    "family": "llama",
    "families": ["llama"],
    "parameter_size": "3.2B",
    "quantization_level": "Q4_0"
  }
}

Hosting su indirizzo pubblico

Come impostazione di default, Ollama è in ascolto solo su localhost (127.0.0.1) e sulla porta 11434, quindi è accessibile solo dalla stessa macchina. Se vuoi esporlo in rete (ad esempio per usarlo da altre macchine o per deployment), devi configurarlo opportunamente. Ma attenzione alla sicurezza!

Prima di esporre Ollama pubblicamente, considera:

  1. Nessuna autenticazione: Ollama non ha autenticazione built-in. Chiunque può usare l’API. Sarà quindi necessario porre l’API dietro un reverse proxy e includere un meccanismo di autenticazione e autorizzazione che ne limiti l’accesso, soprattutto per ciò che riguarda gli endpoint che permettono di creare o cancellare dei modelli. Il consiglio è infatti di esporre pubblicamente solo quelli che consentono di generare una risposta o avviare una chat, e tenere riservati tutti gli altri.
  2. Consumo risorse: Gli LLM sono resource-intensive. Un uso malevolo può saturare la tua macchina, causando rallentamenti o crash.
  3. Privacy: Assicurati di non esporre dati sensibili. Questo è sicuramente l’aspetto a cui porre più attenzione, tanto più che Ollama nasce proprio con l’intento di far girare i modelli in locale, preservando la privacy dei dati. Esporre l’API pubblicamente potrebbe vanificare questo vantaggio.

Configurazione per esposizione su rete locale

All’interno di una rete interna, locale, e sicura, possiamo pensare di esporre Ollama su un indirizzo IP accessibile da altre macchine. Per fare questo, dobbiamo modificare la variabile d’ambiente OLLAMA_HOST per far sì che Ollama ascolti su 0.0.0.0:11434 (ossia tutte le interfacce di rete). Questo vuol dire che sarà accessibile da qualsiasi macchina nella rete locale.

Su Linux:

# Imposta la variabile d'ambiente
export OLLAMA_HOST=0.0.0.0:11434

# Avvia Ollama
ollama serve

Oppure crea un file di configurazione systemd:

sudo nano /etc/systemd/system/ollama.service.d/override.conf

Aggiungi:

[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

Riavvia:

sudo systemctl daemon-reload
sudo systemctl restart ollama

Su Windows:

Imposta la variabile d’ambiente di sistema:

setx OLLAMA_HOST "0.0.0.0:11434"

Riavvia il servizio Ollama.

Su macOS:

# Aggiungi al tuo .zshrc o .bashrc
export OLLAMA_HOST=0.0.0.0:11434

# Riavvia Ollama
launchctl stop com.ollama.ollama
launchctl start com.ollama.ollama

Tieni presente che esporre Ollama in questo modo lo rende accessibile a chiunque nella tua rete locale. Assicurati che la tua rete sia sicura e che solo utenti autorizzati possano accedervi!

Uso di un reverse proxy (Consigliato)

Per maggiore controllo e sicurezza, usa un reverse proxy come Nginx o Caddy.

Esempio con Nginx:

server {
    listen 80;
    server_name ollama.tuodominio.com;

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=ollama:10m rate=10r/m;
    limit_req zone=ollama burst=5;

    location / {
        proxy_pass http://localhost:11434;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # Autenticazione basic
        auth_basic "Ollama API";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

Per chi non ha familiarità con Nginx, nel file di configurazione abbiamo specificato che:

  • per chiamare le API di Ollama, potremo usare la porta 80 (HTTP) del nostro dominio ollama.tuodominio.com
  • abbiamo aggiunto un rate limiting per evitare abusi (10 richieste al minuto con burst di 5, dove per burst si intende il numero massimo di richieste che possono essere fatte in un breve lasso di tempo)
  • abbiamo abilitato l’autenticazione basic utilizzando un file .htpasswd per proteggere l’accesso.

Infatti, per creare le credenziali tramite htpasswd (parte del pacchetto apache2-utils su Linux), puoi eseguire:

sudo htpasswd -c /etc/nginx/.htpasswd username

In questo modo, Nginx fungerà da intermediario, aggiungendo un livello di sicurezza e controllo. Ogni volta che un utente vorrà accedere all’API di Ollama, dovrà autenticarsi. Nell’esempio, abbiamo utilizzato htpasswd, che è sicuramente il metodo più semplice per aggiungere autenticazione basic, ma che non deve essere usato in produzione senza HTTPS e senza gli adeguati strumenti. Sfruttare servizi di autenticazione di terze parti o sistemi di gestione delle identità è sicuramente la strada migliore!

Testing dell’esposizione

Una volta configurato, puoi testare la chiamata da un’altra macchina:

# Sostituisci con l'IP della tua macchina
curl http://192.168.1.100/api/version

Conclusioni

Come visto, l’API REST di Ollama è incredibilmente potente e flessibile. Con gli endpoint che abbiamo visto puoi:

  • Integrare AI in applicazioni web, mobile e desktop
  • Creare chatbot personalizzati
  • Automatizzare task di elaborazione testo
  • Costruire sistemi di Q&A su documenti
  • E molto altro!

Recap della serie:

  1. Articolo 1: Cos’è Ollama e come installarlo
  2. Articolo 2: Comandi CLI per gestire modelli
  3. Articolo 3: API REST per integrare Ollama nelle tue app

Ora hai tutte le conoscenze per iniziare a usare Ollama nei tuoi progetti. Buon coding! 🚀

Risorse utili

Post correlati

TheRedCode.it - Il mondo #tech a piccoli #bit

Partners

Community, aziende e persone che supportano attivamente il blog

Logo di Codemotion
Logo di GrUSP
Logo di Python Milano
Logo di Schrodinger Hat
Logo di Python Biella Group
Logo di Fuzzy Brains
Logo di Django Girls
Logo di Improove
Logo del libro open source
Logo di NgRome
Logo de La Locanda del Tech
Logo di Tomorrow Devs
Logo di DevDojo

Vuoi diventare #tech content creator? 🖊️

Se vuoi raccontare la tua sul mondo #tech con dei post a tema o vuoi condividere la tua esperienza con la community, sei nel posto giusto! 😉

Manda una mail a collaborazioni[at]theredcode.it con la tua proposta e diventa la prossima penna del blog!

Ma sì, facciamolo!