| import os, random |
| import torch |
| import pandas as pd |
| from flask import Flask, render_template, request, jsonify |
| from sentence_transformers import SentenceTransformer, util |
| import gradio as gr |
|
|
| BASE_DIR = os.path.abspath(os.path.dirname(__file__)) |
|
|
| app = Flask( |
| __name__, |
| template_folder=os.path.join(BASE_DIR, "templates"), |
| static_folder=os.path.join(BASE_DIR, "static") |
| ) |
|
|
| CSV_DATA = "dataset_2026.csv" |
| EMB_FILE = "embeddings_questions.pt" |
| TOP_K_RECOMMANDATIONS = 5 |
|
|
| print("🔄 Chargement du modèle...") |
| try: |
| model = SentenceTransformer( |
| "OrdalieTech/Solon-embeddings-mini-beta-1.1", |
| device="cpu", |
| trust_remote_code=True |
| ) |
| print("✓ Modèle principal (Solon) chargé") |
| except Exception as e: |
| print(f"⚠️ Échec du modèle principal (probablement dû à la version Hugging Face Hub): {e}") |
| print("🔄 Chargement du modèle de secours (paraphrase-multilingual)...") |
| model = SentenceTransformer( |
| "paraphrase-multilingual-MiniLM-L12-v2", |
| device="cpu" |
| ) |
| print("✓ Modèle de secours chargé avec succès !") |
|
|
| df_cache = None |
|
|
| def load_data(): |
| global df_cache |
| if df_cache is None: |
| try: |
| |
| df_cache = pd.read_csv(CSV_DATA, sep=";", encoding="latin-1") |
| df_cache.columns = df_cache.columns.str.strip() |
| except: |
| |
| df_cache = pd.read_csv(CSV_DATA, sep=None, engine='python', encoding="utf-8") |
| df_cache.columns = df_cache.columns.str.strip() |
| return df_cache |
|
|
| def load_or_create_embeddings(df): |
| if os.path.exists(EMB_FILE): |
| emb = torch.load(EMB_FILE, map_location="cpu") |
| if emb.shape[0] == len(df) and emb.shape[1] == model.get_sentence_embedding_dimension(): |
| return emb |
| print("⚠️ Dimensions embeddings incorrectes, recréation...") |
|
|
| print("🔨 Création embeddings...") |
| questions = df["Question"].astype(str).tolist() |
| emb = model.encode( |
| questions, |
| convert_to_tensor=True, |
| normalize_embeddings=True |
| ) |
| torch.save(emb, EMB_FILE) |
| return emb |
|
|
| def enrich_message(base): |
| return random.choice([ |
| f"Bonne question 🙂\n\n{base}", |
| f"Voici la réponse détaillée :\n\n{base}", |
| f"Voici ce que j'ai trouvé :\n\n{base}", |
| base |
| ]) |
|
|
| def process_question(question): |
| df = load_data() |
| emb_base = load_or_create_embeddings(df) |
|
|
| emb_q = model.encode(question, convert_to_tensor=True, normalize_embeddings=True) |
| scores = util.pytorch_cos_sim(emb_q, emb_base)[0] |
|
|
| best_idx = torch.argmax(scores).item() |
| confidence = int(scores[best_idx].item() * 100) |
|
|
| if confidence < 40: |
| recs = df["Question"].sample(min(TOP_K_RECOMMANDATIONS, len(df))).tolist() |
| return { |
| "response": "Je ne suis pas sûr", |
| "confidence": confidence, |
| "matched": "—", |
| "intent": "Inconnu", |
| "recs": recs, |
| "service": None, |
| "lat": None, |
| "lon": None |
| } |
|
|
| row = df.iloc[best_idx] |
| |
| service_val = str(row["Service"]).strip() if pd.notna(row["Service"]) else None |
| if service_val and service_val.lower() in ['nan', 'none', 'null', '']: service_val = None |
|
|
| link_val = str(row["ServiceLink"]).strip() if pd.notna(row["ServiceLink"]) else None |
| if link_val and link_val.lower() in ['nan', 'none', 'null', '']: link_val = None |
|
|
| try: |
| raw_lat = str(row["Latitude"]).replace(',', '.') |
| raw_lon = str(row["Longitude"]).replace(',', '.') |
| lat_val = float(raw_lat) |
| lon_val = float(raw_lon) |
| except Exception: |
| lat_val = None |
| lon_val = None |
|
|
| return { |
| "response": enrich_message(row["Response"]), |
| "confidence": confidence, |
| "matched": row["Question"], |
| "intent": row["Intent"], |
| "recs": [], |
| "service": service_val, |
| "link": link_val, |
| "lat": lat_val, |
| "lon": lon_val |
| } |
|
|
| @app.route("/") |
| def index(): |
| return render_template("index.html") |
|
|
| @app.route("/ask", methods=["POST"]) |
| def ask(): |
| return jsonify(process_question(request.json.get("question", ""))) |
|
|
| @app.route("/api/services", methods=["GET"]) |
| def get_services(): |
| df = load_data() |
| valid_df = df.dropna(subset=["Service", "Latitude", "Longitude"]) |
| |
| services = [] |
| seen = set() |
|
|
| for _, row in valid_df.iterrows(): |
| try: |
| name = str(row["Service"]).strip() |
| lat = float(str(row["Latitude"]).replace(',', '.')) |
| lon = float(str(row["Longitude"]).replace(',', '.')) |
|
|
| if name and name not in seen: |
| services.append({"service": name, "lat": lat, "lon": lon}) |
| seen.add(name) |
| except Exception: |
| pass |
| |
| return jsonify({"status": "success", "services": services}) |
|
|
| |
| |
| |
| def gradio_chat(message, history): |
| return process_question(message)["response"] |
|
|
| iface = gr.ChatInterface( |
| fn=gradio_chat, |
| title="AskLaQ Assistant" |
| ) |
|
|
| if __name__ == "__main__": |
| print("🚀 Serveur Flask lancé sur http://127.0.0.1:7860") |
| app.run(host="0.0.0.0", port=7860, debug=True, use_reloader=False) |
|
|