| from groq import Groq |
| from utils.prompts import get_system_prompt_chat, get_keywords_by_lang, get_system_prompt_preprocessing, get_system_prompt_explanation_plot |
| import os |
| import logging |
| from reportlab.lib.pagesizes import A4 |
| from reportlab.pdfgen import canvas |
| from reportlab.lib.utils import ImageReader |
| from reportlab.lib import colors |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
| from reportlab.platypus import Paragraph, Table, TableStyle, SimpleDocTemplate, Spacer, Image, PageBreak |
| from reportlab.lib.enums import TA_LEFT |
| from datetime import datetime |
| import io |
| import base64 |
| import re |
|
|
| |
| GROQ_API_KEY = os.getenv('GROQ_API_KEY') |
|
|
| def clean_markdown(text): |
| |
| return re.sub(r'(\*\*|\*)', '', text) |
|
|
| def handle_chat_request(data): |
| """ |
| Handles a chat request by preparing the context and sending it to the Groq API. |
| It builds the conversation history, attaches dataset previews, stats, plots, etc. |
| """ |
| user_input = data.get('message', '') |
| if not user_input: |
| raise ValueError("error_empty_message") |
|
|
| |
| groq_key = data.get('groq_api_key') or GROQ_API_KEY |
| if not groq_key: |
| raise ValueError("groqMissing") |
|
|
| lang = data.get('lang', 'en') |
| summary = data.get('summary') |
| model_metadata = data.get('model_metadata') or {} |
| dataset = data.get('markdown_preview') |
| stats = data.get('stats') |
| shap_plot = data.get('shap_summary_plot') |
| plot_predictions = data.get('plots_prediction_results') |
| training_pred_plot = data.get('png_result_training_for_prediction') |
| |
| messages = [{"role": "system", "content": get_system_prompt_chat(lang)}] |
|
|
| |
| if dataset is None and any(kw in user_input.lower() for kw in get_keywords_by_lang(lang)): |
| messages.append({ |
| "role": "assistant", |
| "content": "Je ne vois pas encore de dataset ou de résultats d'entraînement. Pour que je puisse vous aider, vous devez d’abord lancer un entraînement via l’interface avec vos données." |
| }) |
|
|
| |
| append_if_exists(messages, "Aperçu du dataset fourni :", dataset) |
| append_if_exists(messages, "Statistiques du dataset :", stats) |
| if summary: |
| append_if_exists(messages, "Résumé des résultats d'entraînement :", summary.get('text')) |
| |
| metrics_plot = summary.get("metrics_plot", {}) |
| if metrics_plot: |
| metric_lines = [] |
| for k, v in metrics_plot.items(): |
| try: |
| val = v.get("value") |
| if val is not None: |
| metric_lines.append(f"{k.upper()}: {val:.4f}") |
| except Exception: |
| continue |
| if metric_lines: |
| append_if_exists(messages, "Métriques:", "\n".join(metric_lines)) |
| |
| meta_lines = [] |
| if model_metadata.get("task_type"): |
| meta_lines.append(f"Tâche: {model_metadata['task_type']}") |
| if model_metadata.get("best_model"): |
| meta_lines.append(f"Meilleur modèle: {model_metadata['best_model']}") |
| if model_metadata.get("target_column"): |
| meta_lines.append(f"Colonne cible: {model_metadata['target_column']}") |
| if model_metadata.get("prediction_length"): |
| meta_lines.append(f"Longueur de prédiction: {model_metadata['prediction_length']}") |
| if meta_lines: |
| append_if_exists(messages, "Métadonnées du modèle:", "\n".join(meta_lines)) |
|
|
| |
| if summary: |
| add_metric_plots_from_summary(messages, summary) |
|
|
| |
| if shap_plot: |
| add_image_message(messages, "Voici le graphe SHAP de l'entraînement.", shap_plot) |
|
|
| |
| if plot_predictions: |
| for name, plot in plot_predictions.items(): |
| add_image_message(messages, f"Voici un des graphes {name} des résultats de prédiction.", plot) |
|
|
| |
| if training_pred_plot: |
| for name, plot in training_pred_plot.items(): |
| add_image_message(messages, f"Voici un des graphes {name} qui montre les résultats de l'entrainement du modèle qui sert à faire les prédictions.", plot) |
|
|
| |
| messages.append({"role": "user", "content": user_input}) |
|
|
| |
| client = Groq(api_key=groq_key) |
| completion = client.chat.completions.create( |
| model="meta-llama/llama-4-scout-17b-16e-instruct", |
| messages=messages, |
| temperature=0.7, |
| max_completion_tokens=1024, |
| top_p=1, |
| stream=False |
| ) |
|
|
| return completion.choices[0].message.content |
|
|
| def append_if_exists(messages, title, content): |
| |
| if content: |
| messages.append({"role": "user", "content": f"{title}\n{content}"}) |
|
|
| def add_image_message(messages, caption, base64_str): |
| |
| try: |
| if not base64_str: |
| return |
| |
| base64.b64decode(base64_str, validate=True) |
| except Exception: |
| logging.exception("Invalid base64 image, skipping.") |
| return |
|
|
| messages.append({"role": "user", "content": [ |
| {"type": "text", "text": caption}, |
| {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_str}"}} |
| ]}) |
|
|
| def add_metric_plots_from_summary(messages, summary): |
| |
| if "feature_importance_plot" in summary: |
| add_image_message(messages, "Voici le graphe de l'importance des variables.", summary["feature_importance_plot"]) |
| if "forecast_plot" in summary and summary["forecast_plot"]: |
| add_image_message(messages, "Prévision vs valeurs réelles", summary["forecast_plot"]) |
| for metric_name, metric_obj in summary.get("metrics_plot", {}).items(): |
| for plot_type in ['plot', 'plot_hist']: |
| if plot_type in metric_obj: |
| base64_plot = metric_obj[plot_type] |
| if base64_plot: |
| add_image_message(messages, f"Voici le graphe {metric_name} ({plot_type}).", base64_plot) |
|
|
| def build_preprocessing_summary(preprocessing_summary: dict, lang='en', groq_api_key=None) -> str: |
| """ |
| Generates a natural language summary of the preprocessing steps using the Groq API. |
| If no API key is available, returns a basic text summary of the preprocessing dict. |
| """ |
| key = groq_api_key or GROQ_API_KEY |
| if not key: |
| return f"Preprocessing steps: {preprocessing_summary}" |
|
|
| try: |
| client = Groq(api_key=key) |
| except Exception: |
| logging.exception("No valid Groq API key for preprocessing summary") |
| return f"Preprocessing steps: {preprocessing_summary}" |
|
|
| system_prompt = get_system_prompt_preprocessing() |
|
|
| messages = [ |
| {"role": "system", "content": system_prompt}, |
| {"role": "user", "content": str(preprocessing_summary)} |
| ] |
|
|
| try: |
| response = client.chat.completions.create( |
| model="meta-llama/llama-4-scout-17b-16e-instruct", |
| messages=messages, |
| temperature=0.5, |
| max_completion_tokens=512, |
| top_p=1, |
| stream=False |
| ) |
| return response.choices[0].message.content |
| except Exception: |
| logging.exception("Groq call failed for preprocessing summary") |
| return f"Preprocessing steps: {preprocessing_summary}" |
|
|
| def generate_explanation_plot(plot = None, name = None, value = None, groq_api_key=None): |
| """ |
| Generates an explanation for a given plot and metric value using the Groq API. |
| """ |
| key = groq_api_key or GROQ_API_KEY |
| if not key: |
| return f"Plot {name} (value={value})" |
| messages = [{"role": "system", "content": get_system_prompt_explanation_plot()}] |
| if plot and value: |
| add_image_message(messages, f"Here the graph {name} to explain and the value of the metric {value}", plot) |
| elif plot: |
| add_image_message(messages, f"Here the graph {name} to explain.", plot) |
| else: |
| messages.append({"role": "user", "content": f"Please explain the metric {name} with the value {value}."}) |
| |
| try: |
| client = Groq(api_key=key) |
| response = client.chat.completions.create( |
| model="meta-llama/llama-4-scout-17b-16e-instruct", |
| messages=messages, |
| temperature=0.5, |
| max_completion_tokens=512, |
| top_p=1, |
| stream=False |
| ) |
| raw_text = response.choices[0].message.content |
| return clean_markdown(raw_text) |
| except Exception: |
| logging.exception("Groq call failed for explanation plot") |
| return f"Plot {name} (value={value})" |
|
|
| def generate_pdf(summary_results, dataset_preview, dataset_stats, preprocessing_summary, |
| target_column, dataset_name, shap_plot, output_dir="pdf_summary", groq_api_key=None): |
| """ |
| Generates a PDF summary report of the training results, including dataset preview, |
| statistics, preprocessing, metrics, plots, and explanations. |
| """ |
| |
| os.makedirs(output_dir, exist_ok=True) |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| pdf_filename = f"{dataset_name}_{timestamp}.pdf" |
| output_path = os.path.join(output_dir, pdf_filename) |
|
|
| |
| buffer = io.BytesIO() |
| doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=40, leftMargin=40, topMargin=60, bottomMargin=40) |
| styles = getSampleStyleSheet() |
| elements = [] |
|
|
| |
| title_style = styles['Heading1'] |
| title_style.alignment = 1 |
|
|
| today_str = datetime.now().strftime("%B %d, %Y") |
| title_text = f"Training Summary - {dataset_name} ({today_str})" |
| elements.append(Paragraph(title_text, title_style)) |
| elements.append(Spacer(1, 20)) |
|
|
| |
| elements.append(Paragraph("1. Dataset Overview", styles['Heading2'])) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| elements.append(Paragraph("<b>Dataset Preview (first 5 rows)</b>", styles['Normal'])) |
| |
| |
| preview_lines = dataset_preview.strip().split('\n') |
| preview_data = [ |
| line.strip('|').split('|') |
| for line in preview_lines |
| if '|' in line and not re.match(r'^\s*-{2,}', line.strip().replace('|', '').strip()) |
| ] |
| preview_data = [[cell.strip() for cell in row] for row in preview_data] |
|
|
| |
| max_columns = len(preview_data[0]) |
|
|
| |
| page_width = A4[0] - doc.leftMargin - doc.rightMargin |
| col_width = page_width / max_columns |
| col_widths = [col_width] * max_columns |
|
|
| |
| cell_style = ParagraphStyle( |
| name='TableCell', |
| fontName='Helvetica', |
| fontSize=7 if max_columns > 8 else 9, |
| leading=10, |
| alignment=TA_LEFT, |
| wordWrap='CJK', |
| ) |
|
|
| |
| wrapped_data = [] |
| for row in preview_data: |
| wrapped_row = [Paragraph(cell, cell_style) for cell in row] |
| wrapped_data.append(wrapped_row) |
|
|
| |
| table = Table(wrapped_data, colWidths=col_widths, hAlign='LEFT') |
| table.setStyle(TableStyle([ |
| ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue), |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), |
| ('VALIGN', (0, 0), (-1, -1), 'TOP'), |
| ])) |
|
|
| elements.append(table) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| elements.append(Paragraph("<b>Dataset Statistics</b>", styles['Normal'])) |
| stat_table_data = [["Column", "Type", "Missing", "Mean", "Std"]] |
| for col in dataset_stats: |
| stat_table_data.append([ |
| col['name'], |
| col['type'], |
| str(col['missing']), |
| col['mean'], |
| col['std'] |
| ]) |
| stat_table = Table(stat_table_data, hAlign='LEFT') |
| stat_table.setStyle(TableStyle([ |
| ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), |
| ])) |
| elements.append(stat_table) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| elements.append(Paragraph("<b>Preprocessing Summary</b>", styles['Normal'])) |
| elements.append(Paragraph(preprocessing_summary, styles['Normal'])) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| elements.append(Paragraph(f"<b>Target Column:</b> {target_column}", styles['Normal'])) |
| elements.append(Spacer(1, 20)) |
|
|
| elements.append(PageBreak()) |
|
|
| |
| elements.append(Paragraph("2. Training Results", styles['Heading2'])) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| elements.append(Paragraph(f"- Detected Task Type: <b>{summary_results['task_type']}</b>", styles['Normal'])) |
| elements.append(Paragraph(f"- Best Model: <b>{summary_results['best_model']}</b>", styles['Normal'])) |
| elements.append(Paragraph(f"- Training Time: <b>{round(summary_results['train_time'], 2)} seconds</b>", styles['Normal'])) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| metrics_data = [["Metric", "Score"]] |
| for metric, data in summary_results['metrics_plot'].items(): |
| metrics_data.append([metric.upper(), f"{data['value']:.4f}"]) |
| metric_table = Table(metrics_data, hAlign='LEFT') |
| metric_table.setStyle(TableStyle([ |
| ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), |
| ])) |
| elements.append(Paragraph("<b>Evaluation Metrics</b>", styles['Normal'])) |
| elements.append(metric_table) |
| elements.append(Spacer(1, 20)) |
|
|
| |
| for metric, data in summary_results['metrics_plot'].items(): |
| score = round(data.get('value', 0.0), 4) |
| explanation = generate_explanation_plot(plot=None, name=metric, value=score, groq_api_key=groq_api_key) |
| if explanation: |
| elements.append(Paragraph(f"<b>{metric.upper()}:</b> {explanation}", styles['Normal'])) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| elements.append(Paragraph("3. Training Visualizations", styles['Heading2'])) |
| elements.append(Spacer(1, 12)) |
|
|
| |
| for metric, data in summary_results['metrics_plot'].items(): |
| |
| score = round(data.get('value', 0.0), 4) |
| elements.append(Paragraph(f"<b>{metric.upper()} Score</b>: {score}", styles['Normal'])) |
| |
| |
| for plot_type in ['plot', 'plot_hist']: |
| if plot_type in data: |
| image_data = base64.b64decode(data[plot_type]) |
| image_stream = io.BytesIO(image_data) |
| img = Image(image_stream, width=400, height=250) |
| elements.append(img) |
| elements.append(Spacer(1, 6)) |
|
|
| |
| explanation = generate_explanation_plot( |
| plot=data.get(plot_type), |
| name=f"{metric} ({plot_type})", |
| value=score, |
| groq_api_key=groq_api_key |
| ) |
| if explanation: |
| elements.append(Paragraph(explanation, styles['Normal'])) |
| elements.append(Spacer(1, 6)) |
|
|
| elements.append(Spacer(1, 18)) |
|
|
| |
| if 'feature_importance_plot' in summary_results: |
| elements.append(Paragraph("<b>Feature Importance</b>", styles['Normal'])) |
| feature_img_data = base64.b64decode(summary_results['feature_importance_plot']) |
| feature_img_stream = io.BytesIO(feature_img_data) |
| feature_img = Image(feature_img_stream, width=400, height=250) |
| elements.append(feature_img) |
|
|
| explanation = generate_explanation_plot(plot=summary_results['feature_importance_plot'], name="Feature Importance", value=None, groq_api_key=groq_api_key) |
| if explanation: |
| elements.append(Paragraph(explanation, styles['Normal'])) |
|
|
| elements.append(Spacer(1, 18)) |
| if shap_plot: |
| elements.append(Paragraph("<b>SHAP Summary Plot</b>", styles['Normal'])) |
| shap_img_data = base64.b64decode(shap_plot) |
| shap_img_stream = io.BytesIO(shap_img_data) |
| shap_img = Image(shap_img_stream, width=400, height=250) |
| elements.append(shap_img) |
|
|
| explanation = generate_explanation_plot(plot=shap_plot, name="SHAP Summary", value=None, groq_api_key=groq_api_key) |
| if explanation: |
| elements.append(Paragraph(explanation, styles['Normal'])) |
|
|
| |
| doc.build(elements) |
| buffer.seek(0) |
| return buffer |
|
|