This commit is contained in:
Johan
2025-12-17 13:30:08 +01:00
parent cc50161771
commit 60b912d524
7 changed files with 39 additions and 85 deletions

View File

@@ -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."
)

View File

@@ -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)
return MovieAnalysis(**analysis_data)

View File

@@ -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"),

View File

@@ -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()

View File

@@ -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()
movie_repository = MovieRepository()

View File

@@ -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"]
}

View File

@@ -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)