From 4525a82f4671b8ff395cf8264c41b7471ea1e006 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 4 Jan 2026 22:51:30 +0100 Subject: [PATCH] =?UTF-8?q?ajout=20des=20caract=C3=A9ristiques=20technique?= =?UTF-8?q?=20du=20LLM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 ++++++++---- main.py | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 141 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e51c665..d1670a7 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,18 @@ Ce projet permet de traduire un document PDF page par page en utilisant un modè --- ## Création d'un modèle LLM de traduction avec Ollama -En partant de zongwei/gemma3-translator:4b, voici le fichier de customisation : +En partant du LLM [zongwei/gemma3-translator:4b](https://ollama.com/zongwei/gemma3-translator), nous allons créer un modèle optimisé pour la traduction avec Ollama. +Pour info : Inutile de le downloader, il le serra automatiquement au lancement de la commande. + ```shell FROM zongwei/gemma3-translator:4b PARAMETER temperature 0.1 PARAMETER num_ctx 131072 SYSTEM """ -Tu es un traducteur professionnel spécialisé dans la traduction de texte ukrainien vers le français. -Traduis fidèlement et naturellement en respectant l'intonation originale utilisée par l'auteur du texte. -Tu ne dois pas interpréter les pensées ou les réflexions de l'auteur. -Ne rajoutes pas de texte avant ou après le texte fourni. -Tu dois toujours répondre en français. +You are a professional translator specialising in translating Ukrainian text into English. +Translate accurately and naturally, respecting the original intonation used by the author of the text. +You must not interpret the author's thoughts or reflections. +Do not add any text before or after the text provided. """ ``` Il faut ensuite compiler le modèle avec la commande : @@ -64,10 +65,22 @@ ollama create traductionUkrainienVersFrancais -f .\Modelfile ```bash ollama list ``` - Vous devez voir `traductionUkrainienVersFrancais:latest` dans la liste. - Si ce n'est pas le cas, vous devez le générer en suivant la drescription vue plus haut (Création d'un modèle LLM de traduction avec Ollama) + Vous devez voir `traductionUkrainienVersFrancais` dans la liste. + + Si ce n'est pas le cas, vous devez le générer comme décrit plus haut (paragraphe "Création d'un modèle LLM de traduction avec Ollama") 3. **Placer votre PDF** dans le même répertoire que le script `main.py` +### Paramétrage du script +`PDF_PATH`= "TaniaBorecMemoir(Ukr).pdf" <- Le nom du fichier pdf à traduire. +`OLLAMA_MODEL` = "mitmul/plamo-2-translate:latest" <- Le nom +`OLLAMA_URL` = "http://localhost:11434/api/generate" <- URL par défaut d'Ollama +`TARGET_LANGUAGE` = "français" <- Langue cible (ex: "français", "anglais", "allemand", "espagnol", etc.) +`SYSTEM_PROMPT` = """You are a professional translator specialising in Ukrainian text translation. +Translate accurately and naturally, respecting the original intonation used by the author of the text. +You must not interpret the author's thoughts or reflections. +Do not add any text before or after the text provided. +Preserve the layout and structure of the original text.""" + ### Exécution diff --git a/main.py b/main.py index 2252919..a1f3635 100644 --- a/main.py +++ b/main.py @@ -14,7 +14,109 @@ import os PDF_PATH = "TaniaBorecMemoir(Ukr).pdf" # Fichier original OLLAMA_MODEL = "traductionUkrainienVersFrancais:latest" OLLAMA_URL = "http://localhost:11434/api/generate" # URL par défaut d'Ollama -OUTPUT_PDF_PATH = PDF_PATH.replace(".pdf", " (FR).pdf") # Chemin du PDF de sortie +TARGET_LANGUAGE = "français" # Langue cible (ex: "français", "anglais", "allemand", "espagnol", etc.) +OUTPUT_PDF_PATH = PDF_PATH.replace(".pdf", f" ({TARGET_LANGUAGE.upper()[:2]}).pdf") # Chemin du PDF de sortie + +# Prompt système personnalisé (instructions pour le LLM) +SYSTEM_PROMPT = """""" + +def extract_parameters_from_template(template_str): + """Extrait les paramètres du modèle à partir du template.""" + import re + + parameters = {} + + if not template_str or not isinstance(template_str, str): + return parameters + + # Si la chaîne contient "parameters:", récupère ce qui suit + if 'parameters:' in template_str: + params_section = template_str.split('parameters:', 1)[1] + else: + # Sinon, utilise la chaîne directement (elle contient déjà les paramètres) + params_section = template_str + + # Parse les lignes de paramètres + # Format: "stop """ + # "temperature 0.1" + lines = params_section.split('\n') + + for line in lines: + if not line.strip(): + continue + + # Divise par le premier groupe d'espaces blancs + # Cela sépare la clé des valeurs avec leurs espaces + parts = line.split(None, 1) # split() avec maxsplit=1 divise sur les espaces + + if len(parts) == 2: + param_name = parts[0].strip() + param_value = parts[1].strip() + parameters[param_name] = param_value + + return parameters + +def get_llm_model_info(model=OLLAMA_MODEL): + """Extrait les informations du modèle LLM depuis Ollama.""" + try: + # URL pour obtenir les informations du modèle + info_url = OLLAMA_URL.replace("/api/generate", "/api/show") + payload = {"name": model} + + response = requests.post(info_url, json=payload) + + if response.status_code == 200: + model_data = response.json() + + # Gère le cas où model_data est une chaîne + if isinstance(model_data, str): + model_data = json.loads(model_data) + + # Extrait les paramètres du template + parameters = model_data.get('parameters', '') + parsed_params = extract_parameters_from_template(parameters) + + # Extraction des informations principales + info = { + "temperature": parsed_params.get('temperature', model_data.get("temperature", "Not available")), + "num_ctx": parsed_params.get('num_ctx', "Not available"), + "top_k": parsed_params.get('top_k', "Not available"), + "top_p": parsed_params.get('top_p', "Not available"), + "system": model_data.get("system", "Not available"), + "modified_at": model_data.get("modified_at", "Not available"), + } + + return info + else: + print(f"Erreur lors de la récupération du modèle : {response.text}") + return None + except Exception as e: + print(f"Erreur lors de l'accès aux informations du modèle : {e}") + return None + +def display_llm_info(): + """Retourne les informations du modèle LLM formatées.""" + from datetime import datetime + + info = get_llm_model_info(OLLAMA_MODEL) + + if info: + # Formate la date en jj/mm/AAAA + modified_at = info.get('modified_at', 'Not available') + if modified_at and modified_at != 'Not available': + try: + # Parse la date ISO + date_obj = datetime.fromisoformat(modified_at.replace('Z', '+00:00')) + # Formate en jj/mm/AAAA + formatted_date = date_obj.strftime("%d/%m/%Y") + except: + formatted_date = modified_at + else: + formatted_date = 'Not available' + + return f"LLM Modèle: {OLLAMA_MODEL}
\nSystem: {info['system']}
\nTemperature: {info['temperature']}
\nDate de modification: {formatted_date}" + else: + return "Informations du modèle non disponibles" def extract_text_from_pdf(pdf_path): """Extrait le texte page par page d'un PDF sans les numéros de pages.""" @@ -67,11 +169,13 @@ def merge_paragraphs_across_pages(pages_text): return paragraphs -def send_to_ollama(prompt, model=OLLAMA_MODEL, context_size=128000): - """Envoie une requête à Ollama et retourne la réponse.""" +def send_to_ollama(text, target_lang=TARGET_LANGUAGE, model=OLLAMA_MODEL, context_size=128000, system_prompt=SYSTEM_PROMPT): + """Envoie une requête à Ollama et retourne la réponse traduite.""" + # Construit le prompt avec les instructions système et la demande de traduction + full_prompt = f"{system_prompt}\n\nTraduis le texte suivant de l'ukrainien vers le {target_lang} :\n{text}" payload = { "model": model, - "prompt": prompt, + "prompt": full_prompt, "stream": False, "options": {"num_ctx": context_size} } @@ -141,8 +245,9 @@ def create_pdf_from_results(results, output_path): fontName=font_name ) - # Titre - story.append(Paragraph("Traduction - Ukrainien vers Français", title_style)) + # Titre avec la langue cible + title_text = f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}" + story.append(Paragraph(title_text, title_style)) story.append(Spacer(1, 0.2*inch)) # Contenu @@ -152,11 +257,18 @@ def create_pdf_from_results(results, output_path): story.append(Paragraph(formatted_text, body_style)) story.append(Spacer(1, 0.1*inch)) + # Infos sur le LLM + story.append(Spacer(1, 0.2*inch)) + story.append(Paragraph(display_llm_info(), page_style)) + # Construction du PDF doc.build(story) print(f"PDF généré avec succès : {output_path}") def main(): + # Affiche les informations du modèle LLM + display_llm_info() + # Extraction du texte page par page pages = extract_text_from_pdf(PDF_PATH) print(f"Nombre de pages extraites : {len(pages)}") @@ -170,10 +282,9 @@ def main(): # Traitement des paragraphes complets for i, paragraph_text in enumerate(paragraphs, start=1): - print(f"{15 * '-'} Traduction du paragraphe {i}/{len(paragraphs)}...\n{paragraph_text}\n") - prompt = f"Traduis le texte suivant de l'ukrainien vers le français : {paragraph_text}" + print(f"{15 * '-'} Traduction du paragraphe {i}/{len(paragraphs)}...") try: - result = send_to_ollama(prompt) + result = send_to_ollama(paragraph_text, target_lang=TARGET_LANGUAGE) print(f"{result}.") results[i] = result except Exception as e: