CDA-Q / app.py
OUAREDAEK's picture
Upload app.py with huggingface_hub
b67bf8f verified
Raw
History Blame Contribute Delete
4.11 kB
import os
import random
import torch
import pandas as pd
from functools import lru_cache
from flask import Flask, render_template, request, jsonify
from sentence_transformers import SentenceTransformer
from fastapi import FastAPI
from fastapi.middleware.wsgi import WSGIMiddleware
import uvicorn
# ===============================
# CONFIG HF
# ===============================
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CSV_DATA = os.path.join(BASE_DIR, "dataset_2026.csv")
EMB_FILE = os.path.join(BASE_DIR, "embeddings_questions.pt")
TOP_K_RECOMMANDATIONS = 5
DEVICE = "cpu" # ⛔ FORCÉ CPU (HF)
# ===============================
# FLASK APP
# ===============================
app = Flask(
__name__,
template_folder=os.path.join(BASE_DIR, "templates"),
static_folder=os.path.join(BASE_DIR, "static")
)
# ===============================
# LOAD MODEL (SAFE)
# ===============================
print("🔹 Loading model (CPU only)...")
model = SentenceTransformer(
"sentence-transformers/all-MiniLM-L6-v2",
device="cpu"
)
# ===============================
# LOAD DATASET
# ===============================
print("🔹 Loading dataset...")
df = pd.read_csv(CSV_DATA)
df = df.dropna(subset=["question"]).reset_index(drop=True)
questions = df["question"].astype(str).tolist()
NB_QUESTIONS = len(questions)
print(f"✅ Questions loaded: {NB_QUESTIONS}")
# ===============================
# LOAD / CREATE EMBEDDINGS
# ===============================
if os.path.exists(EMB_FILE):
print("🔹 Loading cached embeddings...")
emb_base = torch.load(EMB_FILE, map_location="cpu")
else:
print("🔹 Computing embeddings...")
emb_base = model.encode(
questions,
convert_to_tensor=True,
normalize_embeddings=True,
batch_size=32
)
torch.save(emb_base, EMB_FILE)
emb_base = emb_base.cpu()
# ===============================
# CACHE QUESTION EMBEDDING
# ===============================
@lru_cache(maxsize=500)
def encode_question_cached(q: str):
return model.encode(
q,
convert_to_tensor=True,
normalize_embeddings=True
).cpu()
# ===============================
# UTILS
# ===============================
def enrich_message(base):
return random.choice([
f"Bonne question 🙂 {base}",
f"Voici ce que je peux vous dire : {base}",
base
])
# ===============================
# CORE LOGIC
# ===============================
def process_question(question: str):
if not question.strip():
return {"response": "Veuillez poser une question."}
emb_q = encode_question_cached(question).unsqueeze(0)
scores = torch.matmul(emb_q, emb_base.T).squeeze(0)
values, indices = torch.topk(scores, k=min(TOP_K_RECOMMANDATIONS + 1, len(scores)))
best_idx = indices[0].item()
confidence = int(values[0].item() * 100)
if confidence < 50:
return {
"response": "Je ne suis pas sûr de la réponse.",
"confidence": confidence
}
return {
"response": enrich_message(df["rationale"].iloc[best_idx]),
"confidence": confidence,
"matched": df["question"].iloc[best_idx],
"intent": df["intent"].iloc[best_idx]
}
# ===============================
# ROUTES
# ===============================
@app.route("/")
def index():
return render_template("index.html")
@app.route("/ask", methods=["POST"])
def ask():
data = request.get_json() or {}
question = data.get("question", "")
return jsonify(process_question(question))
# ===============================
# FASTAPI WRAPPER (HF)
# ===============================
fastapi_app = FastAPI()
fastapi_app.mount("/", WSGIMiddleware(app))
# ===============================
# MAIN
# ===============================
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
print(f"🚀 Running on port {port}")
uvicorn.run(fastapi_app, host="0.0.0.0", port=port)