From 60b912d524107427053a86e76761aee285f634d6 Mon Sep 17 00:00:00 2001 From: Johan Date: Wed, 17 Dec 2025 13:30:08 +0100 Subject: [PATCH] TP done --- src/app/graphql/queries.py | 14 +---- src/app/graphql/resolvers/analyze_movie_v1.py | 7 +-- src/app/graphql/resolvers/analyze_movie_v2.py | 11 +--- src/app/repositories/genre_repository.py | 4 -- src/app/repositories/movie_repository.py | 16 ++---- src/app/services/movie_analyzer_v1.py | 16 ++---- src/app/services/movie_analyzer_v2.py | 56 +++++++------------ 7 files changed, 39 insertions(+), 85 deletions(-) diff --git a/src/app/graphql/queries.py b/src/app/graphql/queries.py index 44775ea..eb6e93f 100644 --- a/src/app/graphql/queries.py +++ b/src/app/graphql/queries.py @@ -1,7 +1,5 @@ import strawberry -# Note : l'énoncé demande d'importer la V1 à l'étape 3, puis la V2 à l'étape 6 -# L'import ci-dessous correspond à l'étape 6. from app.graphql.resolvers.analyze_movie_v2 import analyze_movie_by_id from app.graphql.types.movie_analysis import MovieAnalysis @@ -11,13 +9,7 @@ class Query: """ Point d'entrée pour toutes les requêtes GraphQL de type 'query'. """ - - # TODO : (Étape 3) L'énoncé vous demande d'implémenter ce champ - # en important et en utilisant 'analyze_movie_v1' - - # (Étape 6) L'énoncé vous demande ensuite de basculer vers 'analyze_movie_v2' - # La configuration ci-dessous correspond à l'étape 6. - analyzeMovie: MovieAnalysis = strawberry.field( - resolver=analyze_movie_by_id, # 'analyze_movie_by_id' est importé depuis v2 - description="Analyse un film en utilisant l'IA." + analyze_movie: MovieAnalysis = strawberry.field( + resolver=analyze_movie_by_id, + description="Lance une analyse par IA d'un film donné par son ID." ) diff --git a/src/app/graphql/resolvers/analyze_movie_v1.py b/src/app/graphql/resolvers/analyze_movie_v1.py index 6f1159d..d290151 100644 --- a/src/app/graphql/resolvers/analyze_movie_v1.py +++ b/src/app/graphql/resolvers/analyze_movie_v1.py @@ -10,9 +10,6 @@ async def analyze_movie_by_id( # movie_input: MovieInput, classe nécessaire seulement si on avait eu beaucoup de champs en entrée ) -> MovieAnalysis: - # TODO : (Étape 3) Remplacer la ligne suivante par un appel au service V1 - # analysis_data = await analyze_movie(movie_id=movie_id) - raise NotImplementedError("Le resolver analyze_movie_v1.analyze_movie_by_id() n'est pas implémenté.") + analysis_data = await analyze_movie(movie_id=movie_id) - # La ligne ci-dessous doit être décommentée une fois le TODO complété - # return MovieAnalysis(**analysis_data) \ No newline at end of file + return MovieAnalysis(**analysis_data) diff --git a/src/app/graphql/resolvers/analyze_movie_v2.py b/src/app/graphql/resolvers/analyze_movie_v2.py index 7e5506d..20ff5cc 100644 --- a/src/app/graphql/resolvers/analyze_movie_v2.py +++ b/src/app/graphql/resolvers/analyze_movie_v2.py @@ -5,18 +5,13 @@ from app.graphql.resolvers.helper import is_field_requested from app.graphql.types.movie_analysis import MovieAnalysis from app.services.movie_analyzer_v2 import analyze_movie - async def analyze_movie_by_id( - movie_id: strawberry.ID, - info: Info, + movie_id: strawberry.ID, + info: Info, ) -> MovieAnalysis: - # TODO : (Étape 6) Remplacer la ligne suivante par la récupération - # du LLM depuis 'info.context' (ex: llm = info.context["llm"]) - raise NotImplementedError("Le resolver V2 n'a pas encore récupéré le LLM du contexte.") - # llm = ... # <== Code à écrire + llm = info.context["llm"] - # Le 'llm=llm' ci-dessous fonctionnera une fois le TODO complété analysis_data = await analyze_movie( movie_id=movie_id, ai_summary=is_field_requested(info, "aiSummary"), diff --git a/src/app/repositories/genre_repository.py b/src/app/repositories/genre_repository.py index 55523c9..5936e99 100644 --- a/src/app/repositories/genre_repository.py +++ b/src/app/repositories/genre_repository.py @@ -7,10 +7,6 @@ from app.repositories._base_client import api_client class GenreRepository: async def list(self) -> List[Genre]: response = await api_client._request("GET", "/genres/") - # TODO : (Étape 2) Remplacer la ligne suivante par un appel à - # response = await api_client._request("GET", "/genres/") - # La ligne ci-dessous doit être décommentée une fois le TODO complété return [Genre.model_validate(g) for g in response.json()] - genre_repository = GenreRepository() \ No newline at end of file diff --git a/src/app/repositories/movie_repository.py b/src/app/repositories/movie_repository.py index 6ede8eb..c25653d 100644 --- a/src/app/repositories/movie_repository.py +++ b/src/app/repositories/movie_repository.py @@ -1,28 +1,24 @@ from typing import List, Optional -import httpx + from app.core.exceptions import DALException from app.models.movie import Movie from app.repositories._base_client import api_client - class MovieRepository: async def list(self, skip: int = 0, limit: int = 100) -> List[Movie]: - # TODO : (Étape 2) Remplacer la ligne suivante par un appel à - # response = await api_client._request("GET", "/movies/", params={"skip": skip, "limit": limit}) - # La ligne ci-dessous doit être décommentée une fois le TODO complété - response = await api_client._request("GET", "/movies/", params={"skip": skip, "limit": limit}) + response = await api_client._request( + "GET", "/movies/", params={"skip": skip, "limit": limit} + ) return [Movie.model_validate(m) for m in response.json()] async def find_by_id(self, movie_id: int) -> Optional[Movie]: try: response = await api_client._request("GET", f"/movies/{movie_id}") - # TODO : (Étape 2) Remplacer la ligne suivante par un appel à - # response = await api_client._request("GET", f"/movies/{movie_id}") - # La ligne ci-dessous doit être décommentée une fois le TODO complété return Movie.model_validate(response.json()) except DALException as e: if e.status_code == 404: return None raise -movie_repository = MovieRepository() \ No newline at end of file + +movie_repository = MovieRepository() diff --git a/src/app/services/movie_analyzer_v1.py b/src/app/services/movie_analyzer_v1.py index b0c8dc6..175aec7 100644 --- a/src/app/services/movie_analyzer_v1.py +++ b/src/app/services/movie_analyzer_v1.py @@ -1,17 +1,9 @@ async def analyze_movie(movie_id: str) -> dict: - # TODO : retourner un dictionnaire python statique (chaînes de caractères en dur) avec comme attributs: - # id (correspondant à movie_id) - # aiSummary (chaîne de caractères arbitraire) - # aiOpinionSummary (chaîne de caractères arbitraire) - # aiBestGenre (chaîne de caractères arbitraire) - # aiTags (TABLEAU de chaînes de caractères arbitraire) - raise NotImplementedError("Le service movie_analyzer_v1.analyze_movie() n'est pas implémenté.") return { "id": movie_id, - - - - + "aiSummary" : "C'est l'histoire de...", + "aiOpinionSummary": "Le film est une aventure épique...", + "aiBestGenre": "Fantastique", + "aiTags": ["Épique", "Quête", "Magie"] } - diff --git a/src/app/services/movie_analyzer_v2.py b/src/app/services/movie_analyzer_v2.py index 4894d87..a27db1b 100644 --- a/src/app/services/movie_analyzer_v2.py +++ b/src/app/services/movie_analyzer_v2.py @@ -41,23 +41,13 @@ async def get_ai_best_genre(llm, synopsis, all_genres): genres_list = ", ".join([genre.label for genre in all_genres]) # Prompt pour choisir le genre le plus pertinent - # TODO : compléter les instructions du prompt prompt = f""" - # TODO : Écrire les instructions pour le LLM. - # Objectif : Choisir le *seul* genre le plus pertinent pour le film. - # Contraintes : - # 1. Le LLM DOIT répondre en français. - # 2. Le LLM DOIT choisir son genre EXCLUSIVEMENT parmi la liste fournie. - # 3. Le LLM NE DOIT retourner QUE le nom du genre (ex: "Drame"), sans aucune autre phrase. - - Voici le synopsis : - {synopsis} - - Voici la liste des genres autorisés : - {genres_list} - - Genre le plus pertinent : - """ + Français uniquement. + Parmi la liste suivante de genres cinématographiques, choisis le genre le plus pertinent pour le synopsis donné. + Liste des genres : {genres_list} + Ne retourne que le nom du genre, sans explication, sans phrase d'introduction. + Synopsis : {synopsis} + """ # Appel asynchrone au modèle de langage response = await llm.ainvoke(prompt) @@ -68,21 +58,15 @@ async def get_ai_tags(llm, title, synopsis): if not title or not synopsis: return None - # TODO : définir le prompt approprié prompt = f""" - # TODO : Écrire les instructions pour le LLM. - # Objectif : Générer 5 tags (mots-clés) pertinents pour le film. - # Contraintes : - # 1. Le LLM DOIT répondre en français. - # 2. Le LLM DOIT retourner une liste de tags séparés par des virgules. - # 3. Le LLM NE DOIT PAS inclure de phrase d'introduction (ex: "Voici les tags :"). - - Titre du film : {title} - Synopsis : {synopsis} - - Génère 5 tags pertinents, séparés par des virgules : - """ + Français uniquement. + Liste 5 à 8 mots-clés (tags) pertinents pour le film suivant. + Réponds uniquement avec les mots-clés séparés par des virgules, sans numérotation, sans explication, sans phrase d'introduction. + Par exemple : "intelligence artificielle, prophétie, pirate informatique, réalité virtuelle". + Titre : {title} + Synopsis : {synopsis} + """ response = await llm.ainvoke(prompt) tags = [tag.strip() for tag in response.content.split(',') if tag.strip()] return tags @@ -113,12 +97,14 @@ async def analyze_movie( if ai_summary: tasks["aiSummary"] = get_ai_summary(llm, movie_data.synopsis) - # TODO : (Étape 5) compléter la logique d'ajout des tâches avec : - # appeler 'get_ai_opinion_summary', 'get_ai_best_genre', 'get_ai_tags' - # mettre le résultat respectivement dans la clé "aiOpinionSummary", "aiBestGenre", "aiTags" (ATTENTION : il faut respecter la casse pour ces clés!) du tableau associatif (dictionnaire) "tasks" - # respectivement en fonction des booléens 'ai_opinion_summary', 'ai_best_genre', 'ai_tags' - if ai_opinion_summary or ai_best_genre or ai_tags: - raise NotImplementedError("La logique d'ajout de tâches (opinion, genre, tags) n'est pas implémentée.") + if ai_opinion_summary: + tasks["aiOpinionSummary"] = get_ai_opinion_summary(llm, movie_data.title, movie_data.opinions) + + if ai_best_genre: + tasks["aiBestGenre"] = get_ai_best_genre(llm, movie_data.synopsis, all_genres) + + if ai_tags: + tasks["aiTags"] = get_ai_tags(llm, movie_data.title, movie_data.synopsis) if tasks: # On récupère les coroutines (les fonctions async prêtes à être lancées)