DEV-AI

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

Python FastAPI LLM IA Locale Open Source

API IA Privée — FastAPI + Ollama

Gemma 3 · Llama 3.2 · Qwen2.5 · Mistral

REST endpoint · Streaming SSE · Historique · Docker

Ce que vous allez construire : Une API FastAPI complète connectée à Ollama — endpoint REST /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 :

Choisir son modèle : comparatif 2026

Modèle OllamaTailleVRAM min (Q4)Point fort
gemma3:1b1B~2 GoEdge, latence ultra-faible
gemma3:4b4B~4 GoMultimodal, 128K, sweet spot 2026
llama3.2:3b3B~2,5 GoMeta Llama License, rapide
qwen2.5:7b7B~5 GoCode + math, Apache 2.0
mistral:7b7B~5 GoInstruction-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

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 →
← Retour au blog

Questions fréquentes

Quel modèle open-source choisir pour un chatbot local en Python ?

Pour un CPU standard (8-16 Go RAM) : Phi-3 Mini ou Mistral-7B quantisé en GGUF. Pour un GPU ≥ 8 Go VRAM : LLaMA 3 8B ou Mistral-7B en FP16. Mistral-7B offre le meilleur rapport qualité/performance pour la majorité des cas d'usage.

Quelle est la différence entre llama-cpp-python et Ollama ?

llama-cpp-python est une librairie Python bas niveau — contrôle total, intégration directe dans du code Python. Ollama est un outil de haut niveau qui gère le téléchargement et l'exposition des modèles via une API REST compatible OpenAI. Ollama est plus simple pour commencer.

Comment sécuriser l'API FastAPI LLM sur le réseau ?

Ajoutez une authentification API key via un middleware ou un header HTTP Authorization. Limitez le nombre de requêtes avec slowapi (rate limiting). Validez les inputs utilisateur contre les prompt injections. Utilisez HTTPS via un reverse proxy Nginx ou Caddy.

Peut-on déployer un LLM local avec FastAPI sur un serveur cloud ?

Oui. Les options économiques : RunPod (GPU A100 à l'heure), Vast.ai, ou Hetzner (serveurs dédiés avec GPU). Pour les cas sans GPU, Phi-3 Mini peut tourner sur un VPS CPU performant. Docker + Docker Compose simplifient le déploiement.

Partager cet article :

À propos de DEV-AI

DEV-AI est une plateforme française dédiée aux formations IA, aux outils NLP open-source et au développement d'applications d'intelligence artificielle. Nous publions régulièrement des tutoriels techniques et des guides pratiques pour les développeurs francophones.

Lire aussi

Aller plus loin

Formations recommandées