# ❓ FAQ d'Entretien Technique & Simulations Voici les questions critiques d'entretien sur les projets d'ingénierie d'IA et de backend, accompagnées des réponses professionnelles et de notes explicatives. --- ### Q1 : "Pourquoi choisir FastAPI pour vos API Backend plutôt que Django ou Express ?" > **Réponse attendue :** > "FastAPI est moderne et asynchrone par défaut (compatibilité ASGI). Ses performances sont comparables à celles de Node.js ou Go car il est bâti sur Starlette. > De plus, son intégration native avec Pydantic permet de valider automatiquement les types des requêtes en entrée et génère une documentation OpenAPI (Swagger) vivante sans effort additionnel. C'est la stack de choix pour les projets combinant l'asynchronisme et l'ingénierie d'IA." > > **💡 Note pédagogique pour débutant :** > * **ASGI (Asynchronous Server Gateway Interface) :** C'est le standard moderne pour les serveurs Python asynchrones. Contrairement à WSGI (l'ancien standard utilisé par Django ou Flask), ASGI permet de gérer des milliers de connexions en attente (comme des requêtes IA très longues) sans bloquer le reste des utilisateurs du serveur. > * **Starlette :** C'est le moteur réseau ultra-rapide sous le capot de FastAPI. > * **Pydantic :** Une bibliothèque Python de validation de données. Si vous déclarez qu'une route attend un entier, Pydantic vérifie automatiquement et rejette la requête si l'utilisateur envoie du texte. > * **OpenAPI / Swagger :** C'est le format standard de description des API. Swagger génère une interface web (généralement à l'adresse `/docs`) qui permet de tester les endpoints de votre API directement depuis votre navigateur. --- ### Q2 : "Dans votre projet Traducteur i18n, comment assurez-vous que le LLM ne modifie pas les clés JSON ou les variables de traduction comme `{username}` ?" > **Réponse attendue :** > "Nous utilisons une double approche. > D'abord, le **Prompt Engineering** : le *System Prompt* définit un rôle strict d'assistant de traduction et interdit explicitement de modifier les variables entourées d'accolades ou de modifier la syntaxe des clés JSON. > Ensuite, la **validation structurelle** : nous utilisons l'option **Structured Outputs** de l'API avec un schéma de sortie validé par Pydantic. Si le modèle venait à omettre une clé ou à altérer le format, l'API lèverait une exception immédiatement, nous permettant de faire un *fallback* (retenter l'appel) plutôt que d'écrire un fichier corrompu en production." > > **💡 Note pédagogique pour débutant :** > * **Prompt Engineering :** C'est l'art de rédiger des instructions claires pour guider le comportement du LLM. C'est l'équivalent de donner des consignes de travail extrêmement précises à un stagiaire. > * **Fallback (Plan de repli) :** C'est un plan de secours automatisé dans le code. Si une action échoue (ex: l'IA renvoie un résultat erroné ou l'API plante), le programme le détecte et bascule automatiquement sur une solution de secours (ex: ré-essayer l'appel, ou appeler un autre modèle plus robuste). --- ### Q3 : "Si vous devez traduire un fichier JSON géant de 10 000 clés, comment gérez-vous les limites de tokens et les Rate Limits de l'API ?" > **Réponse attendue :** > "Il est impensable d'envoyer 10 000 clés en une seule fois car cela dépasserait la fenêtre de contexte ou le quota de tokens de sortie. > La solution est d'implémenter un algorithme d'**aplatissement (flattening)** pour transformer le JSON imbriqué en dictionnaire plat. Ensuite, nous découpons ce dictionnaire en **lots (batches)** d'environ 50 à 100 clés. > Nous envoyons chaque lot de manière asynchrone en surveillant les en-têtes HTTP de l'API (`x-ratelimit-remaining`). Si nous approchons de la limite, nous implémentons un délai d'attente exponentiel (*exponential backoff*) avec du bruit (*jitter*) pour lisser la charge." > > **💡 Note pédagogique pour débutant :** > * **Flattening (Aplatissement) :** Consiste à convertir une structure imbriquée complexe `{ "auth": { "login": "Connexion" } }` en une liste simple avec des chemins en clés `"auth.login": "Connexion"`. C'est plus facile à manipuler pour les algorithmes et plus lisible pour l'IA. > * **Exponential Backoff (Retrait exponentiel) :** En cas d'erreur de surcharge du serveur, on n'essaye pas de le rappeler en boucle (ce qui aggraverait la panne). On attend 1 seconde, puis si ça échoue encore, on attend 2 secondes, puis 4 secondes, puis 8 secondes... > * **Jitter (Bruit / Aléa de latence) :** Ajout d'une fraction aléatoire de temps au délai d'attente (ex: attendre 2,3 secondes au lieu de 2 secondes pile) pour éviter que toutes les requêtes en échec ne relancent leur appel exactement au même instant, ce qui saturerait à nouveau le serveur. --- ### Q4 : "Quelle est la différence entre le mode JSON d'OpenAI et les Structured Outputs ?" > **Réponse attendue :** > "Le **mode JSON** garantit uniquement que la sortie de l'IA sera syntaxiquement valide en tant que JSON. Cependant, l'IA reste libre de structurer ce JSON comme elle le souhaite (elle peut omettre des champs ou renommer des clés). > Les **Structured Outputs** vont plus loin : ils forcent l'IA à respecter exactement le schéma JSON ou la classe Pydantic fournie en entrée. C'est une garantie absolue au niveau du moteur d'inférence du LLM, indispensable en production pour s'assurer que notre backend puisse parser le résultat sans erreur." > > **💡 Note pédagogique pour débutant :** > * **JSON Mode vs Structured Outputs :** Pensez au **mode JSON** comme à un formulaire libre où l'utilisateur a le droit d'écrire ce qu'il veut, tant qu'il utilise le formatage JSON. Les **Structured Outputs** sont comme un formulaire administratif avec des cookies ou des cases très strictes : l'IA est techniquement incapable d'écrire en dehors des champs définis dans le schéma. --- ### Q5 : "Qu'est-ce qu'un cache sémantique et dans quel cas l'utiliseriez-vous ?" > **Réponse attendue :** > "Un cache sémantique permet de stocker les réponses aux questions déjà posées à un LLM non pas sur une égalité stricte de chaîne de caractères, mais sur la **similarité sémantique** de la question. > On calcule l'embedding de la question de l'utilisateur et on recherche dans une base de données vectorielle (comme Redis ou Qdrant) s'il existe une question similaire avec un score supérieur à 95% (similarité cosinus). Si c'est le cas, on retourne directement la réponse en cache. Cela permet de diviser la latence par 100 (moins de 10ms au lieu de 2 secondes) et de réduire à zéro le coût de tokens pour les questions répétitives." > > **💡 Note pédagogique pour débutant :** > * **Embedding (Vecteur sémantique) :** C'est la conversion d'un texte en coordonnées mathématiques (une liste de nombres). Les phrases "J'ai faim" et "Je mangerais bien quelque chose" n'ont aucun mot en commun, mais l'IA leur donnera des coordonnées géométriques extrêmement proches dans son espace multidimensionnel car elles partagent le même sens. > * **Similarité cosinus :** C'est le calcul trigonométrique qui permet de mesurer l'angle entre deux vecteurs d'embeddings pour savoir s'ils pointent dans la même direction (c'est-à-dire s'ils ont un sens très proche). --- ### Q6 : "Le LLM peut parfois altérer subtilement la syntaxe des variables d'interpolation en ajoutant des espaces, par exemple en traduisant `{username}` par `{ username }`. Comment gérez-vous cela ?" > **Réponse attendue :** > "C'est un problème classique d'intégration de LLM. Pour y remédier, nous implémentons un pipeline de **post-traitement (post-processing)**. > Une fois le JSON de traduction reçu du LLM, nous passons une expression régulière (Regex) en Python sur toutes les valeurs textuelles. Cette Regex identifie les accolades et supprime automatiquement les espaces superflus (ex: remplacer `{ (.*?) }` par `{\1}`). Cela garantit que les variables restent parfaitement compatibles avec le moteur de rendu i18n du frontend (comme react-i18next)." > > **💡 Note pédagogique pour débutant :** > * **Regex (Expression Régulière) :** Un outil puissant de recherche et de remplacement de texte basé sur des motifs complexes (ex: trouver tous les mots se trouvant entre accolades `{}`). > * **Post-processing (Post-traitement) :** Étape de nettoyage ou de reformatage des données effectuée automatiquement par notre code Python *après* l'obtention du résultat brut du LLM et *avant* sa sauvegarde ou son utilisation par d'autres systèmes. --- ### Q7 : "Si la traduction d'un gros fichier de 5 000 clés échoue à mi-chemin (ex: coupure réseau au 4ème lot sur 10), doit-on relancer tout le processus ?" > **Réponse attendue :** > "Absolument pas, cela serait inefficace et coûteux. Pour concevoir un système résilient, nous implémentons un mécanisme de **sauvegarde d'état (Checkpointing)**. > Au lieu de tout garder en mémoire vive, chaque lot (batch) traduit avec succès est immédiatement persisté dans un fichier JSON temporaire de checkpoint (ou dans une base SQLite légère locale). Si le script plante et est relancé, il vérifie l'existence de ce fichier de checkpoint, reprend la liste des clés déjà traduites pour les ignorer, et continue la traduction à partir du dernier lot validé." > > **💡 Note pédagogique pour débutant :** > * **Checkpointing (Point de sauvegarde) :** C'est exactement comme la sauvegarde automatique dans un jeu vidéo. Si vous perdez ou si le jeu plante, vous reprenez au dernier checkpoint plutôt que de devoir recommencer le jeu depuis le premier niveau. --- ### Q8 : "Quelle température configurez-vous pour l'appel au LLM dans ce projet et pourquoi ?" > **Réponse attendue :** > "Nous configurons une **température de 0 (ou proche de 0.1)**. > Dans une tâche de traduction et de conservation de structure, nous recherchons le maximum de déterminisme, de fidélité et de rigueur syntaxique. Une température élevée augmenterait la 'créativité' de l'IA, ce qui risquerait de paraphraser inutilement les textes techniques, d'inventer des formulations fantaisistes ou de corrompre le formatage des balises." > > **💡 Note pédagogique pour débutant :** > * **Déterminisme :** Un programme est déterministe s'il produit toujours exactement le même résultat pour une entrée donnée. Dans notre cas, nous voulons que le LLM traduise toujours fidèlement, sans inventer d'alternatives créatives qui changeraient à chaque exécution du script. --- ### Q9 : "Si un client refuse que ses données textuelles soient envoyées sur le Cloud (ex: OpenAI) pour des raisons de confidentialité, comment adaptez-vous votre outil ?" > **Réponse attendue :** > "Nous concevons notre code selon le principe du couplage faible en utilisant le pattern **Provider/Adapter**. > Nous créons une classe abstraite ou une interface commune pour notre client LLM. Nous pouvons ainsi implémenter un adaptateur cloud (ex: `OpenAIProvider`) et un adaptateur local (ex: `OllamaProvider`). En local, l'outil interroge un modèle souverain (comme Llama3 ou Mistral) hébergé directement sur le serveur ou la machine du client via **Ollama**. Cela garantit 100% de confidentialité des données et un coût d'API nul." > > **💡 Note pédagogique pour débutant :** > * **Pattern Provider/Adapter (Fournisseur/Adaptateur) :** C'est un principe de programmation orientée objet. Vous créez un "moule" générique (ex: `traduire_texte()`), et vous écrivez ensuite des connecteurs spécifiques pour chaque service (un pour OpenAI, un pour Mistral, un pour Ollama). Le reste de votre application n'a pas besoin de savoir quel service est utilisé sous le capot, elle appelle juste la méthode générique. > * **Ollama :** Un outil open source génial qui permet de faire tourner des modèles d'IA (comme Llama ou Mistral) directement sur votre propre ordinateur ou serveur local, de manière autonome et sans connexion Internet. --- ### Q10 : "Pourquoi avoir choisi l'architecture Typer + Aplatissement + Batching + Structured Outputs pour votre traducteur i18n, plutôt qu'une récursion directe ?" > **Réponse attendue :** > "Nous avons retenu cette architecture pour des raisons de performance, de coût et de robustesse. > 1. **Typer** élimine le code verbeux ('boilerplate') de gestion des entrées en ligne de commande en exploitant le typage statique de Python, tout en fournissant une auto-documentation immédiate. > 2. **L'aplatissement (Flattening) et le traitement par lots (Batching)** résolvent le goulet d'étranglement des appels réseau et des limites de taux (*Rate Limits*). Au lieu d'effectuer un appel API HTTP pour chaque clé (récursion directe), nous regroupons les couples clé-valeur plats par paquets de 50. Cela divise par 50 la latence et la consommation de jetons de prompt système, tout en fournissant un contexte sémantique indispensable au LLM. > 3. **Les Structured Outputs** garantissent par contrat au niveau de l'API LLM que le JSON retourné par l'IA aura exactement la même structure de clés plates qu'au départ, éliminant tout risque d'erreur de parsing ou de clés manquantes." > > **💡 Note pédagogique pour débutant :** > * **Boilerplate (Code verbeux) :** Code répétitif et technique qui est obligatoire pour faire tourner un programme (comme valider qu'un fichier existe ou parser des arguments console bruts), mais qui n'apporte pas de valeur métier directe. > * **Sémantique contextuelle (Contextual translation) :** Traduire des mots isolés (ex: 'Cancel') peut être ambigu pour une IA (est-ce un verbe ou un nom ?). Envoyer les clés en groupe (ex: 'Submit', 'Cancel', 'Save') donne de l'information contextuelle à l'IA sur l'utilisation du mot dans une interface utilisateur, ce qui donne une traduction beaucoup plus précise. --- ### Q11 : "Comment garantissez-vous qu'un crash de la machine pendant l'écriture du fichier de checkpoint ne corrompe pas le JSON sur disque ?" > **Réponse attendue :** > "Nous appliquons le pattern d'**écriture atomique sur disque**. Au lieu d'ouvrir directement le fichier `.json` cible en écriture (ce qui laisserait un fichier incomplet ou illisible si le processus est interrompu à mi-chemin), nous écrivons l'état dans un fichier temporaire `.tmp`. > Nous appelons ensuite `f.flush()` puis `os.fsync(f.fileno())` pour forcer le contrôleur matériel à valider l'écriture physique des blocs sur disque, puis nous effectuons un remplacement atomique via `Path.replace()`. Sur les systèmes de fichiers POSIX et Windows NT, `rename` est une opération atomique garantie par le noyau du système d'exploitation : soit l'ancien fichier est conservé intact, soit le nouveau le remplace instantanément, éliminant tout risque de fichier JSON corrompu." > > **💡 Note pédagogique pour débutant :** > * **Opération Atomique :** Une opération est dite atomique si elle s'exécute en un bloc indivisible : elle réussit à 100% ou elle échoue sans laisser le moindre état intermédiaire incomplet. > * **`os.fsync()` :** Les systèmes d'exploitation modernes gardent les fichiers en mémoire cache RAM avant de les écrire physiquement sur le disque. `fsync()` force l'écriture immédiate sur le disque dur/SSD pour éviter la perte de données si l'ordinateur s'éteint. --- ### Q12 : "Qu'est-ce que le problème du 'Thundering Herd' (Effet de meute) et comment le Jitter le résout-il dans votre algorithme de ré-essai ?" > **Réponse attendue :** > "Lorsque 1 000 requêtes en parallèle rencontrent une erreur de Rate Limit (HTTP 429), un algorithme de ré-essai sans Jitter ferait ré-essayer tous les clients exactement au même instant (ex: 2,000 secondes plus tard). Ces 1 000 requêtes frapperaient à nouveau le serveur simultanément, provoquant un 'effet de meute' (*Thundering Herd*) qui ferait à nouveau tomber le serveur en cours de rétablissement. > Le **Jitter** ajoute un délai aléatoire court (ex: entre 0.0s et 0.5s) à la formule d'attente exponentielle. Chaque client ré-essaie à un instant légèrement différent ($2.12s, 2.41s, 2.05s$), ce qui étale la charge réseau de manière fluide et permet à l'API de récupérer normalement." > > **💡 Note pédagogique pour débutant :** > * **Thundering Herd (Effet de meute) :** Pensez à un magasin qui ferme 5 minutes pour réorganiser ses rayons et où 500 personnes attendent devant la porte. Si toutes se précipitent exactement à la même seconde à l'ouverture, la porte bloque. Si le magasin distribue des tickets d'entrée espacés de quelques secondes, le flux reste fluide. --- ### Q13 : "Comment avez-vous structuré le code du projet pour éviter d'avoir un fichier 'Fourre-tout' (God Object) et maintenir une architecture propre, modulaire et testable ?" > **Réponse attendue :** > "Nous avons appliqué le principe de responsabilité unique (SRP) et les design patterns d'architecture backend. > Côté cœur métier (`src/`), nous avons découpé le code en sous-modules spécialisés (`json_utils.py`, `regex_utils.py`, `checkpoint.py`, `retry.py`), et nous les avons coiffés d'un **Pattern Façade** (`src/core.py`). La façade unifie les imports et garantit 100% de rétrocompatibilité sans aucun breaking change. > Côté serveur web Flask (`dashboard/`), nous avons remplacé le fichier monolithique par des **Blueprints Flask modulaires** (`dashboard/routes/`). Chaque module fait moins de 100 lignes, est facile à lire, tester et faire évoluer indépendamment." > > **💡 Note pédagogique pour débutant :** > * **God Object / Monolithe :** Anti-pattern de conception où une seule classe ou un seul fichier contient toute la logique du projet. C'est difficile à lire, propice aux bugs et impossible à maintenir en équipe. > * **Blueprints Flask :** Façon officielle de découper un serveur Flask en petits sous-modules indépendants (ex: un fichier pour les routes du journal, un fichier pour les tests, un fichier pour les bacs à sable). --- ### Q14 : "Pourquoi et comment avoir séparé l'API Flask en un Microservice REST FastAPI autonome et un Dashboard avec Proxy Inverse ?" > **Réponse attendue :** > "Nous avons découpé l'architecture selon le principe de **séparation des responsabilités microservices**. > Le moteur de traduction automatique (`api/main.py`) est désormais un **Microservice REST FastAPI** autonome, à très haute performance, stateless, doté d'une documentation interactive Swagger OpenAPI (`/docs`), et prêt à être conteneurisé dans un container Docker ou déployé sur un cluster Cloud (AWS/GCP/Kubernetes). > > Le serveur Flask (`dashboard/app.py`) conserve la responsabilité du **Dashboard UI**, du Journal d'apprentissage, du Glossaire, de la FAQ et du lanceur de tests QA. Les requêtes de traduction faites depuis l'IHM sont automatiquement relayées vers FastAPI via un **Proxy Inverse HTTP** (`httpx`). Cela permet d'exécuter un moteur de traduction léger en production tout en gardant une interface riche de suivi." > > **💡 Note pédagogique pour débutant :** > * **Microservice vs Dashboard UI :** Un microservice de production doit être léger et ne contenir que la logique métier essentielle. L'interface graphique, la formation et le suivi n'ont pas à être embarqués dans le conteneur cloud de production. > * **Proxy Inverse :** Mécanisme par lequel un serveur web (Flask sur le port 5000) transmet de manière transparente certaines requêtes au serveur microservice (FastAPI sur le port 8000) sans que l'utilisateur n'ait à s'en soucier. --- ### Q15 : "Pourquoi utiliser un build Docker Multi-Stage et exécuter le conteneur en tant qu'utilisateur non-root (`appuser`) ?" > **Réponse attendue :** > "Nous utilisons le pattern **Multi-Stage Build** dans notre `Dockerfile` pour isoler la phase de compilation des paquets Python (stage `builder`) du runtime final (stage `runner`). Cela réduit drastiquement la taille de l'image minimale de production et élimine les outils de build superflus qui représenteraient une surface d'attaque en production. > > De plus, nous créons un utilisateur dédié `appuser` (UID 1000) et forçons son utilisation via la directive `USER appuser`. Exécuter un conteneur en tant que `root` est une faille de sécurité majeure : si un attaquant parvient à s'échapper du conteneur (Container Escape), il obtiendrait les privilèges `root` sur la machine hôte ou le nœud Google Cloud Run." > > **💡 Note pédagogique pour débutant :** > * **Multi-Stage Build :** C'est comme préparer une recette dans une cuisine de préparation avec tous les ustensiles lourds, puis n'apporter que le plat final sur la table. L'image finale ne contient que le strict nécessaire. > * **Principe de Moindre Privilège (Non-Root User) :** Ne jamais donner les clés de la maison (droits root) à une application web. Si l'application subit une faille, l'attaquant est confiné dans un compte restreint sans aucun pouvoir d'altération système. --- ### Q16 : "Comment le Microservice FastAPI est-il déployé sur Google Cloud Run et comment la clé d'API Gemini est-elle sécurisée ?" > **Réponse attendue :** > "Nous utilisons le script automatisé `deploy_gcp_cloud_run.sh` et le pipeline CI/CD `cloudbuild.yaml`. > L'image Docker multi-stage est d'abord poussée sur **Google Artifact Registry**. Elle est ensuite déployée sur **Google Cloud Run** en mode Serverless (scaling automatique de 0 à N instances, allocation de 512Mo RAM et 1 CPU). > > Pour la gestion des secrets, nous n'inscrivons jamais la clé d'API en dur dans le code ou le `Dockerfile`. Nous utilisons **GCP Secret Manager** : la clé `GEMINI_API_KEY` est enregistrée de manière chiffrée dans le coffre-fort GCP et injectée dynamiquement sous forme de variable d'environnement dans le conteneur Cloud Run au moment de son exécution." > > **💡 Note pédagogique pour débutant :** > * **Google Cloud Run :** Un service cloud où l'on dépose simplement une image Docker. Google s'occupe de démarrer les serveurs, de gérer la sécurité HTTPS et de couper les serveurs inutilisés pour ne payer que les requêtes réellement consommées. > * **GCP Secret Manager :** Un coffre-fort numérique dans le cloud. Au lieu d'écrire un mot de passe ou une clé d'API dans un fichier, le serveur Cloud demande au coffre-fort la valeur exacte au moment du démarrage.