API IA privée avec FastAPI et Ollama en Python 2026 : LLM local, streaming et Docker
Publié le — Mis à jour le — Par l'équipe DEV-AI
API IA Privée — FastAPI + Ollama
Gemma 3 · Llama 3.2 · Qwen2.5 · Mistral
REST endpoint · Streaming SSE · Historique · Docker
/chat, endpoint streaming SSE /chat/stream, gestion de l'historique de conversation, authentification par API key. Modèles supportés : Gemma 3, Llama 3.2, Qwen2.5, Mistral. Zero donnée dans le cloud, déploiement Docker inclus.
Pourquoi héberger son propre assistant IA ?
De plus en plus d'équipes cherchent à déployer des assistants IA en interne, sans dépendance aux APIs d'OpenAI ou d'Anthropic. Les raisons sont multiples :
- Confidentialité des données : aucune donnée ne quitte vos serveurs — essentiel pour les projets liés au RGPD, à la santé ou aux données clients sensibles
- Maîtrise des coûts : à volume élevé, un modèle local devient beaucoup moins cher que des appels API (GPT-4 coûte ~0,01 $/1000 tokens ; un serveur GPU loué à l'heure peut traiter des millions de tokens)
- Contrôle total : vous choisissez le modèle, les paramètres de génération, le system prompt et la façon dont les réponses sont filtrées
- Latence : un modèle local bien configuré peut répondre en quelques secondes, sans dépendre des latences réseau ou des rate limits des APIs externes
Choisir son modèle : comparatif 2026
| Modèle Ollama | Taille | VRAM min (Q4) | Point fort |
|---|---|---|---|
| gemma3:1b | 1B | ~2 Go | Edge, latence ultra-faible |
| gemma3:4b | 4B | ~4 Go | Multimodal, 128K, sweet spot 2026 |
| llama3.2:3b | 3B | ~2,5 Go | Meta Llama License, rapide |
| qwen2.5:7b | 7B | ~5 Go | Code + math, Apache 2.0 |
| mistral:7b | 7B | ~5 Go | Instruction-following solide |
Pour un assistant IA privé polyvalent en 2026, gemma3:4b est le meilleur choix : multimodal, contexte 128K, 4 Go de VRAM en Q4. Pour le code spécialisé, qwen2.5:7b. Pour un serveur CPU sans GPU, gemma3:1b ou llama3.2:3b. Pour en savoir plus : guide Gemma 3 et guide Ollama complet.
Installation
# 1. Installer Ollama
curl -fsSL https://ollama.com/install.sh | sh
ollama pull gemma3:4b # ou llama3.2:3b, qwen2.5:7b, mistral:7b
# 2. Créer un environnement virtuel Python
python -m venv venv && source venv/bin/activate
# 3. Installer les dépendances
pip install fastapi uvicorn httpx python-multipart
API FastAPI + Ollama — code complet 2026
L'approche recommandée : FastAPI en façade, Ollama comme backend LLM. FastAPI gère l'authentification, le CORS et la validation des données ; Ollama gère le modèle et l'inférence. Zéro dépendance GPU dans FastAPI lui-même.
# main.py
from fastapi import FastAPI, HTTPException, Depends, Header
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import List, Optional, AsyncIterator
import httpx, os, json
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
MODEL = os.environ.get("MODEL", "gemma3:4b")
API_KEY = os.environ.get("API_KEY", "change-me")
app = FastAPI(title="API IA Privée", version="2.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
# --- Modèles de données ---
class Message(BaseModel):
role: str # "user", "assistant" ou "system"
content: str
class ChatRequest(BaseModel):
message: str
history: Optional[List[Message]] = []
system: str = "Tu es un assistant IA utile et précis. Réponds en français."
temperature: float = 0.7
max_tokens: int = 1024
class ChatResponse(BaseModel):
response: str
model: str
# --- Auth ---
def verify_key(authorization: Optional[str] = Header(None)):
if authorization != f"Bearer {API_KEY}":
raise HTTPException(status_code=401, detail="Clé API invalide")
# --- Endpoint REST classique ---
@app.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest, _=Depends(verify_key)):
messages = [{"role": "system", "content": req.system}]
messages += [{"role": m.role, "content": m.content} for m in req.history[-8:]]
messages.append({"role": "user", "content": req.message})
async with httpx.AsyncClient(timeout=120) as client:
r = await client.post(
f"{OLLAMA_URL}/api/chat",
json={"model": MODEL, "messages": messages,
"stream": False,
"options": {"temperature": req.temperature, "num_predict": req.max_tokens}}
)
r.raise_for_status()
data = r.json()
return ChatResponse(response=data["message"]["content"], model=MODEL)
# --- Endpoint streaming SSE ---
@app.post("/chat/stream")
async def chat_stream(req: ChatRequest, _=Depends(verify_key)):
messages = [{"role": "system", "content": req.system}]
messages += [{"role": m.role, "content": m.content} for m in req.history[-8:]]
messages.append({"role": "user", "content": req.message})
async def token_generator() -> AsyncIterator[str]:
async with httpx.AsyncClient(timeout=300) as client:
async with client.stream(
"POST", f"{OLLAMA_URL}/api/chat",
json={"model": MODEL, "messages": messages, "stream": True}
) as r:
async for line in r.aiter_lines():
if line:
chunk = json.loads(line)
token = chunk.get("message", {}).get("content", "")
if token:
yield f"data: {json.dumps({'token': token})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(token_generator(), media_type="text/event-stream")
@app.get("/health")
async def health():
return {"status": "ok", "model": MODEL, "ollama": OLLAMA_URL}
Démarrer l'API
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
L'API est maintenant accessible sur http://localhost:8000. Rendez-vous sur http://localhost:8000/docs pour la documentation Swagger interactive.
Tester l'API avec curl
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dev-secret-key" \
-d '{
"message": "Quelle est la différence entre supervised et unsupervised learning ?",
"history": [],
"max_tokens": 400,
"temperature": 0.7
}'
Interface web minimaliste
Ajoutez ce fichier static/index.html pour une interface de chat :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon ChatGPT Local</title>
<style>
body { font-family: sans-serif; max-width: 700px; margin: 2rem auto; padding: 1rem; }
#chat { border: 1px solid #ddd; border-radius: 8px; height: 400px; overflow-y: auto; padding: 1rem; margin-bottom: 1rem; }
.user { color: #1e40af; margin: 0.5rem 0; }
.assistant { color: #065f46; margin: 0.5rem 0; }
input { width: 80%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 0.5rem 1rem; background: #6366f1; color: white; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<h1>Mon Assistant IA Local</h1>
<div id="chat"></div>
<input id="input" type="text" placeholder="Posez votre question..." />
<button onclick="sendMessage()">Envoyer</button>
<script>
const history = [];
async function sendMessage() {
const input = document.getElementById('input');
const message = input.value.trim();
if (!message) return;
appendMessage('user', message);
history.push({ role: 'user', content: message });
input.value = '';
const res = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer dev-secret-key'
},
body: JSON.stringify({ message, history: history.slice(0, -1) })
});
const data = await res.json();
appendMessage('assistant', data.response);
history.push({ role: 'assistant', content: data.response });
}
function appendMessage(role, text) {
const div = document.createElement('div');
div.className = role;
div.textContent = (role === 'user' ? '🧑 ' : '🤖 ') + text;
document.getElementById('chat').appendChild(div);
document.getElementById('chat').scrollTop = 9999;
}
document.getElementById('input').addEventListener('keydown', e => {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>
Servez ce fichier depuis FastAPI en ajoutant dans main.py :
app.mount("/", StaticFiles(directory="static", html=True), name="static")
Déploiement avec Docker
Pour un déploiement reproductible, voici un Dockerfile complet :
FROM python:3.11-slim
WORKDIR /app
# Dépendances système
RUN apt-get update && apt-get install -y \
build-essential cmake git \
&& rm -rf /var/lib/apt/lists/*
# Dépendances Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Code et modèle
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Builder et lancer
docker build -t chatgpt-local .
docker run -p 8000:8000 -v $(pwd)/models:/app/models chatgpt-local
Déploiement Docker — Ollama + FastAPI
En production, Ollama et FastAPI tournent dans deux conteneurs distincts reliés par un réseau Docker. Le docker-compose.yml complet :
# docker-compose.yml
services:
ollama:
image: ollama/ollama
ports: ["11434:11434"]
volumes: ["ollama_data:/root/.ollama"]
deploy:
resources:
reservations:
devices: [{driver: nvidia, count: all, capabilities: [gpu]}]
api:
build: .
ports: ["8000:8000"]
environment:
- OLLAMA_URL=http://ollama:11434
- MODEL=gemma3:4b
- API_KEY=change-me-in-production
depends_on: [ollama]
volumes:
ollama_data:
# Dockerfile pour l'API FastAPI
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Lancer et pré-charger le modèle
docker compose up -d
docker exec -it <ollama_container> ollama pull gemma3:4b
Pour aller plus loin
- Streaming avancé : voir notre guide FastAPI streaming LLM — SSE, WebSocket, métriques TTFT/TPS
- RAG : connectez votre API à une base vectorielle — guide RAG avec Ollama et ChromaDB
- Modèles multimodaux : Gemma 3 4B accepte des images dans les messages Ollama — ajoutez
"images": ["photo.png"]dans votreChatRequest - Fine-tuning métier : guide Unsloth — fine-tuner Gemma 3 / Llama 3 sur vos données, exporter en GGUF pour Ollama
- Rate limiting :
slowapipour limiter par IP,redispour un compteur distribué
Maîtrisez le NLP derrière les LLMs
Transformers, tokenisation, attention mechanism, fine-tuning — tout ce qu'il faut comprendre pour vraiment maîtriser les modèles comme Mistral et LLaMA. Formation pratique avec projets concrets en Python.
Découvrir la formation NLP →