🎯 Le Contexte & L'Objectif
Sparrow est né dans le cadre de l'UE Projet IA du semestre 8 à l'ESIGELEC. Le sujet, commun à toute la classe, imposait de concevoir un système multi-agents capable de planifier des itinéraires de voyage complets — vols, hôtels et activités — à partir de requêtes utilisateur complexes en langage naturel. Chaque groupe de trois devait proposer sa propre implémentation de bout en bout.
L'idée de Sparrow est simple en apparence : l'utilisateur décrit son voyage en langage naturel — « Je veux partir à Tokyo pendant cinq jours avec un budget de 2 000 € » — et l'application lui propose un itinéraire complet incluant vols, hébergements et activités, le tout dans une interface de chat fluide avec des réponses en streaming. Derrière cette simplicité se cache une architecture multi-agents où chaque domaine (vols, hôtels, guide local) est délégué à un agent IA spécialisé, orchestré par un agent central qui coordonne l'ensemble et synthétise les résultats.
Le projet devait démontrer une maîtrise de bout en bout : modélisation d'une base de données, développement d'un backend capable de piloter plusieurs agents IA simultanément, et construction d'une interface utilisateur soignée capable de consommer des flux temps réel.
🛠️ Ma Contribution & Mon Rôle
Le projet a été réparti en trois responsabilités distinctes. Mes coéquipiers ont pris en charge l'orchestrateur central, les agents spécialisés (vols et hôtels), ainsi que les routes API du backend. De mon côté, j'ai assuré trois piliers du projet :
Frontend complet (React + TypeScript)
J'ai conçu et développé l'intégralité de l'interface utilisateur, depuis la page d'accueil jusqu'à l'interface de chat :
- Page d'accueil : hero section animée avec Framer Motion, section « Comment ça marche » en timeline, cartes de présentation des fonctionnalités et footer.
- Interface de chat : sidebar de conversations, fil de messages avec distinction user/IA, affichage structuré des résultats (tableaux de vols, fiches hôtels, cartes d'activités), indicateur de réflexion de l'agent et input de saisie.
- Authentification : pages de connexion et d'inscription avec validation, gestion du token JWT via un
AuthContextReact et protection des routes. - Profil utilisateur : édition de la biographie, changement de mot de passe et gestion des préférences de voyage.
Agent Local Guide
J'ai développé l'agent IA spécialisé dans la découverte d'activités et de lieux d'intérêt. Cet agent reçoit une requête en langage naturel de l'orchestrateur — par exemple « Quels restaurants japonais traditionnels recommandes-tu à Tokyo ? » — et interroge la base de données pour renvoyer des résultats filtrés par ville, catégorie, budget, note et quartier.
Base de données (Supabase / PostgreSQL)
J'ai modélisé et peuplé l'ensemble de la base de données hébergée sur Supabase :
- Schéma relationnel : six tables couvrant les données métier (vols, hôtels, activités) et les données utilisateur (comptes, conversations, messages).
- Données de test : plus de 50 jours de vols sur quatre axes majeurs (Paris–Tokyo, Berlin–New York, etc.), une dizaine d'hôtels répartis sur trois villes avec des profils variés (budget, standing, équipements), et un catalogue d'activités couvrant restaurants, musées, parcs et attractions.
- Indexation : index optimisés pour les recherches par route de vol, par ville d'hôtel, par ville d'activité et par identifiant utilisateur, garantissant des temps de réponse rapides même sur des requêtes combinées.
💻 Stack Technique
- Frontend : React 19 + TypeScript 5.9 avec Vite 7 — un socle moderne offrant un typage strict, un rechargement instantané en développement et un build optimisé en production.
- UI : Mantine 8 pour les composants (formulaires, modales, navigation), Tailwind CSS 4 pour le styling utilitaire, et Framer Motion pour les animations de page et de messages.
- Backend : FastAPI (Python) — choisi pour son support natif de l'asynchrone, essentiel pour gérer les appels concurrents aux agents IA et le streaming SSE.
- IA / Agents : Google ADK (Agent Development Kit) avec les modèles Gemini —
gemini-3-flash-previewpour l'orchestrateur,gemini-2.5-flashpour l'agent vols,gemini-2.5-flash-litepour les agents hôtels et guide local. - Base de données : Supabase (PostgreSQL managé) — authentification intégrée, client Python natif et interface d'administration pour le suivi des données en développement.
- Temps réel : Server-Sent Events (SSE) pour le streaming des réponses de l'orchestrateur vers le frontend, permettant un affichage progressif de la réponse.
- Sécurité : JWT (expiration 72h) avec bcrypt pour le hachage des mots de passe, headers d'autorisation sur toutes les routes protégées.
⚙️ Architecture & Défis Techniques
Orchestration multi-agents avec Google ADK
Le cœur de Sparrow repose sur une architecture de délégation : un orchestrateur central analyse la requête de l'utilisateur, identifie les sous-tâches (chercher un vol, trouver un hôtel, suggérer des activités) et les distribue aux agents spécialisés via des appels de fonctions simultanés. Chaque agent dispose de son propre modèle Gemini et de ses propres outils (fonctions Python qui interrogent la base de données). L'orchestrateur récupère les résultats, vérifie la cohérence budgétaire via un outil get_total_price, et synthétise le tout dans une réponse structurée en Markdown avec des tableaux comparatifs.
Ce design présente un avantage clé : chaque agent est isolé et testable indépendamment. L'agent vols peut être interrogé seul via un endpoint dédié, sans passer par l'orchestrateur. Cela a facilité le développement en parallèle au sein de l'équipe.
Streaming SSE pour une expérience conversationnelle
L'un des défis techniques du projet a été la gestion du flux SSE entre le backend et le frontend. Contrairement à une requête classique qui renvoie une réponse complète, le backend émet des événements au fil de la réflexion de l'agent : un événement tool_call quand un sous-agent est invoqué, des événements text progressifs pendant la génération de la réponse, un événement data contenant les résultats structurés (vols, hôtels), et un événement done pour clôturer le flux.
Côté frontend, le hook useChat parse ce flux en temps réel et met à jour l'état React de manière incrémentale. L'utilisateur voit l'agent « réfléchir », puis les résultats apparaître progressivement — une expérience bien plus engageante qu'un spinner suivi d'un bloc de texte.
// useChat.ts — Lecture du flux SSE et mise à jour incrémentale de l'UI
const response = await fetch("/stream-orchestrator", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ prompt, session_id, user_id }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
for (const line of chunk.split("\n")) {
if (!line.startsWith("data: ")) continue;
const event = JSON.parse(line.slice(6));
if (event.status === "text") appendToMessage(event.message);
if (event.status === "data") setStructuredData(event.data);
if (event.status === "done") finalizeMessage();
}
}Conception de l'agent Local Guide
L'agent Local Guide interroge la table mock_local_guides via une fonction get_activities() qui construit dynamiquement sa requête Supabase en fonction des filtres reçus : ville (obligatoire), catégorie, note minimale, niveau de coût, durée suggérée et quartier. Le modèle Gemini interprète la requête utilisateur en langage naturel et appelle la fonction avec les bons paramètres — par exemple, « restaurants pas chers à Shibuya » se traduit en city="Tokyo", category="Restaurant", cost_level=1, neighborhood="Shibuya".
L'enjeu principal était de définir un mapping clair entre le langage naturel et les filtres de la base. Le niveau de coût, par exemple, est encodé sur une échelle de 1 à 4 (budget à luxe), mais l'utilisateur peut dire « pas cher », « milieu de gamme » ou « haut de gamme ». Ce mapping est intégré directement dans le prompt système de l'agent pour que Gemini fasse la traduction de manière fiable.
Persistance des préférences utilisateur
Un quatrième agent — le User Preference Agent — tourne silencieusement en parallèle des conversations. Quand l'utilisateur mentionne « je suis végétarien » ou « je voyage avec deux enfants », l'orchestrateur invoque cet agent qui extrait les préférences et les stocke en JSONB dans la table users. Lors des requêtes suivantes, ces préférences sont injectées dans le contexte de l'orchestrateur, permettant des recommandations personnalisées sans que l'utilisateur ait besoin de se répéter.
🚀 Résultats & Impact
- Interface complète : cinq pages (accueil, chat, connexion, inscription, profil), un système d'authentification fonctionnel et une expérience de chat avec streaming en temps réel.
- Architecture multi-agents opérationnelle : quatre agents spécialisés coordonnés par un orchestrateur, capables de planifier un voyage complet en une seule requête.
- Base de données peuplée : six tables, plus de 50 entrées de vols, 13 hôtels et un catalogue d'activités couvrant trois villes — suffisant pour démontrer la pertinence de l'approche sur des scénarios réalistes.
- Réponses personnalisées : les préférences utilisateur (régime alimentaire, allergies, style de voyage) sont mémorisées et influencent les recommandations futures.
- Respect du budget : l'orchestrateur vérifie systématiquement le total (vols + hôtels + activités) via un outil dédié avant de proposer un itinéraire, garantissant que les suggestions restent dans l'enveloppe budgétaire.
💡 Ce que j'ai appris
Ce projet m'a confronté pour la première fois à l'orchestration multi-agents — un paradigme très différent du simple appel à une API de LLM. Concevoir un système où plusieurs modèles collaborent, chacun avec ses propres outils et son propre périmètre, impose une réflexion sur les responsabilités, les formats d'échange et la gestion des erreurs entre agents. C'est une compétence que je n'avais pas avant ce projet.
Côté frontend, construire une interface de chat capable de consommer et d'afficher les réponses d'agents IA m'a poussé à soigner la structuration des composants React : distinguer visuellement les messages utilisateur et IA, formater des tableaux de résultats (vols, hôtels, activités) à la volée, et gérer les différents états d'une conversation (chargement, réponse en cours, erreur). C'est un travail d'intégration plus fin qu'un formulaire classique, où chaque type de donnée demande son propre rendu.
Ce projet a aussi été l'occasion de découvrir Supabase — un outil que je n'avais jamais utilisé auparavant. J'aime intégrer une technologie nouvelle à chaque projet : c'est un moteur d'apprentissage constant et ça me permet d'élargir ma boîte à outils à chaque itération. Supabase s'est révélé particulièrement agréable à prendre en main, avec un client Python simple, une interface d'administration claire et un PostgreSQL managé sans configuration.
Si c'était à refaire, plusieurs axes d'amélioration se dessinent :
- Données réelles : la branche principale fonctionne sur des données mock, mais l'intégration avec des APIs réelles (notamment Amadeus pour les vols) existe sur une branche dédiée (
origin/maelle). Le manque de temps en fin de projet n'a pas permis d'assembler proprement le tout et de tester l'ensemble de manière fiable — c'est le principal axe de progression si le projet devait être repris. - Tests : la couverture de tests est quasi inexistante, autant côté frontend que backend. Des tests unitaires sur les agents et des tests d'intégration sur le flux SSE seraient indispensables pour un passage en production.
- Gestion d'erreurs : le comportement en cas de timeout d'un agent ou de réponse incomplète mériterait d'être formalisé, avec des fallbacks explicites côté frontend.
- Déploiement : le projet tourne uniquement en local. Un déploiement sur Vercel (frontend) et un service cloud (backend) permettrait une démonstration live, mais impose de gérer les coûts des appels API Gemini.
Sparrow restera pour moi un projet marquant : c'est la première fois que je construis une application où l'intelligence artificielle n'est pas un simple appel d'API, mais une architecture à part entière avec ses propres couches de logique, de délégation et de coordination.