Cours Python · Module 12: Projet final
Planifier un projet d'IA
Dans cette leçon3
Bienvenue dans la grande finale de Python Safari ! Imagine une expédition qui part dans la savane sans carte, sans liste de provisions et sans objectif. Au bout de trois jours, quelqu'un demande « au fait, pourquoi on est là ? » et personne ne sait répondre. Les projets de programmation fonctionnent exactement pareil : ils échouent rarement à cause d'un mauvais code, mais parce que personne n'a écrit quel problème ils résolvent ni quand on peut les considérer comme terminés. Dans ce module, tu vas combiner toutes les compétences acquises dans un seul projet complet, et on commence par la carte de l'expédition.
Ce que tu vas apprendre
- noter le problème et les indicateurs de réussite d'un projet dans du code Python, au lieu de les garder en tête
- rédiger des user stories avec des critères d'acceptation
- estimer des tâches en story points et les répartir en sprints
- justifier ton choix de stack technologique
Méthodologie de planification de projet
1. Définir le problème
Avant d'écrire la première ligne de l'application, donnons un nom au type de projet. Pour une liste fermée de variantes, Python propose le type Enum du module enum : chaque membre de l'énumération a un nom et une valeur, et une faute de frappe dans un nom provoque tout de suite une erreur au lieu de passer inaperçue.
1from dataclasses import dataclass
2from enum import Enum
3
4class ProjectType(Enum):
5 WEB_APP = "web_application"
6 API = "api_service"
7 ML_PIPELINE = "ml_pipeline"
8 RAG_SYSTEM = "rag_system"
9 AUTOMATION = "automation"Pour l'instant, ce n'est qu'un catalogue des types d'expédition, rien ne se passe encore. Décrivons maintenant l'expédition elle-même. Le décorateur @dataclass du module dataclasses génère __init__, __repr__ et les comparaisons à partir des champs annotés, donc une classe de données tient en quelques lignes.
1@dataclass
2class ProjectDefinition:
3 """Définition du projet."""
4 name: str
5 problem_statement: str
6 target_users: list[str]
7 success_metrics: list[str]
8 project_type: ProjectType
9 tech_stack: list[str]Les annotations comme list[str] servent de documentation et d'indice pour ton éditeur et pour des outils comme mypy. Python ne les vérifie pas à l'exécution, donc la dataclass ne rejettera pas un mauvais type. Regardons une définition de projet remplie :
1# Exemple
2capstone_project = ProjectDefinition(
3 name="AI Document Assistant",
4 problem_statement="Les utilisateurs perdent du temps à chercher des informations dans les documents",
5 target_users=["analystes", "juristes", "chercheurs"],
6 success_metrics=[
7 "Réduction de 70% du temps de recherche",
8 "Précision des réponses > 90%",
9 "Temps de réponse < 2s"
10 ],
11 project_type=ProjectType.RAG_SYSTEM,
12 tech_stack=["Python", "FastAPI", "LlamaIndex", "Qdrant", "React"]
13)Le plus important ici, ce sont les indicateurs de réussite. « Temps de réponse < 2s » se mesure, « une appli rapide » non. Ce sont des objectifs qu'on se fixe, pas des données toutes faites : à côté de chaque indicateur, note tout de suite comment tu vas le mesurer.
2. User Stories
Une user story décrit une fonctionnalité du point de vue de l'utilisateur, dans un format fixe : « en tant que [qui], je veux [quoi], afin de [pourquoi] ». Les critères d'acceptation disent quand la story est terminée. Dans le code, on l'écrit avec une autre dataclass :
1@dataclass
2class UserStory:
3 """User story au format Agile."""
4 as_a: str
5 i_want: str
6 so_that: str
7 acceptance_criteria: list[str]
8 priority: int # 1-5, 1 = la plus élevéeLe champ priority est un simple nombre, et le commentaire fixe la convention : 1 signifie la priorité la plus élevée. Voici maintenant deux stories concrètes, pour un analyste et pour un administrateur :
1stories = [
2 UserStory(
3 as_a="analyste",
4 i_want="poser des questions sur les documents en langage naturel",
5 so_that="je peux trouver rapidement l'information dont j'ai besoin",
6 acceptance_criteria=[
7 "Le système comprend les questions en français",
8 "La réponse inclut les sources",
9 "Temps de réponse < 3s"
10 ],
11 priority=1
12 ),
13 UserStory(
14 as_a="administrateur",
15 i_want="ajouter facilement de nouveaux documents",
16 so_that="la base de connaissances reste à jour",
17 acceptance_criteria=[
18 "Téléversement par glisser-déposer",
19 "Prise en charge de PDF, DOCX, TXT",
20 "Indexation automatique"
21 ],
22 priority=2
23 )
24]Remarque que les stories ne disent rien de la technologie. L'analyste ne sait pas ce qu'est une base de données vectorielle, et il n'a pas besoin de le savoir. Des critères comme « La réponse contient les sources » deviendront plus tard des tests, alors écris-les de façon vérifiable.
3. Estimation et planification des sprints
En Scrum, le travail est découpé en sprints, des périodes de durée fixe d'un mois au maximum. En pratique, les équipes choisissent le plus souvent deux semaines. On estime les tâches en story points, une mesure de complexité relative et non en heures. Une échelle populaire est la suite de Fibonacci (1, 2, 3, 5, 8, 13), car les écarts croissants entre les nombres rappellent que les grosses tâches sont de moins en moins prévisibles.
1from datetime import datetime, timedelta
2
3@dataclass
4class Task:
5 name: str
6 story_points: int # Fibonacci : 1, 2, 3, 5, 8, 13
7 dependencies: list[str]
8 assigned_to: str = ""Task retient aussi ses dépendances, c'est-à-dire les noms des tâches qui doivent être terminées avant. Le sprint reçoit deux propriétés calculées à la volée grâce à @property : la date de fin et la somme des points.
1@dataclass
2class Sprint:
3 number: int
4 start_date: datetime
5 tasks: list[Task]
6 velocity: int = 20 # Story points par sprint
7
8 @property
9 def end_date(self) -> datetime:
10 return self.start_date + timedelta(days=14)
11
12 @property
13 def total_points(self) -> int:
14 return sum(t.story_points for t in self.tasks)velocity est le nombre de points que l'équipe boucle réellement en un sprint. On suppose 20, mais tu ne connaîtras la vraie valeur qu'après quelques sprints. Voyons le plan des deux premières étapes de l'expédition :
1# Plan du projet
2sprints = [
3 Sprint(1, datetime(2024, 1, 1), [
4 Task("Mise en place du projet", 2, []),
5 Task("Configuration CI/CD", 3, ["Mise en place du projet"]),
6 Task("API de base", 5, ["Mise en place du projet"]),
7 Task("Intégration de la base vectorielle", 5, ["API de base"]),
8 ]),
9 Sprint(2, datetime(2024, 1, 15), [
10 Task("Pipeline RAG", 8, ["Intégration de la base vectorielle"]),
11 Task("Interface de chat", 5, ["API de base"]),
12 Task("Téléversement de documents", 5, ["Pipeline RAG"]),
13 ])
14]L'ordre découle des dépendances : d'abord le setup, puis l'API, et seulement ensuite le RAG. Le premier sprint compte 15 points, le second 18, donc les deux tiennent dans la velocity prévue et il reste de la marge pour les imprévus.
Choisir la stack technologique
Le dernier élément de la carte, ce sont les décisions technologiques. Note les raisons à côté de chaque choix, car dans six mois quelqu'un (peut-être toi) demandera pourquoi Qdrant précisément.
1tech_decisions = {
2 "backend": {
3 "choice": "FastAPI",
4 "reasons": ["Async", "Type hints", "Docs auto", "Performance"]
5 },
6 "vector_db": {
7 "choice": "Qdrant",
8 "reasons": ["Auto-hébergé", "Filtrage", "Performance Rust"]
9 },
10 "llm": {
11 "choice": "OpenAI API",
12 "reasons": ["Qualité", "Function calling", "Fiable"]
13 },
14 "rag_framework": {
15 "choice": "LlamaIndex",
16 "reasons": ["Flexibilité", "Intégrations", "Communauté"]
17 }
18}Pour le modèle de langage, on a volontairement noté le fournisseur et pas un modèle précis. Les noms de modèles changent tous les quelques mois et les anciennes versions sont retirées, alors choisis le modèle actuel dans la documentation du fournisseur le jour où le projet démarre. Mon conseil : garde cette liste de décisions dans le dépôt, à côté du code, et pas dans tes notes, pour qu'elle vive avec le projet.
Le cycle de vie complet d'un projet, c'est dans l'ordre la planification, l'implémentation, les tests, le déploiement et le monitoring. On installe les dépendances d'un fichier requirements.txt avec pip install -r requirements.txt, et on fixe une version précise d'un paquet avec la forme nom==version, par exemple pip install numpy==1.26.4. Dans la prochaine leçon, on concevra l'architecture du système qui transformera ce plan en structure de code.
Souviens-toi : un bon plan d'expédition, c'est la moitié du succès, car une carte tracée avant le départ t'épargne des semaines d'errance dans la savane.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Qu'est-ce qu'une User Story dans la méthodologie Agile ?
2. Combien de temps dure généralement un Sprint en Scrum ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri vertical
Range les étapes du cycle de vie d'un projet d'IA dans le bon ordre :
- Éditeur de code
Crée un fichier de définition de projet avec la structure de base d'un projet d'IA
- Tri horizontal
Range la commande pip install pour une version précise d'un paquet dans le bon ordre :
- Clique dans l'ordre
Clique sur les éléments pour construire une définition de dataclass Python dans le bon ordre :