Déployer des fonctionnalités IA en production : GPT-4o intégré dans une plateforme live
Nous n'avons pas juste ajouté un chatbot — nous avons intégré l'IA dans le moteur de priorisation central de Fygurs. Voici comment nous avons conçu les prompts, géré la charge asynchrone, et déployé GPT-4o-mini sur Azure sans ajouter de latence à notre chemin critique.
La première fois que j'ai intégré GPT-4o-mini dans notre moteur de priorisation chez Fygurs, la latence était inacceptable — 4 secondes sur un chemin de requête qui devait rester sous 800 ms. Le modèle était correct. L'architecture autour ne l'était pas. Voici ce que nous avons changé : traitement asynchrone, conception des prompts, et structuration de l'intégration Azure OpenAI pour que l'IA soit une dépendance de service, et non un goulot d'étranglement.
Les Grands Modèles de Langage : Fondamentaux
Les Grands Modèles de Langage (LLM) sont des réseaux de neurones entraînés sur d'immenses corpus de texte pour prédire le prochain token dans une séquence. Comprendre leur fonctionnement permet de les utiliser efficacement en production.
ARCHITECTURE D'UN LLM
Texte d'entrée Tokenisation Embedding
"Bonjour monde" → [15496, 995] → [0.1, -0.3, ...]
│
▼
┌─────────────────────────────────────┐
│ Couches Transformer │
│ │
│ Auto-Attention → Feed-Forward │
│ ↓ ↓ │
│ Auto-Attention → Feed-Forward │
│ ↓ ↓ │
│ Auto-Attention → Feed-Forward │
│ │
└─────────────────┬───────────────────┘
│
▼
Probabilités de sortie → Token suivant
"!" (0.3), "." (0.2), "," (0.1)...
Concepts Clés
- Tokens : Le texte est découpé en sous-mots (GPT-4 utilise ~100k de vocabulaire). "bonheur" → ["bon", "heur"]
- Fenêtre de contexte : Nombre maximum de tokens que le modèle peut traiter en une fois (GPT-4o : 128k tokens)
- Température : Contrôle l'aléatoire. 0 = déterministe, 1 = créatif, 2 = chaotique
- Top-p (Nucleus Sampling) : Ne considère que les tokens dont la probabilité cumulée dépasse p
- Max Tokens : Limite la longueur de la réponse (entrée + sortie doit tenir dans la fenêtre de contexte)
API de Complétion de Chat
Les LLM modernes utilisent une API basée sur les messages avec des rôles qui structurent la conversation :
RÔLES DES MESSAGES
┌──────────────────────────────────────────────────────────────┐
│ Message Système │
│ "Tu es un assistant qui répond toujours en JSON" │
│ → Définit le comportement, la personnalité et le format │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Message Utilisateur │
│ "Génère 3 recommandations de produits électroniques" │
│ → La requête ou la question réelle │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Message Assistant │
│ {"recommendations": [...]} │
│ → La réponse du modèle │
└──────────────────────────────────────────────────────────────┘
Mode JSON
Pour les systèmes en production, il faut une sortie structurée. Le mode JSON garantit que le modèle retourne du JSON valide :
response = await client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Retourne un objet JSON avec un tableau 'items'"},
{"role": "user", "content": "Liste 3 langages de programmation"}
],
response_format={"type": "json_object"} # Impose du JSON valide
)
Azure OpenAI Service
Azure OpenAI fournit un accès de niveau entreprise aux modèles OpenAI, avec des options supplémentaires de sécurité, conformité et déploiement régional.
ARCHITECTURE AZURE OPENAI
┌─────────────────┐ ┌─────────────────────────────────────────┐
│ Application │ │ Azure OpenAI Service │
│ │ │ │
│ AsyncAzureOAI │────▶│ ┌─────────────┐ ┌─────────────────┐ │
│ Client │ │ │ Endpoint │ │ Déploiements │ │
│ │ │ │ (Régional) │ │ │ │
└─────────────────┘ │ └─────────────┘ │ • gpt-4o │ │
│ │ • gpt-4o-mini │ │
│ │ • embeddings │ │
│ └─────────────────┘ │
└─────────────────────────────────────────┘
Différences avec OpenAI
- Déploiements : Vous créez des déploiements nommés de modèles (pas seulement des noms de modèles)
- Endpoints : URLs spécifiques à la région (ex : eastus.api.cognitive.microsoft.com)
- Versions d'API : Azure utilise des versions d'API datées pour la stabilité
- Authentification : Clés API ou Azure Active Directory
Fine-Tuning : Personnaliser les Modèles
Le fine-tuning adapte un modèle pré-entraîné à votre domaine ou tâche spécifique. Au lieu d'entraîner depuis zéro, vous ajustez les poids du modèle à partir de vos propres exemples.
PIPELINE DE FINE-TUNING
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Données │ │ Modèle de base │ │ Modèle │
│ d'entraînement │ │ │ │ Fine-tuné │
│ │ │ │ │ │
│ {"prompt": ... │────▶│ GPT-4o │────▶│ Spécialiste │
│ "completion"} │ │ (figé) │ │ de votre │
│ │ │ │ │ Domaine │
└─────────────────┘ └─────────────────┘ └─────────────────┘
100-1000 Plus rapide +
exemples Moins cher +
Plus précis
Quand Faire du Fine-Tuning
- Format cohérent : Toujours produire selon un schéma JSON précis
- Terminologie métier : Utiliser correctement le vocabulaire de votre industrie
- Style/ton : Correspondre systématiquement à la voix de votre marque
- Réduction des coûts : Un petit modèle fine-tuné peut atteindre la qualité d'un grand modèle
Format des Données d'Entraînement (JSONL)
{"messages": [{"role": "system", "content": "Tu es un assistant qui extrait des données structurées."}, {"role": "user", "content": "Extrais les entités de : Jean a visité Paris lundi dernier"}, {"role": "assistant", "content": "{"personne": "Jean", "lieu": "Paris", "date": "lundi dernier"}"}]}
{"messages": [{"role": "system", "content": "Tu es un assistant qui extrait des données structurées."}, {"role": "user", "content": "Extrais les entités de : La réunion est à 15h en Salle 101"}, {"role": "assistant", "content": "{"heure": "15h", "lieu": "Salle 101"}"}]}
Fine-Tuning Efficace en Paramètres (PEFT)
Le fine-tuning complet met à jour tous les poids du modèle, ce qui est coûteux. Les méthodes PEFT comme LoRA (Low-Rank Adaptation) gèlent la majorité des poids et n'entraînent que de petites couches d'adaptation. Ceci est utilisé pour les modèles open-source auto-hébergés (Llama, Mistral). Pour Azure OpenAI, utilisez plutôt l'API de fine-tuning managée.
ARCHITECTURE LORA ┌───────────────────────────────────────────────────────────┐ │ Couche Transformer │ │ │ │ ┌───────────────────┐ ┌───────────────────┐ │ │ │ Poids │ │ Adaptateur LoRA │ │ │ │ Originaux (W) │ │ │ │ │ │ │ │ ┌─────┐ ┌─────┐ │ │ │ │ [Figés] │ + │ │ A │×│ B │ │ │ │ │ │ │ │ r×d │ │ d×r │ │ │ │ │ │ │ └─────┘ └─────┘ │ │ │ │ │ │ │ │ │ │ │ │ [Entraînable] │ │ │ └─────────┬─────────┘ └─────────┬─────────┘ │ │ │ │ │ │ └────────────┬───────────────┘ │ │ │ │ │ Sortie = Wx + BAx │ └───────────────────────────────────────────────────────────┘ Fine-tuning complet : Met à jour ~7 milliards de paramètres LoRA (r=16) : Met à jour seulement ~4M de paramètres (0,05%)
LoRA avec Hugging Face
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
# Charger le modèle de base
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b")
# Configurer LoRA
lora_config = LoraConfig(
r=16, # Rang des matrices d'adaptation
lora_alpha=32, # Facteur d'échelle
target_modules=["q_proj", "v_proj"], # Couches à adapter
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# Créer le modèle PEFT
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.06%
RAG : Génération Augmentée par Récupération
Le RAG combine la recherche vectorielle avec la génération LLM. Au lieu de se reposer uniquement sur les données d'entraînement du modèle, vous récupérez des documents pertinents et les intégrez dans le prompt.
ARCHITECTURE RAG
Requête utilisateur Réponse
│ ▲
▼ │
┌───────────────────┐ ┌───────────────────┐
│ Modèle │ │ LLM │
│ d'Embedding │ │ Génération │
└─────────┬─────────┘ └─────────┬─────────┘
│ │
▼ │
┌───────────────────┐ ┌───────────────────┐ ┌───────┴─────────┐
│ Vecteur de │───▶│ Recherche │─▶│ Prompt │
│ Requête │ │ Vectorielle │ │ Augmenté │
│ [0.1, -0.3...] │ │ (Top K=3) │ │ │
└───────────────────┘ └─────────┬─────────┘ │ Contexte: ... │
│ │ Question: ... │
┌─────────┴─────────┐ └─────────────────┘
│ Base Vectorielle│
│ │
│ ● Doc 1 [0.2] │
│ ● Doc 2 [-0.1] │
│ ● Doc 3 [0.4] │
└──────────────────┘
Implémentation RAG
class RAGService:
def __init__(self, vector_store):
self.vector_store = vector_store
async def query(self, question: str, top_k: int = 3) -> str:
# 1. Encoder la question
query_embedding = await get_embedding(question)
# 2. Rechercher les documents pertinents
results = self.vector_store.search(
vector=query_embedding,
top_k=top_k
)
# 3. Construire le contexte à partir des documents récupérés
context = "\n\n".join([
f"Document {i+1}:\n{doc.content}"
for i, doc in enumerate(results)
])
# 4. Générer une réponse avec le contexte
prompt = f"""Réponds à la question en te basant uniquement sur le contexte ci-dessous.
Contexte :
{context}
Question : {question}
Réponse :"""
response = await AIClient.generate(
system_prompt="Tu es un assistant. Utilise uniquement les informations du contexte fourni.",
user_prompt=prompt,
temperature=0.3
)
return response
Quand Utiliser RAG vs Fine-Tuning
| Cas d'usage | RAG | Fine-Tuning |
|---|---|---|
| Informations à jour | Meilleur choix | Nécessite ré-entraînement |
| Citer des sources | Intégré | Impossible |
| Style/format cohérent | Limité | Meilleur choix |
| Terminologie métier | Correct | Meilleur choix |
| Coût par requête | Élevé (récupération) | Faible |
Intégration Django
Intégrer l'IA dans Django nécessite une initialisation soignée. Charger les prompts et les modèles au démarrage évite les I/O répétées et garantit une gestion rapide des requêtes.
AppConfig : Initialisation au Démarrage
La méthode AppConfig.ready() de Django s'exécute une seule fois au démarrage de l'application. Utilisez-la pour charger les prompts en mémoire :
# apps/ai_service/apps.py
from django.apps import AppConfig
class AIServiceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.ai_service'
def ready(self):
# Import ici pour éviter les imports circulaires
from .services.prompt_engine import PromptEngine
# Charger tous les prompts en mémoire au démarrage
PromptEngine.load_prompts()
Commandes de Gestion : Initialisation Async
Pour les clients asynchrones nécessitant la boucle d'événements, utilisez des commandes de gestion :
# apps/ai_service/management/commands/init_models.py
from django.core.management.base import BaseCommand
import asyncio
class Command(BaseCommand):
help = 'Initialise les modèles IA'
def handle(self, *args, **options):
from apps.ai_service.services.ai_client import AIClient
loop = asyncio.get_event_loop()
loop.run_until_complete(AIClient.initialize())
self.stdout.write(self.style.SUCCESS('Modèles IA initialisés'))
Moteur de Prompts : Gestion par Fichiers
Gérer les prompts comme des chaînes de caractères dans le code est ingérable. Un moteur de prompts adéquat charge les templates depuis des fichiers, supporte plusieurs langues et permet aux non-développeurs de modifier les prompts.
STRUCTURE DES FICHIERS DE PROMPTS
resources/
└── prompts/
├── system/
│ ├── generator_fr.txt
│ ├── generator_en.txt
│ ├── summarizer_fr.txt
│ └── summarizer_en.txt
└── user/
├── generation_template_fr.txt
├── generation_template_en.txt
├── summary_template_fr.txt
└── summary_template_en.txt
Implémentation du PromptEngine
# services/prompt_engine.py
import os
from string import Template
from typing import Dict
class PromptEngine:
# Stockage des prompts au niveau de la classe
SYSTEM_PROMPTS: Dict[str, Dict[str, str]] = {}
USER_TEMPLATES: Dict[str, Dict[str, str]] = {}
PROMPTS_DIR = os.path.join(os.path.dirname(__file__), '..', 'resources', 'prompts')
@classmethod
def load_prompts(cls):
"""Charge tous les prompts depuis les fichiers en mémoire au démarrage."""
# Charger les prompts système
system_dir = os.path.join(cls.PROMPTS_DIR, 'system')
for filename in os.listdir(system_dir):
name, lang = cls._parse_filename(filename)
if name not in cls.SYSTEM_PROMPTS:
cls.SYSTEM_PROMPTS[name] = {}
with open(os.path.join(system_dir, filename), 'r') as f:
cls.SYSTEM_PROMPTS[name][lang] = f.read()
# Charger les templates utilisateur
user_dir = os.path.join(cls.PROMPTS_DIR, 'user')
for filename in os.listdir(user_dir):
name, lang = cls._parse_filename(filename)
if name not in cls.USER_TEMPLATES:
cls.USER_TEMPLATES[name] = {}
with open(os.path.join(user_dir, filename), 'r') as f:
cls.USER_TEMPLATES[name][lang] = f.read()
@classmethod
def _parse_filename(cls, filename: str) -> tuple:
"""Extrait le nom du prompt et la langue à partir du nom de fichier."""
# generator_fr.txt → ('generator', 'Fr')
name = filename.rsplit('_', 1)[0]
lang = filename.rsplit('_', 1)[1].replace('.txt', '').capitalize()
return name, lang
@classmethod
def get_system_prompt(cls, name: str, language: str = 'Fr') -> str:
"""Obtient un prompt système par nom et langue."""
return cls.SYSTEM_PROMPTS.get(name, {}).get(language, '')
@classmethod
def render_user_prompt(cls, name: str, language: str = 'Fr', **kwargs) -> str:
"""Rend un template utilisateur avec des variables."""
template_str = cls.USER_TEMPLATES.get(name, {}).get(language, '')
return Template(template_str).safe_substitute(**kwargs)
Variables de Template
Les prompts utilisateur utilisent la syntaxe Template de Python pour la substitution de variables :
# resources/prompts/user/generation_template_fr.txt
Génère $count éléments de contenu sur le sujet suivant :
Catégorie : $category
Format : $item_type
Exigences :
$requirements
Contexte additionnel :
$context
Retourne un objet JSON avec un tableau "items" contenant les champs id, title, content et type.
Client AsyncAzureOpenAI
Pour les services à fort débit, utilisez le client asynchrone afin d'éviter de bloquer la boucle d'événements :
# services/ai_client.py
from openai import AsyncAzureOpenAI
import os
class AIClient:
_instance: AsyncAzureOpenAI = None
@classmethod
async def initialize(cls):
"""Initialise le client singleton."""
if cls._instance is None:
cls._instance = AsyncAzureOpenAI(
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION", "2024-02-01"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
)
@classmethod
def get_client(cls) -> AsyncAzureOpenAI:
"""Obtient l'instance singleton du client."""
if cls._instance is None:
raise RuntimeError("AIClient non initialisé. Appelez initialize() d'abord.")
return cls._instance
@classmethod
async def generate(cls, system_prompt: str, user_prompt: str,
temperature: float = 0.5) -> str:
"""Génère une complétion en mode JSON."""
client = cls.get_client()
response = await client.chat.completions.create(
model=os.getenv("AZURE_DEPLOYMENT_NAME"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=temperature,
max_tokens=4000,
response_format={"type": "json_object"}
)
return response.choices[0].message.content
Validation des Réponses
Les LLM sont probabilistes. Ils peuvent retourner du JSON malformé, des champs manquants ou des types incorrects. Une couche de validation avec auto-correction garantit la fiabilité :
FLUX DE VALIDATION
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ Réponse IA │────▶│ Valider │────▶│ Valide ? │────▶│ Retourner │
│ (JSON) │ │ Schéma │ │ Oui │ │ Résultat │
└────────────┘ └────────────┘ └─────┬──────┘ └────────────┘
│ Non
▼
┌────────────┐ ┌────────────┐
│ Auto- │────▶│ Réessayer │──┐
│ Correction │ │ (3x) │ │
└────────────┘ └────────────┘ │
▲ │
└────────────────────────────┘
Implémentation du Validateur
# services/validator.py
import json
import re
from typing import List, Dict, Any, Optional
class Validator:
REQUIRED_FIELDS = ['id', 'title', 'content', 'format']
VALID_FORMATS = ['summary', 'analysis', 'tutorial']
def validate(self, items: List[Dict]) -> bool:
"""Valide une liste d'éléments."""
if not isinstance(items, list) or len(items) == 0:
return False
for item in items:
if not self._validate_item(item):
return False
return True
def _validate_item(self, item: Dict) -> bool:
"""Valide un seul élément."""
# Vérifier la présence des champs requis
for field in self.REQUIRED_FIELDS:
if field not in item:
return False
# Valider l'enum de format
if item.get('format', '').lower() not in self.VALID_FORMATS:
return False
# Valider que les champs textuels ne sont pas vides
if not item.get('title') or not item.get('content'):
return False
return True
def auto_correct(self, raw_response: str) -> Optional[Dict]:
"""Tente de corriger les problèmes JSON courants."""
try:
# Essayer d'abord l'analyse directe
return json.loads(raw_response)
except json.JSONDecodeError:
pass
# Corriger les problèmes courants
corrected = raw_response
# Supprimer les blocs de code markdown
corrected = re.sub(r'```json?\n?', '', corrected)
corrected = re.sub(r'```', '', corrected)
# Corriger les virgules de fin
corrected = re.sub(r',\s*}', '}', corrected)
corrected = re.sub(r',\s*]', ']', corrected)
try:
return json.loads(corrected)
except json.JSONDecodeError:
return None
Traitement Parallèle avec asyncio
Générer de nombreuses sorties séquentiellement prend des minutes. En divisant le travail en lots et en les exécutant en parallèle, on réduit la latence de façon spectaculaire.
TRAITEMENT PAR LOTS EN PARALLÈLE
Séquentiel (120 secondes) Parallèle (15 secondes)
───────────────────────── ──────────────────────
[Lot 1] ──▶ [Lot 2] ──▶ [Lot 1] ──┐
[Lot 3] ──▶ [Lot 4] ──▶ [Lot 2] ──┤
[Lot 5] ──▶ [Lot 6] ──▶ [Lot 3] ──┤
[Lot 7] ──▶ [Lot 8] [Lot 4] ──┼──▶ Combiner
[Lot 5] ──┤
Total : 8 × 15s = 120s [Lot 6] ──┤
[Lot 7] ──┤
[Lot 8] ──┘
Total : max(15s) = 15s
Implémentation du Générateur par Lots
# services/generator.py
import asyncio
import json
from typing import List, Dict, Any
class BatchGenerator:
def __init__(self, language: str = 'Fr'):
self.language = language
self.validator = Validator()
async def generate_items(self, context: Dict[str, Any]) -> List[Dict]:
"""Génère des éléments via un traitement par lots en parallèle."""
# Définir les catégories pour chaque lot (garantit la diversité)
batch_configs = [
{'category': 'Technologie', 'count': 3},
{'category': 'Science', 'count': 3},
{'category': 'Business', 'count': 3},
{'category': 'Éducation', 'count': 3},
{'category': 'Santé', 'count': 2},
{'category': 'Environnement', 'count': 2},
]
# Créer les tâches en parallèle
tasks = [
self._generate_batch(config, context)
for config in batch_configs
]
# Exécuter tous les lots en parallèle
results = await asyncio.gather(*tasks, return_exceptions=True)
# Combiner les résultats en gérant les échecs
all_items = []
for i, result in enumerate(results):
if isinstance(result, Exception):
print(f"Lot {i+1} échoué : {result}")
continue
all_items.extend(result)
return all_items
async def _generate_batch(self, config: Dict, context: Dict,
retries: int = 3) -> List[Dict]:
"""Génère un seul lot avec logique de réessai."""
system_prompt = PromptEngine.get_system_prompt('generator', self.language)
user_prompt = PromptEngine.render_user_prompt(
'generation_template',
self.language,
count=config['count'],
category=config['category'],
item_type=context.get('format', 'summary'),
requirements=context.get('requirements', ''),
context=json.dumps(context.get('data', {}))
)
response = await AIClient.generate(system_prompt, user_prompt)
# Analyser et valider
parsed = self.validator.auto_correct(response)
if parsed is None:
if retries > 0:
return await self._generate_batch(config, context, retries - 1)
raise ValueError("Impossible d'analyser la réponse IA")
items = parsed.get('items', [])
if not self.validator.validate(items):
if retries > 0:
print(f"Validation échouée, nouvel essai... ({retries} restants)")
return await self._generate_batch(config, context, retries - 1)
raise ValueError("Sortie IA invalide")
return items
Réglage des Paramètres
Les différents cas d'usage nécessitent des paramètres de modèle différents :
| Paramètre | Tâches créatives | Tâches analytiques | Classification |
|---|---|---|---|
| Temperature | 0,7-1,0 | 0,3-0,5 | 0,0-0,2 |
| Top-p | 0,9-1,0 | 0,5-0,7 | 0,1-0,3 |
| Frequency Penalty | 0,3-0,5 | 0,0-0,2 | 0,0 |
| Presence Penalty | 0,5-0,8 | 0,0-0,3 | 0,0 |
- Temperature : Plus élevée = plus aléatoire/créatif, plus basse = plus focalisé/déterministe
- Top-p : Limite la sélection aux tokens de plus haute probabilité. Plus bas = plus prévisible
- Frequency Penalty : Réduit la répétition des tokens déjà utilisés
- Presence Penalty : Encourage le modèle à aborder de nouveaux sujets
Gestion des Erreurs
Les services IA en production doivent gérer différents modes d'échec :
async def safe_generate(self, prompt: str, max_retries: int = 3) -> Dict:
"""Génère avec une gestion complète des erreurs."""
for attempt in range(max_retries):
try:
response = await AIClient.generate(
self.system_prompt,
prompt,
temperature=0.5
)
parsed = json.loads(response)
if self.validator.validate(parsed):
return parsed
# Validation échouée, réessayer avec une température plus basse
print(f"Validation échouée, tentative {attempt + 1}")
except json.JSONDecodeError as e:
print(f"Erreur d'analyse JSON : {e}")
# Essayer l'auto-correction
corrected = self.validator.auto_correct(response)
if corrected and self.validator.validate(corrected):
return corrected
except Exception as e:
print(f"Erreur API : {e}")
if "rate_limit" in str(e).lower():
await asyncio.sleep(2 ** attempt) # Backoff exponentiel
continue
raise RuntimeError(f"Échec après {max_retries} tentatives")
Bonnes Pratiques
Ingénierie des Prompts
- Soyez précis : "Retourne exactement 5 éléments" plutôt que "Retourne quelques éléments"
- Montrez le format : Incluez un exemple JSON dans le prompt système
- Contraignez la sortie : Listez les valeurs valides pour les enums
- Séparez les responsabilités : Prompt système pour le comportement, prompt utilisateur pour les données
Performance
- Regroupez les appels parallèles : Utilisez asyncio.gather pour les requêtes indépendantes
- Pré-chargez les prompts : Chargez depuis les fichiers au démarrage, pas par requête
- Utilisez le bon modèle : GPT-4o-mini pour les tâches simples, GPT-4o pour le raisonnement complexe
- Streamez les réponses : Pour le chat orienté utilisateur, streamez les tokens au fur et à mesure
Fiabilité
- Validez toujours : Ne faites jamais confiance à la sortie d'un LLM sans validation
- Implémentez les réessais : 3 tentatives avec backoff exponentiel
- Auto-corrigez le JSON : Gérez les erreurs de formatage courantes
- Dégradation gracieuse : Retournez des résultats partiels si certains lots échouent
Conclusion
L'ingénierie IA en production englobe plusieurs disciplines. Les fondamentaux des LLM (tokens, température, fenêtres de contexte) informent la façon dont vous interagissez avec les modèles. Le fine-tuning avec PEFT/LoRA vous permet de spécialiser les modèles pour votre domaine sans coûts de calcul massifs. Le RAG permet des réponses ancrées dans la connaissance, avec citation des sources.
Côté implémentation, les patterns Django comme AppConfig garantissent une initialisation efficace. Le PromptEngine sépare le contenu du code, permettant aux non-développeurs de modifier les prompts. La validation avec auto-correction gère la nature probabiliste des LLM. Le pattern de traitement parallèle avec asyncio.gather réduit la latence de plusieurs minutes à quelques secondes. Combiné à la logique de réessai et à la gestion gracieuse des erreurs, cette architecture délivre des fonctionnalités IA fiables à l'échelle de la production.
Lecture Associée
Ces articles couvrent les microservices et l'infrastructure cloud sur lesquels cette couche IA s'appuie :
- Comment nous avons architecturé un SaaS en production : une plongée dans les microservices — l'architecture de services NestJS dans laquelle l'intégration GPT-4o-mini est embarquée, incluant le pattern gateway et la communication inter-services.
- Comment Nous Avons Déployé et Scalé sur Azure : Un Playbook de Production — l'environnement Azure Container Apps qui héberge le service IA, incluant la configuration de scaling nécessaire pour gérer les pics de charge d'inférence.
Consultez le projet Fygurs dans la section projets pour le contexte produit complet autour de la fonctionnalité de priorisation IA.
Écrit par

Tech Lead et Ingénieur Full Stack pilotant une équipe de 5 ingénieurs chez Fygurs (Paris, Remote) sur un SaaS cloud-native Azure. Diplômé de 1337 Coding School (42 Network / UM6P). Écrit sur l'architecture, l'infrastructure cloud et le leadership technique.