Intégrer Ollama dans une application Python via l'API REST
Ollama expose deux APIs HTTP sur le port 11434 : une API native (/api/generate, /api/chat) et une API OpenAI-compatible (/v1/chat/completions). Cette dernière est la voie royale pour l'intégration Ollama API Python : votre code utilise exactement le même SDK qu'avec GPT-4, mais tourne sur votre machine. Ce guide couvre les patterns concrets — streaming, JSON structuré, function calling — avec des exemples FastAPI et Flask prêts à coller dans un projet.
Par Mohamed Meguedmi·Màj 2026-05-15·Testé sur Windows, macOS, Linux
#Pourquoi passer par l'API REST
La CLI ollama run est pratique pour tester, mais elle n'est pas faite pour être appelée depuis une application. L'API REST, elle, est conçue pour ça : requêtes HTTP standards, JSON en entrée et sortie, streaming via Server-Sent Events. C'est ce que toutes les interfaces (Open WebUI, Continue.dev, LangChain) utilisent sous le capot.
Compatibilité OpenAI
L'endpoint /v1/chat/completions accepte exactement le même payload que api.openai.com/v1/chat/completions. Vous changez l'URL et la clé, et votre code existant fonctionne.
Pas de réinvention
Le SDK officiel openai en Python (ou n'importe quel client HTTP) parle directement à Ollama. Aucun client spécifique à apprendre.
Découplage du runtime
Votre app Python tourne dans son conteneur, Ollama dans le sien. Le jour où vous passez à vLLM ou LM Studio, vous ne changez que la base_url.
Multi-clients simultanés
Plusieurs scripts Python, un notebook Jupyter et Open WebUI peuvent taper sur la même instance Ollama. Le daemon gère la file d'attente tout seul.
i
API native vs OpenAI-compatible
Ollama maintient les deux. L'API native (/api/chat) expose des paramètres spécifiques (num_ctx, num_predict, mirostat) mais elle est moins portable. L'API OpenAI-compatible couvre 95 % des besoins et reste utilisable avec n'importe quel autre fournisseur. Par défaut, partez sur celle-ci.
#Prérequis
Ollama installé et démarré
Le daemon doit écouter sur http://localhost:11434. Vérifiez avec curl http://localhost:11434 — vous devez voir "Ollama is running".
Python 3.10+
Les SDK récents (openai 1.x) demandent au moins Python 3.8, mais 3.10+ pour les annotations modernes.
Un modèle compatible chat
ollama pull llama3.1:8b ou qwen3:14b. Pour le function calling, choisissez un modèle qui le supporte : Llama 3.1, Qwen 3, Mistral Instruct v0.3, Granite 3.
VRAM suffisante
Un 7B Q4 demande environ 5 Go de VRAM, un 14B environ 9 Go. Sans GPU, ça tourne aussi mais à 5-10 tok/s.
#1. Les deux APIs côté Ollama
Avant d'écrire du Python, jetons un œil aux endpoints depuis le terminal pour bien voir ce qui se passe. Avec curl, on parle directement au daemon, sans aucune abstraction.
Le second renvoie un payload strictement identique à celui d'OpenAI : champs choices[0].message.content, id, model, usage. C'est ce qui rend le drop-in possible.
→
La clé API est ignorée mais obligatoire
Le SDK openai exige un paramètre api_key. Ollama ne vérifie rien — passez "ollama" ou n'importe quelle chaîne non-vide. Si vous mettez votre vraie clé OpenAI par habitude, elle reste sur votre machine, mais préférez une chaîne neutre pour éviter les confusions.
#2. Le SDK OpenAI pointé sur Ollama
Le pattern de base d'une intégration Ollama API Python tient en cinq lignes : on installe le SDK OpenAI, on l'instancie avec la base_url locale, et on appelle chat.completions.create comme d'habitude.
Installation
pip install openai
client.py — appel de 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="llama3.1:8b",
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)
Lancez le script. Si Ollama tourne et que le modèle est téléchargé, vous obtenez une phrase. Si vous voyez une ConnectionRefusedError, vérifiez avec ollama ps que le daemon est bien actif.
model
Le nom exact tel que listé par ollama list (llama3.1:8b, qwen3:14b, mistral:7b-instruct, etc.).
messages
La liste des tours de conversation. Rôles supportés : system, user, assistant, tool.
temperature
0 pour déterministe, 0.7 pour créatif. Pour de l'extraction de données, restez à 0 ou 0.1.
max_tokens
Limite supérieure sur la réponse. Optionnel — Ollama applique un défaut de num_predict raisonnable.
#3. Streaming token par token avec SSE
Pour une UX correcte (chatbot, génération longue), vous voulez afficher les tokens au fur et à mesure plutôt qu'attendre la fin. Ollama supporte le streaming via Server-Sent Events, et le SDK OpenAI en fait une boucle Python triviale.
streaming.py
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
flux = client.chat.completions.create(
model="llama3.1:8b",
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()
Chaque chunk contient un delta (le morceau de texte ajouté). Le dernier chunk a delta.content à None et finish_reason rempli — c'est le signal d'arrêt.
!
Ne pas oublier flush=True
Sans flush=True, Python bufferise stdout par ligne et l'effet streaming disparaît dans le terminal. Pour une API HTTP en revanche, c'est le serveur web (uvicorn, gunicorn) qui flush — vous n'avez pas à vous en occuper.
#4. JSON mode pour des sorties structurées
Quand vous voulez parser la réponse (extraction, classification, génération de payload), demander "renvoie du JSON" dans le prompt ne suffit pas — le modèle insère souvent du texte autour. Le JSON mode force le décodeur à ne produire que du JSON valide.
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="llama3.1:8b",
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"} active le mode JSON. Côté Ollama, ça se traduit par une contrainte au niveau du sampler : tout token qui produirait un JSON invalide est rejeté. C'est plus fiable que de prompter "réponds en JSON" et de prier.
→
Mentionnez "JSON" dans le prompt
Comme chez OpenAI, le mode JSON impose au moins une mention du mot "JSON" dans la conversation (system ou user). Sans ça, certains modèles produisent un objet vide. Décrivez le schéma attendu dans le system prompt — c'est ce qui guide le contenu, le mode JSON ne fait que garantir la syntaxe.
#5. Function calling (tool use)
Le function calling permet au modèle de signaler qu'il veut appeler une fonction Python plutôt que de répondre directement. Tous les modèles ne le supportent pas — vérifiez sur ollama.com/library que la mention "tools" apparaît dans les capacités. Llama 3.1, Qwen 3, Mistral Instruct v0.3 et Granite 3 le supportent nativement.
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="llama3.1:8b",
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="llama3.1:8b", messages=messages)
print(finale.choices[0].message.content)
La boucle a deux tours : le premier renvoie un tool_calls (le modèle dit "appelle meteo avec ville=Bordeaux"), le second renvoie la réponse naturelle après que vous avez exécuté la fonction et injecté son résultat. En production, vous bouclez tant que tool_calls est rempli.
!
Tous les modèles ne sont pas égaux
Avec un modèle qui supporte mal les tools (vieux Llama 2, Mistral 7B v0.1), vous obtiendrez des appels mal formés ou des arguments hallucinés. Si ça vous arrive : (1) confirmez que le modèle supporte officiellement les tools, (2) baissez la température à 0, (3) simplifiez le schéma des paramètres.
#6. Exposer Ollama via FastAPI
Cas typique : votre frontend appelle votre backend Python, qui appelle Ollama. FastAPI gère l'asynchrone proprement et le streaming arrive jusqu'au navigateur via un StreamingResponse.
Dépendances
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 = "llama3.1:8b"
@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")
Lancer le serveur
uvicorn main:app --reload --port 8000
Tester en CLI
curl -N -X POST http://localhost:8000/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "Écris un haïku sur Paris."}'
L'option -N (--no-buffer) de curl désactive le buffering côté client pour voir le streaming en direct. Côté frontend JS, vous lisez le ReadableStream de la réponse fetch — pareil que pour l'API OpenAI.
#7. Chatbot Flask avec historique
Pour un chatbot complet, il faut garder l'historique des messages entre les tours. Voici une version Flask minimaliste qui stocke la conversation en mémoire (à remplacer par une vraie session/DB en production).
Plus l'historique grossit, plus vous consommez de tokens à chaque appel. Pour Llama 3.1, la fenêtre par défaut côté Ollama est de 2048 tokens — au-delà, les anciens messages sont silencieusement coupés. Augmentez via l'API native ou en surchargeant avec un Modelfile (num_ctx 8192 ou 32768).
#Pour la production
Exposer Ollama sur le réseau
Par défaut, le daemon n'écoute que sur 127.0.0.1. Pour autoriser d'autres machines, lancez avec OLLAMA_HOST=0.0.0.0 — et mettez un reverse proxy avec auth devant, sinon n'importe qui sur le réseau peut tirer sur vos modèles.
Concurrence et file d'attente
Ollama sérialise les requêtes par modèle. Pour servir plusieurs utilisateurs en parallèle, lancez plusieurs instances ou passez à vLLM qui gère le batching dynamique nativement.
Timeouts côté client
Une requête sur un modèle non-chargé peut prendre 10-30 s (chargement en VRAM). Réglez le timeout du client OpenAI : OpenAI(..., timeout=120) plutôt que le défaut de 10 minutes côté lib mais souvent court côté reverse proxy.
Garder le modèle chaud
Par défaut, Ollama décharge un modèle après 5 minutes d'inactivité. En API, passez keep_alive="30m" via l'API native /api/chat, ou laissez un ping périodique pour éviter le froid sur la première requête utilisateur.
Observabilité
Logguez systématiquement model, prompt_tokens, completion_tokens (présent dans reponse.usage). Ce sont vos métriques d'inférence — utiles pour spotter un modèle qui ralentit ou un prompt qui explose.
→
Migrer depuis l'API OpenAI
Si vous avez déjà du code qui parle à api.openai.com, la bascule vers Ollama tient en deux lignes : changez base_url="https://api.openai.com/v1" en base_url="http://localhost:11434/v1" et adaptez le nom du modèle. Tout le reste — streaming, JSON mode, tools — fonctionne pareil. C'est le grand intérêt de l'endpoint OpenAI-compatible.
#Pour aller plus loin
Vous avez les briques de base. Trois directions pour aller plus loin selon votre cas d'usage :
Construire un agent qui décide tout seul
Le guide sur les agents IA locaux en Python avec LangChain pousse le function calling jusqu'à la boucle d'agent complète, avec gestion d'outils multiples et raisonnement multi-étapes.
Ajouter du RAG sur vos documents
Pour que votre app réponde à partir d'un corpus interne (PDF, notes, code), branchez une base vectorielle. Le guide d'introduction au RAG local pose les fondations.
Personnaliser le comportement du modèle
Plutôt que de répéter le system prompt à chaque appel, créez une variante via Modelfile. Le guide personnalisation Ollama Modelfile montre comment figer un assistant FR ou un mode coder dans un nom de modèle réutilisable.
Ce guide vous a aidé ?
Un retour, une erreur, une précision ? Faites-nous signe, ça améliore le guide pour tout le monde.