DEV-AI

ChatGPT local avec FastAPI : créer son propre assistant IA open-source

Publié le — Par l'équipe DEV-AI

Python FastAPI LLM IA Locale Open Source

ChatGPT Local

FastAPI + LLaMA 3 / Mistral / Phi-3

llama-cpp-python · Ollama · Transformers HuggingFace

Ce que vous allez construire : Une API FastAPI complète qui charge un modèle LLM open-source en local (LLaMA 3, Mistral ou Phi-3), expose une route /ask REST, gère l'historique de conversation, et inclut une interface web simple — le tout sans envoyer une seule donnée dans le cloud.

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 2025

ModèleParamètresRAM / VRAM minQualitéIdéal pour
Phi-3 Mini3.8B4 Go RAM (CPU)⭐⭐⭐Machines légères, Edge
Mistral-7B7B8 Go RAM / 4 Go VRAM (Q4)⭐⭐⭐⭐Meilleur rapport qualité/perf
LLaMA 3 8B8B10 Go RAM / 6 Go VRAM (Q4)⭐⭐⭐⭐Usage général, instructions
LLaMA 3 70B70B40 Go RAM / 24 Go VRAM (Q4)⭐⭐⭐⭐⭐Production, haute qualité
Mixtral 8x7B46B actifs (MoE)24 Go RAM / 16 Go VRAM⭐⭐⭐⭐⭐Raisonnement complexe

Pour la plupart des cas d'usage, Mistral-7B quantisé (GGUF Q4_K_M) est le meilleur point de départ : excellent rapport qualité/performance, tourne sur un GPU 8 Go ou sur un CPU puissant.

Installation des dépendances

# Créer un environnement virtuel
python -m venv venv
source venv/bin/activate  # Linux/Mac
# ou venv\Scripts\activate  # Windows

# Installer les dépendances
pip install fastapi uvicorn python-multipart
pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
pip install huggingface_hub
GPU NVIDIA ? Remplacez la dernière commande par : CMAKE_ARGS="-DLLAMA_CUDA=on" pip install llama-cpp-python pour activer l'accélération CUDA et multiplier les performances par 5 à 10x.

Télécharger un modèle GGUF

Les modèles GGUF sont des versions quantisées optimisées pour llama-cpp. Téléchargez-les depuis HuggingFace :

from huggingface_hub import hf_hub_download

# Télécharger Mistral-7B Q4_K_M (~4 Go)
model_path = hf_hub_download(
    repo_id="TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
    filename="mistral-7b-instruct-v0.2.Q4_K_M.gguf",
    local_dir="./models"
)
print(f"Modèle téléchargé : {model_path}")

Charger le modèle et tester

from llama_cpp import Llama

# Charger le modèle (ajustez n_ctx et n_threads selon votre machine)
llm = Llama(
    model_path="./models/mistral-7b-instruct-v0.2.Q4_K_M.gguf",
    n_ctx=4096,       # taille de la fenêtre de contexte
    n_threads=8,      # nombre de threads CPU
    n_gpu_layers=35,  # couches sur GPU (0 si CPU seulement)
    verbose=False
)

# Test simple
response = llm(
    "Explique-moi les transformers en NLP en 3 phrases.",
    max_tokens=300,
    temperature=0.7,
    stop=["", "[INST]"]
)
print(response["choices"][0]["text"])

Créer l'API FastAPI complète

Voici une API production-ready avec gestion de l'historique de conversation :

from fastapi import FastAPI, HTTPException, Depends, Header
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from llama_cpp import Llama
from typing import List, Optional
import os

app = FastAPI(title="ChatGPT Local API", version="1.0.0")

# CORS — autorise le frontend local
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://localhost:8000"],
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)

# Charger le modèle au démarrage
llm = Llama(
    model_path="./models/mistral-7b-instruct-v0.2.Q4_K_M.gguf",
    n_ctx=4096,
    n_threads=8,
    n_gpu_layers=35,
    verbose=False
)

# Modèles de données
class Message(BaseModel):
    role: str  # "user" ou "assistant"
    content: str

class ChatRequest(BaseModel):
    message: str
    history: Optional[List[Message]] = []
    max_tokens: int = 512
    temperature: float = 0.7

class ChatResponse(BaseModel):
    response: str
    tokens_used: int

# Authentification simple par API key
API_KEY = os.environ.get("API_KEY", "dev-secret-key")

def verify_api_key(authorization: Optional[str] = Header(None)):
    if authorization != f"Bearer {API_KEY}":
        raise HTTPException(status_code=401, detail="Clé API invalide")
    return True

def build_prompt(message: str, history: List[Message]) -> str:
    """Construit un prompt au format Mistral Instruct."""
    prompt = ""
    for msg in history[-6:]:  # Garder les 6 derniers échanges
        if msg.role == "user":
            prompt += f"[INST] {msg.content} [/INST]"
        else:
            prompt += f" {msg.content} "
    prompt += f"[INST] {message} [/INST]"
    return prompt

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest, authenticated: bool = Depends(verify_api_key)):
    """Endpoint principal de conversation avec historique."""
    prompt = build_prompt(request.message, request.history)

    output = llm(
        prompt,
        max_tokens=request.max_tokens,
        temperature=request.temperature,
        stop=["[INST]", ""],
        echo=False
    )

    response_text = output["choices"][0]["text"].strip()
    tokens_used = output["usage"]["total_tokens"]

    return ChatResponse(response=response_text, tokens_used=tokens_used)

@app.get("/health")
async def health():
    """Vérification que l'API est opérationnelle."""
    return {"status": "ok", "model": "mistral-7b-instruct"}

@app.get("/")
async def root():
    return {"message": "ChatGPT Local API — /docs pour la documentation"}

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

Alternative : Ollama + FastAPI

Si vous préférez une approche plus simple sans gérer llama-cpp directement, Ollama expose une API REST compatible OpenAI que vous pouvez proxy via FastAPI :

# Installer Ollama
curl -fsSL https://ollama.ai/install.sh | sh

# Télécharger et lancer Mistral
ollama pull mistral
ollama serve  # tourne sur http://localhost:11434
import httpx
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ChatRequest(BaseModel):
    message: str

@app.post("/chat")
async def chat(request: ChatRequest):
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "http://localhost:11434/api/generate",
            json={"model": "mistral", "prompt": request.message, "stream": False},
            timeout=120
        )
        data = response.json()
        return {"response": data["response"]}

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