Integrare Ollama in un'applicazione Python tramite l'API REST
Ollama espone due API HTTP sulla porta 11434: un'API nativa (/api/generate, /api/chat) e un'API compatibile con OpenAI (/v1/chat/completions). Quest'ultima è la soluzione ideale per integrare l'API di Ollama in Python: il tuo codice usa esattamente lo stesso SDK che useresti con GPT-4, ma viene eseguito sulla tua macchina. Questa guida illustra schemi d'uso concreti — streaming, JSON strutturato, function calling — con esempi FastAPI e Flask pronti da incollare in un progetto.
Di Mohamed Meguedmi·Agg. 2026-08-27·Testato su Windows, macOS e Linux
#Perché usare l'API REST
La CLI ollama run est pratica per testare, ma non è stata progettata per essere chiamata da un'applicazione. L'API REST, invece, è stata creata proprio per questo: richieste HTTP standard, input e output in JSON, streaming tramite Server-Sent Events. Questo è ciò che tutte le interfacce (Open WebUI, Cline, LangChain) usano all'interno.
Compatibilità con OpenAI
L'endpoint /v1/chat/completions accetta esattamente lo stesso payload di api.openai.com/v1/chat/completions. Basta cambiare l'URL e la chiave, e il tuo codice esistente funziona.
Non serve reinventare
Lo SDK ufficiale openai per Python (o qualsiasi client HTTP) comunica direttamente con Ollama. Non serve imparare a usare un client specifico.
Disaccoppiamento del runtime
La tua app Python viene eseguita nel proprio container, Ollama nel suo. Il giorno in cui passerai a vLLM o LM Studio, cambierai solo base_url.
Più client simultanei
Diversi script Python, un notebook Jupyter e Open WebUI possono inviare richieste alla stessa istanza Ollama. Il daemon gestisce la coda da solo.
i
API nativa vs compatibile con OpenAI
Ollama mantiene entrambe le API. L'API nativa (/api/chat) espone parametri specifici (num_ctx, num_predict, mirostat), ma è meno portabile. L'API compatibile con OpenAI copre il 95% delle esigenze e resta utilizzabile con qualsiasi altro fornitore. Come scelta predefinita, usa quest'ultima.
#Prerequisiti
✓
Il kit Copilota Locale
Questa guida ti porta al modello. Il kit ti porta al copilota che scrive codice nel tuo editor.
Il daemon deve ascoltare su http://localhost:11434. Verifica con curl http://localhost:11434 — devi vedere "Ollama is running".
Python 3.10+
Gli SDK recenti (openai 1.x) richiedono almeno Python 3.8, ma 3.10+ per le annotazioni moderne.
Un modello compatibile con la chat
ollama pull qwen3.5:9b ou gemma4:12b. Pour le function calling, choisissez un modèle qui le supporte : Qwen 3.5, Granite 4.2, Mistral Small 24B, Devstral.
VRAM sufficiente
Un modello 9B Q4 (come Qwen 3.5 9B) richiede circa 6-7 GB di VRAM, un 12B (Gemma 4 12B) circa 8 GB. Senza GPU funziona anche se a 5-10 tok/s
#1. Le due API dal lato Ollama
Prima di scrivere codice Python, diamo un'occhiata agli endpoint dal terminale per capire bene cosa succede. Con curl parliamo direttamente al daemon, senza alcuna astrazione.
Il secondo restituisce un payload esattamente identico a quello di OpenAI: campi choices[0].message.content, id, model, usage. È questo che rende possibile la sostituzione diretta (drop-in).
→
La chiave API è ignorata ma obbligatoria
Lo SDK openai richiede un parametro api_key. Ollama non verifica nulla — passa "ollama" o qualsiasi stringa non vuota. Se per abitudine inserisci la tua vera chiave OpenAI, rimane sul tuo dispositivo, ma è preferibile usare una stringa neutra per evitare confusione.
#2. Lo SDK OpenAI configurato per usare Ollama
Lo schema di base per integrare l'API di Ollama in Python sta in cinque righe: si installa l'SDK OpenAI, lo si istanzia con la base_url locale e si chiama chat.completions.create come di consueto.
Installazione
pip install openai
client.py — chiamata di base
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # ignoré, mais requis par le SDK
)
reponse = client.chat.completions.create(
model="qwen3.5:9b",
messages=[
{"role": "system", "content": "Tu réponds en français, de façon concise."},
{"role": "user", "content": "Explique en une phrase ce qu'est un LLM."},
],
temperature=0.3,
)
print(reponse.choices[0].message.content)
Avvia lo script. Se Ollama funziona e il modello è scaricato, ottieni una frase. Se vedi un ConnectionRefusedError, verifica con ollama ps che il daemon sia attivo.
model
Il nome esatto come elencato da ollama list (qwen3.5:9b, gemma4:12b, mistral-small, ecc.).
messages
L'elenco dei turni della conversazione. Ruoli supportati: system, user, assistant, tool.
temperature
0 per risposte deterministiche, 0.7 per risposte creative. Per l'estrazione di dati, mantieni il valore a 0 o 0.1.
max_tokens
Limite massimo della risposta. Facoltativo — Ollama applica un valore predefinito ragionevole per num_predict.
#3. Streaming token per token con SSE
Per una buona esperienza utente (chatbot, generazione di testi lunghi), è meglio visualizzare i token man mano che vengono generati invece di aspettare la fine. Ollama supporta lo streaming tramite Server-Sent Events e l'SDK OpenAI permette di gestirlo con un semplice ciclo Python.
streaming.py
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
flux = client.chat.completions.create(
model="qwen3.5:9b",
messages=[{"role": "user", "content": "Raconte une courte histoire de robot."}],
stream=True,
)
for chunk in flux:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
Ogni chunk contiene un delta (la porzione di testo aggiunta). Nell'ultimo chunk, delta.content è impostato a None e finish_reason è valorizzato — è il segnale di arresto.
!
Non dimenticare flush=True
Senza flush=True, Python bufferizza stdout per riga e l'effetto streaming scompare nel terminale. Per un'API HTTP invece, è il server web (uvicorn, gunicorn) a effettuare il flush — non devi preoccupartene.
#4. Modalità JSON per output strutturati
Quando vuoi effettuare il parsing della risposta (estrazione, classificazione, generazione di payload), chiedere "restituisci JSON" nel prompt non basta — il modello inserisce spesso del testo prima o dopo. La modalità JSON obbliga il decoder a produrre solo JSON valido.
json_mode.py
from openai import OpenAI
import json
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
reponse = client.chat.completions.create(
model="qwen3.5:9b",
messages=[
{"role": "system", "content": (
"Tu extrais des informations structurées. "
"Réponds uniquement avec un objet JSON contenant les clés : "
"nom (string), age (int), ville (string)."
)},
{"role": "user", "content": "Marie a 34 ans, elle habite à Lyon."},
],
response_format={"type": "json_object"},
temperature=0,
)
donnees = json.loads(reponse.choices[0].message.content)
print(donnees)
# {'nom': 'Marie', 'age': 34, 'ville': 'Lyon'}
response_format={"type": "json_object"} attiva la modalità JSON. In Ollama, questo si traduce in un vincolo a livello del sampler: ogni token che produrrebbe un JSON non valido viene scartato. È più affidabile che scrivere nel prompt "rispondi in JSON" e pregare.
→
Menziona "JSON" nel prompt
Come nel caso di OpenAI, la modalità JSON richiede almeno una menzione della parola "JSON" nella conversazione (system o user). Altrimenti, alcuni modelli producono un oggetto vuoto. Descrivi lo schema atteso nel prompt di sistema — è questo a guidare il contenuto; la modalità JSON garantisce soltanto la sintassi.
#5. Chiamata di funzione (uso degli strumenti)
Il function calling permette al modello di segnalare che vuole chiamare una funzione Python piuttosto che rispondere direttamente. Non tutti i modelli lo supportano — controlla su ollama.com/library che la dicitura "tools" compaia tra le capacità. Qwen 3.5, Granite 4.2, Mistral Small 24B e Devstral lo supportano nativamente.
tools.py
from openai import OpenAI
import json
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
# 1. Une fonction Python réelle
def meteo(ville: str) -> dict:
# En vrai, vous appelleriez Open-Meteo ou autre
return {"ville": ville, "temperature_c": 18, "conditions": "nuageux"}
# 2. Sa description au format OpenAI
outils = [{
"type": "function",
"function": {
"name": "meteo",
"description": "Donne la météo actuelle d'une ville française.",
"parameters": {
"type": "object",
"properties": {
"ville": {"type": "string", "description": "Nom de la ville"},
},
"required": ["ville"],
},
},
}]
messages = [{"role": "user", "content": "Quel temps fait-il à Bordeaux ?"}]
# 3. Premier appel : le modèle décide d'appeler la fonction
reponse = client.chat.completions.create(
model="qwen3.5:9b",
messages=messages,
tools=outils,
)
appel = reponse.choices[0].message.tool_calls[0]
args = json.loads(appel.function.arguments)
resultat = meteo(**args)
# 4. Second appel : on renvoie le résultat au modèle pour la réponse finale
messages.append(reponse.choices[0].message)
messages.append({
"role": "tool",
"tool_call_id": appel.id,
"content": json.dumps(resultat),
})
finale = client.chat.completions.create(model="qwen3.5:9b", messages=messages)
print(finale.choices[0].message.content)
Il ciclo ha due iterazioni: la prima restituisce un tool_calls (il modello dice "chiama meteo con ville=Bordeaux"), la seconda restituisce la risposta in linguaggio naturale dopo che hai eseguito la funzione e inserito il suo risultato. In produzione, ripeti il ciclo finché tool_calls non è vuoto.
!
I modelli non sono tutti uguali
Con un modello che ha difficoltà a gestire i tools (vecchi Llama 2, Mistral 7B v0.1), otterrai chiamate mal formate o argomenti allucinati. Se accade: (1) verifica che il modello supporti ufficialmente i tools, (2) riduci la temperatura a 0, (3) semplifica lo schema dei parametri.
#6. Esporre Ollama tramite FastAPI
Caso tipico: il tuo frontend chiama il tuo backend Python, che chiama Ollama. FastAPI gestisce correttamente le operazioni asincrone e lo streaming arriva fino al browser tramite una StreamingResponse.
Dipendenze
pip install fastapi uvicorn openai
main.py
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from openai import OpenAI
app = FastAPI()
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
class Question(BaseModel):
message: str
model: str = "qwen3.5:9b"
@app.post("/chat")
def chat(q: Question):
reponse = client.chat.completions.create(
model=q.model,
messages=[{"role": "user", "content": q.message}],
)
return {"reponse": reponse.choices[0].message.content}
@app.post("/chat/stream")
def chat_stream(q: Question):
def generateur():
flux = client.chat.completions.create(
model=q.model,
messages=[{"role": "user", "content": q.message}],
stream=True,
)
for chunk in flux:
delta = chunk.choices[0].delta.content
if delta:
yield delta
return StreamingResponse(generateur(), media_type="text/plain")
Avviare il server
uvicorn main:app --reload --port 8000
Testare in CLI
curl -N -X POST http://localhost:8000/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "Écris un haïku sur Paris."}'
L'opzione -N (--no-buffer) di curl disattiva il buffering lato client per visualizzare lo streaming in tempo reale. Nel frontend JS, leggi il ReadableStream della risposta fetch, come per l'API OpenAI.
#7. Chatbot Flask con storico
Per un chatbot completo, bisogna conservare la cronologia dei messaggi tra un turno e l'altro. Ecco una versione minimalista basata su Flask che conserva la conversazione in memoria (da sostituire con una vera sessione o un database in produzione).
Più cresce la cronologia, più token consumi a ogni chiamata. Per Qwen 3.5, la finestra predefinita in Ollama è di 2048 token: oltre questo limite, i messaggi più vecchi vengono troncati senza avviso. Aumentala tramite l'API nativa oppure sovrascrivendo il valore con un Modelfile (num_ctx 8192 o 32768).
#Per la produzione
Esporre Ollama sulla rete
Per impostazione predefinita, il daemon ascolta solo su 127.0.0.1. Per consentire l'accesso ad altre macchine, avvialo con OLLAMA_HOST=0.0.0.0 e proteggilo con un reverse proxy con autenticazione, altrimenti chiunque sulla rete può usare i tuoi modelli.
Concorrenza e coda
Ollama serializza le richieste per modello. Per servire più utenti in parallelo, avvia più istanze o passa a vLLM che gestisce il batching dinamico nativamente.
Timeouts dal lato client
Una richiesta a un modello non caricato può richiedere 10-30 s (caricamento in VRAM). Imposta il timeout del client OpenAI: OpenAI(..., timeout=120) anziché lasciare il valore predefinito di 10 minuti della libreria; sul reverse proxy, invece, il timeout è spesso breve.
Mantenere il modello caricato
Per impostazione predefinita, Ollama scarica un modello dalla memoria dopo 5 minuti di inattività. Quando usi l'API, passa keep_alive="30m" tramite l'API nativa /api/chat, oppure mantieni un ping periodico per evitare un avvio a freddo alla prima richiesta dell'utente.
Osservabilità
Registra sistematicamente model, prompt_tokens, completion_tokens (presenti in reponse.usage). Sono le tue metriche di inferenza — utili per individuare un modello che rallenta o un prompt che cresce a dismisura.
→
Passare dall'API OpenAI
Se hai già del codice che comunica con api.openai.com, il passaggio a Ollama richiede solo due righe: cambia base_url="https://api.openai.com/v1" in base_url="http://localhost:11434/v1" e adatta il nome del modello. Tutto il resto — streaming, modalità JSON, tools — funziona allo stesso modo. È il grande vantaggio dell'endpoint compatibile con OpenAI.
#Per approfondire
Hai le basi. Tre percorsi per andare oltre in base al tuo caso d'uso:
Costruire un agente che decida da solo
La guida sugli agenti IA locali in Python con LangChain porta il function calling fino a un ciclo completo dell'agente, con gestione di più strumenti e ragionamento in più passaggi.
Aggiungere il RAG basato sui tuoi documenti
Per far sì che la tua app risponda a partire da un corpus interno (PDF, note, codice), collega un database vettoriale. La guida introduttiva al RAG locale fornisce le basi.
Personalizzare il comportamento del modello
Piuttosto che ripetere il system prompt a ogni chiamata, crea una variante tramite Modelfile. La guida alla personalizzazione con Ollama Modelfile mostra come definire stabilmente un assistente in francese o una modalità di programmazione associandoli a un nome di modello riutilizzabile.
Questa guida ti è stata utile?
Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.