Spaces:
Sleeping
Sleeping
| import os | |
| from smolagents import tool | |
| import spaces | |
| from dotenv import load_dotenv | |
| # ---------------------------------------------------------------------------- | |
| # SECTION 1: Vision & Multimodal | |
| # ---------------------------------------------------------------------------- | |
| load_dotenv() | |
| def initialize_gpu() : | |
| return 0 | |
| def vision_tool(prompt: str, image_list: list) -> str: | |
| """ | |
| Analyzes one or more images using a multimodal model to answer specific questions. | |
| It processes image content and returns the model's text-based response. | |
| Args: | |
| prompt: The user question or task to perform on the images. | |
| image_list: A list of PIL Image objects to be analyzed. | |
| """ | |
| import io, base64, os | |
| from smolagents import OpenAIServerModel | |
| from PIL import Image | |
| initialize_gpu() | |
| model = OpenAIServerModel( | |
| model_id='gemma4:31b-cloud', | |
| api_base='https://ollama.com/v1', | |
| api_key=os.getenv('GEMMA_API_KEY'), | |
| temperature=0.0, | |
| max_tokens=2048, | |
| ) | |
| payload = [{"type": "text", "text": prompt}] | |
| for img in image_list: | |
| try: | |
| buffered = io.BytesIO() | |
| img.save(buffered, format="JPEG") | |
| b64_img = base64.b64encode(buffered.getvalue()).decode("utf-8") | |
| payload.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64_img}"}}) | |
| except Exception as e: | |
| return f"Error processing image: {str(e)}" | |
| return model([{"role": "user", "content": payload}]).content | |
| # ---------------------------------------------------------------------------- | |
| # SECTION 2: YouTube Pipeline (Tout-en-un pour la robustesse) | |
| # ---------------------------------------------------------------------------- | |
| def ask_youtube_full_pipeline(url: str, question: str) -> str: | |
| """ | |
| Downloads a YouTube video, extracts key frames at regular intervals, and uses a multimodal model to answer questions about the video's content. | |
| Args: | |
| url: The YouTube video URL to process. | |
| question: The specific question to ask about the video content. | |
| """ | |
| import os, re, tempfile, yt_dlp, imageio | |
| from PIL import Image | |
| from smolagents import OpenAIServerModel | |
| initialize_gpu() | |
| # 1. Extraction ID interne | |
| patterns = [r"v=([a-zA-Z0-9_-]{11})", r"youtu\.be/([a-zA-Z0-9_-]{11})"] | |
| video_id = None | |
| for pat in patterns: | |
| match = re.search(pat, url) | |
| if match: | |
| video_id = match.group(1) | |
| break | |
| if not video_id: | |
| return "Erreur: URL invalide." | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| # Configuration optimisée : légère (évite les timeouts) et robuste | |
| ydl_opts = { | |
| 'format': 'best[ext=mp4]/best', # Récupère un fichier MP4 direct déjà fusionné | |
| 'outtmpl': os.path.join(tmpdir, 'downloaded_video.%(ext)s'), | |
| 'quiet': True, | |
| 'no_warnings': True | |
| } | |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
| ydl.extract_info(url, download=True) | |
| # CORRECTIF CRITIQUE : On liste le dossier pour trouver le fichier, peu importe son extension exacte | |
| downloaded_files = os.listdir(tmpdir) | |
| if not downloaded_files: | |
| return "Erreur: Le téléchargement de la vidéo a échoué." | |
| video_path = os.path.join(tmpdir, downloaded_files[0]) | |
| # Extraction des images via le chemin dynamique détecté | |
| reader = imageio.get_reader(video_path, format='ffmpeg') | |
| frames = [Image.fromarray(f) for i, f in enumerate(reader) if i % 60 == 0][:8] | |
| reader.close() | |
| # 2. Analyse VLM (Auto-contenu) | |
| model = OpenAIServerModel(model_id='gemma4:31b-cloud', api_base='https://ollama.com/v1', api_key=os.getenv('GEMMA_API_KEY')) | |
| import base64, io | |
| payload = [{"type": "text", "text": question}] | |
| for img in frames: | |
| b = io.BytesIO() | |
| img.save(b, format="JPEG") | |
| payload.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64.b64encode(b.getvalue()).decode()}"}}) | |
| return model([{"role": "user", "content": payload}]).content | |
| # ---------------------------------------------------------------------------- | |
| # SECTION 3: Fichiers & Audio | |
| # ---------------------------------------------------------------------------- | |
| def read_pdf_file(file_path: str) -> str: | |
| """ | |
| Reads the content of a PDF file, extracting all visible text page by page. | |
| Args: | |
| file_path: The absolute or relative path to the .pdf file that needs to be read. | |
| """ | |
| import os | |
| from pypdf import PdfReader | |
| initialize_gpu() | |
| if not os.path.exists(file_path): | |
| return f"Erreur : Le fichier au chemin '{file_path}' est introuvable." | |
| try: | |
| reader = PdfReader(file_path) | |
| full_text = [] | |
| # Parcourir et extraire le texte de chaque page | |
| for page_num, page in enumerate(reader.pages, start=1): | |
| page_text = page.extract_text() | |
| if page_text and page_text.strip(): | |
| full_text.append(f"\n--- [Page {page_num}] ---") | |
| full_text.append(page_text.strip()) | |
| output = "\n".join(full_text) | |
| return output if output.strip() else "Le document PDF semble vide ou ne contient que des images (scan)." | |
| except Exception as e: | |
| return f"Erreur lors de la lecture du fichier PDF : {str(e)}" | |
| def read_docx_file(file_path: str) -> str: | |
| """ | |
| Reads the content of a Word document (.docx file), extracting all text from paragraphs and tables. | |
| Args: | |
| file_path: The path to the .docx file that needs to be read. | |
| """ | |
| import docx | |
| import os | |
| initialize_gpu() | |
| if not os.path.exists(file_path): | |
| return f"Erreur : Le fichier au chemin '{file_path}' est introuvable." | |
| try: | |
| doc = docx.Document(file_path) | |
| full_text = [] | |
| # 1. Extraction du texte des paragraphes classiques | |
| for para in doc.paragraphs: | |
| if para.text.strip(): | |
| full_text.append(para.text.strip()) | |
| # 2. Extraction du texte des tableaux (très fréquent dans GAIA) | |
| for table in doc.tables: | |
| full_text.append("\n--- [Tableau détecté] ---") | |
| for row in table.rows: | |
| row_text = [cell.text.strip() for cell in row.cells if cell.text.strip()] | |
| if row_text: | |
| # On sépare les colonnes par des barres verticales pour l'agent | |
| full_text.append(" | ".join(row_text)) | |
| full_text.append("-------------------------\n") | |
| # Renvoyer le tout proprement assemblé | |
| output = "\n".join(full_text) | |
| return output if output.strip() else "Le document est vide." | |
| except Exception as e: | |
| return f"Erreur lors de la lecture du fichier .docx : {str(e)}" | |
| def file_from_url(url: str, save_as: str = "downloaded_file") -> str: | |
| """ | |
| Downloads a file from a provided URL, determines its extension properly, and saves it. | |
| Args: | |
| url: The source URL of the file to download. | |
| save_as: Optional filename to save the file as. If not provided, derives the name from the URL or headers. | |
| """ | |
| import requests | |
| import os | |
| from urllib.parse import urlparse | |
| import mimetypes | |
| # 1. Utiliser un répertoire local dédié et contrôlé (comme ton dossier downloads) | |
| download_dir = os.path.abspath("downloads") | |
| os.makedirs(download_dir, exist_ok=True) | |
| try: | |
| with requests.get(url, stream=True, timeout=20) as r: | |
| r.raise_for_status() | |
| # 2. Détermination intelligente du nom et de l'extension | |
| if save_as == "downloaded_file": | |
| base_name = os.path.basename(urlparse(url).path) | |
| if not base_name or "." not in base_name: | |
| # Si l'URL est opaque, on regarde le Content-Type envoyé par le serveur | |
| content_type = r.headers.get('content-type', '').split(';')[0] | |
| ext = mimetypes.guess_extension(content_type) or ".txt" | |
| base_name = f"downloaded_media_{int(os.path.getctime(download_dir))}{ext}" | |
| save_as = base_name | |
| file_path = os.path.join(download_dir, save_as) | |
| # 3. Écriture par blocs (chunks) | |
| with open(file_path, 'wb') as f: | |
| for chunk in r.iter_content(chunk_size=8192): | |
| if chunk: | |
| f.write(chunk) | |
| # Renvoie le chemin absolu impeccable pour tes autres sous-agents | |
| return os.path.abspath(file_path) | |
| except Exception as e: | |
| return f"Erreur lors du téléchargement du fichier depuis l'URL : {str(e)}" | |
| def transcribe_youtube(yt_url: str) -> str: | |
| """ | |
| Downloads the audio from a YouTube video and performs speech-to-text transcription using the Groq cloud API (Whisper). | |
| Useful for retrieving text content or answering questions based on video dialogue without local heavy models. | |
| Args: | |
| yt_url: The URL of the YouTube video to transcribe. | |
| """ | |
| import os, tempfile, yt_dlp, requests | |
| groq_api_key = os.getenv("GROQ_API_KEY") | |
| if not groq_api_key: | |
| return "Erreur : La variable d'environnement GROQ_API_KEY n'est pas configurée dans l'environnement." | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| # Téléchargement de l'audio uniquement | |
| ydl_opts = { | |
| "format": "bestaudio", | |
| "outtmpl": os.path.join(tmpdir, "audio.%(ext)s"), | |
| "quiet": True, | |
| "postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "mp3"}] | |
| } | |
| try: | |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
| ydl.extract_info(yt_url, download=True) | |
| audio_path = os.path.join(tmpdir, "audio.mp3") | |
| # Appel API Groq Cloud (Whisper Large V3) | |
| url = "https://api.groq.com/openai/v1/audio/transcriptions" | |
| headers = {"Authorization": f"Bearer {groq_api_key}"} | |
| with open(audio_path, 'rb') as f: | |
| files = {"file": f} | |
| data = {"model": "whisper-large-v3"} | |
| response = requests.post(url, headers=headers, files=files, data=data) | |
| if response.status_code != 200: | |
| return f"Erreur API Groq : {response.text}" | |
| return response.json().get("text", "") | |
| except Exception as e: | |
| return f"Erreur de traitement audio / API : {str(e)}" | |
| def read_text_file(file_path: str) -> str: | |
| """ | |
| Reads and returns the raw plain text content from a specified local file path. | |
| Args: | |
| file_path: The full local path to the text file. | |
| """ | |
| with open(file_path, "r", encoding="utf-8") as f: return f.read() | |
| # ---------------------------------------------------------------------------- | |
| # SECTION 4: OCR & Tabulaire | |
| # ---------------------------------------------------------------------------- | |
| def extract_text_via_ocr(image_path: str) -> str: | |
| """ | |
| Extracts text from an image using the cloud-based OCR.space API. | |
| Returns the detected text found within the provided image path without heavy local dependencies. | |
| Args: | |
| image_path: The local path to the image file containing text. | |
| """ | |
| import requests, os | |
| # Utilisation de la clé publique 'helloworld' d'OCR.space si aucune clé n'est configurée | |
| api_key = os.getenv("OCR_SPACE_API_KEY", "helloworld") | |
| try: | |
| with open(image_path, 'rb') as f: | |
| response = requests.post( | |
| 'https://api.ocr.space/parse/image', | |
| files={'file': f}, | |
| data={'apikey': api_key, 'language': 'fra'} | |
| ) | |
| result = response.json() | |
| if result.get("IsErroredOnProcessing"): | |
| return f"Erreur OCR.space : {result.get('ErrorMessage')}" | |
| parsed_results = result.get('ParsedResults', []) | |
| if not parsed_results: | |
| return "Aucun texte détecté sur l'image." | |
| return parsed_results[0].get('ParsedText', '') | |
| except Exception as e: | |
| return f"Erreur lors de l'appel API OCR : {str(e)}" | |
| def summarize_csv_data(path: str, query: str = "") -> str: | |
| """ | |
| Loads a CSV file into a pandas DataFrame and generates a descriptive summary, including column statistics. | |
| Optionally allows filtering rows via a pandas query string. | |
| Args: | |
| path: The local file path to the CSV file. | |
| query: Optional pandas query expression to filter rows before summarizing (e.g., "age > 30"). | |
| """ | |
| import pandas as pd | |
| df = pd.read_csv(path) | |
| if query: df = df.query(query) | |
| return df.describe().to_string() | |
| def calculer_probabilite_loi(loi: str, parametres: dict, valeur_k: float) -> str: | |
| """ | |
| Calculates the exact or cumulative probability for a specified statistical distribution law (Normal or Binomial). | |
| Args: | |
| loi: The distribution type, either 'normale' or 'binomiale'. | |
| parametres: Dictionary containing distribution parameters (e.g., {'moyenne': float, 'ecart_type': float} for Normal). | |
| valeur_k: The value or bound for which the cumulative probability P(X <= k) is calculated. | |
| """ | |
| # Import local obligatoire pour l'autonomie en sandbox | |
| from scipy import stats | |
| try: | |
| if loi.lower() == "normale": | |
| loc = parametres.get("moyenne", 0) | |
| scale = parametres.get("ecart_type", 1) | |
| prob = stats.norm.cdf(valeur_k, loc=loc, scale=scale) | |
| return f"Pour une Loi Normale({loc}, {scale}), P(X <= {valeur_k}) = {prob:.6f}" | |
| elif loi.lower() == "binomiale": | |
| n = int(parametres.get("n", 1)) | |
| p = float(parametres.get("p", 0.5)) | |
| prob = stats.binom.cdf(valeur_k, n, p) | |
| prob_exacte = stats.binom.pmf(valeur_k, n, p) | |
| return ( | |
| f"Pour une Loi Binomiale(n={n}, p={p}) :\n" | |
| f"- Probabilité exacte P(X = {valeur_k}) = {prob_exacte:.6f}\n" | |
| f"- Probabilité cumulative P(X <= {valeur_k}) = {prob:.6f}" | |
| ) | |
| else: | |
| return "Loi non supportée. Utilisez 'normale' ou 'binomiale'." | |
| except Exception as e: | |
| return f"Erreur de paramètres : {str(e)}" | |
| def summarize_excel_data(path: str, query: str = "") -> str: | |
| """ | |
| Loads an Excel file (.xls or .xlsx) into a pandas DataFrame and generates a descriptive summary, including column statistics. | |
| Optionally allows filtering rows via a pandas query string. | |
| Args: | |
| path: The local file path to the Excel file. | |
| query: Optional pandas query expression to filter rows before summarizing (e.g., "age > 30"). | |
| """ | |
| import pandas as pd | |
| df = pd.read_excel(path) | |
| if query: df = df.query(query) | |
| return df.describe().to_string() | |
| def analyser_serie_statistique(donnees: list[float]) -> str: | |
| """ | |
| Calculates essential descriptive statistics for a provided series of numerical data, such as mean, median, standard deviation, and range. | |
| Args: | |
| donnees: A list of floats representing the dataset to analyze. | |
| """ | |
| import numpy as np | |
| arr = np.array(donnees) | |
| return f"Moyenne: {np.mean(arr):.2f}, Écart-type: {np.std(arr):.2f}, Min: {np.min(arr)}, Max: {np.max(arr)}" | |
| # ---------------------------------------------------------------------------- | |
| # SECTION 5: Maths & Chess | |
| # ---------------------------------------------------------------------------- | |
| def resoudre_calcul_formel(expression: str, action: str, variable: str = "x") -> str: | |
| """ | |
| Performs formal mathematical computations such as solving equations, differentiation, integration, and simplification using sympy. | |
| Args: | |
| expression: The mathematical expression in Python syntax (e.g., "x**2 + 2*x - 3" or "x**2 - 4 = 0"). | |
| action: The mathematical operation to perform ('resoudre', 'deriver', 'integrer', or 'simplifier'). | |
| variable: The primary variable in the expression to operate on. Defaults to 'x'. | |
| """ | |
| import sympy as sp | |
| v = sp.Symbol(variable) | |
| e = sp.sympify(expression.split("=")[0]) if "=" in expression else sp.sympify(expression) | |
| if action == "simplifier": return str(sp.simplify(e)) | |
| if action == "deriver": return str(sp.diff(e, v)) | |
| if action == "integrer": return str(sp.integrate(e, v)) | |
| return str(sp.solve(e, v)) | |
| def analyze_chess_position(fen: str) -> str: | |
| """ | |
| Analyzes a chess board state from a FEN (Forsyth-Edwards Notation) string. | |
| Detects checks, checkmates, stalemates, lists legal moves, and scans for immediate 'Mate in 1' opportunities. | |
| Args: | |
| fen: The FEN string representing the current board state. | |
| """ | |
| import chess | |
| board = chess.Board(fen) | |
| moves = [board.san(m) for m in board.legal_moves] | |
| return f"Turn: {'White' if board.turn else 'Black'}. Moves: {', '.join(moves)}" | |
| def get_stockfish_evaluation(fen: str) -> str: | |
| """ | |
| Calls an external Chess API to get a deep engine evaluation (Stockfish 18) for a given FEN string. | |
| It returns the absolute best move, tactical score, winning chances, and the expected continuation line. | |
| Args: | |
| fen: The FEN string representing the current board state to analyze. | |
| """ | |
| import requests | |
| r = requests.post("https://chess-api.com/v1", json={"fen": fen, "depth": 12}) | |
| d = r.json() | |
| return f"Best: {d.get('san')}, Eval: {d.get('eval')}" |