oc-p07-sentiment-api/deployment/api/main.py
2025-07-04 08:58:47 +02:00

113 lines
4 KiB
Python

import os
import dill
import pickle
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import mlflow
from mlflow.tracking import MlflowClient
import mlflow.keras
from tensorflow.keras.preprocessing.sequence import pad_sequences
# Charger les variables d'environnement
load_dotenv(dotenv_path='../../.env')
#%%
mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI")
if mlflow_tracking_uri:
mlflow.set_tracking_uri(mlflow_tracking_uri)
else:
print("⚠️ Attention : MLFLOW_TRACKING_URI non défini dans .env")
# Modèle et étape par défaut
MODEL_NAME = "SentimentAnalysisLSTM"
STAGE = "Production"
class TweetRequest(BaseModel):
tweet: str
def get_latest_version(client, model_name, stage):
versions = client.search_model_versions(f"name='{model_name}'")
filtered = [v for v in versions if v.current_stage == stage]
if not filtered:
return None
latest = max(filtered, key=lambda v: int(v.version))
return latest
def load_model_and_artifacts(model_name="SentimentAnalysisLSTM", stage="Production"):
client = MlflowClient()
versions = client.get_latest_versions(model_name, [stage])
if not versions:
raise RuntimeError(f"Aucune version du modèle '{model_name}' en stage '{stage}'")
version = versions[0]
run_id = version.run_id
if run_id is None:
raise RuntimeError("'run_id' introuvable.")
# Récupérer les tags du modèle pour trouver embedding_type
model_version_info = client.get_model_version(model_name, version.version)
embedding_type = model_version_info.tags.get("embedding_type", "glove").lower()
local_dir = "./downloaded_artifacts"
os.makedirs(local_dir, exist_ok=True)
base_path = "local_artifacts/logs"
if embedding_type == "glove":
keras_file = "final_model_LSTM_GloVe-300d-Fige.keras"
tokenizer_file = "tokenizer.pickle"
preprocess_file = "preprocess_function.dill"
elif embedding_type == "word2vec":
keras_file = "final_model_LSTM_Word2Vec.keras"
tokenizer_file = "tokenizer_word2vec.pickle"
preprocess_file = "preprocess_function_word2vec.dill"
else:
raise ValueError(f"Type d'embedding inconnu: {embedding_type}")
def dl(path):
return client.download_artifacts(run_id=run_id, path=f"{base_path}/{path}", dst_path=local_dir)
model_path = dl(keras_file)
tokenizer_path = dl(tokenizer_file)
preprocess_path = dl(preprocess_file)
model = mlflow.keras.load_model(model_path)
with open(tokenizer_path, "rb") as f:
tokenizer = pickle.load(f)
with open(preprocess_path, "rb") as f:
preprocess_fn = dill.load(f)
return model, tokenizer, preprocess_fn, embedding_type
app = FastAPI(title="API Prédiction Sentiment - Air Paradis")
try:
model, tokenizer, preprocess_tweet = load_model_and_artifacts()
except Exception as e:
print(f"❌ Erreur lors du chargement des artefacts : {e}")
model, tokenizer, preprocess_tweet = None, None, None
@app.get("/")
def root():
return {"message": "API Prédiction Sentiment Air Paradis - OK"}
@app.post("/predict")
def predict_sentiment(request: TweetRequest):
if model is None or tokenizer is None or preprocess_tweet is None:
raise HTTPException(status_code=503, detail="Modèle ou artefacts non disponibles.")
processed_tweet = preprocess_tweet(request.tweet)
if not processed_tweet.strip():
raise HTTPException(status_code=400, detail="Tweet vide ou invalide après prétraitement.")
sequence = tokenizer.texts_to_sequences([processed_tweet])
sequence_padded = pad_sequences(sequence, maxlen=100) # Ajuster maxlen si besoin
try:
prediction_prob = model.predict(sequence_padded)[0][0]
sentiment = "positif" if prediction_prob >= 0.5 else "negatif"
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erreur lors de l'inférence : {e}")
return {"sentiment": sentiment, "probability": float(prediction_prob)}