Update UI language handling and improve .gitignore for Python artifacts
This commit is contained in:
parent
cb43b1176f
commit
9d142c269d
8
.gitignore
vendored
8
.gitignore
vendored
@ -1 +1,7 @@
|
||||
apigit.txt
|
||||
apigit.txt
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
# Dossier de cache
|
||||
__pycache__/
|
||||
44
app.py
44
app.py
@ -1,22 +1,59 @@
|
||||
# filepath: f:\Dev\Rag\chat_bot_rag\app.py
|
||||
|
||||
import gradio as gr
|
||||
from config.settings import DEFAULT_MODEL, QDRANT_COLLECTION_NAME, AVAILABLE_MODELS
|
||||
from services.rag_service import initialize_rag_bot
|
||||
from components.chatbot import process_query, reset_conversation, change_model, change_collection
|
||||
from components.ui import build_interface, update_ui_language_elements
|
||||
from translations.lang_mappings import UI_TRANSLATIONS, UI_SUPPORTED_LANGUAGES, LANGUAGE_MAPPING
|
||||
|
||||
def update_ui_language(language):
|
||||
"""Fonction pour mettre à jour la langue de l'interface utilisateur"""
|
||||
if language not in UI_SUPPORTED_LANGUAGES:
|
||||
language = "Français" # Langue par défaut
|
||||
|
||||
# Récupérer les traductions pour la langue sélectionnée
|
||||
translations = UI_TRANSLATIONS[language]
|
||||
|
||||
# Afficher un message de débogage
|
||||
print(f"Mise à jour de la langue UI : {language}")
|
||||
print(f"AVAILABLE_MODELS : {AVAILABLE_MODELS}")
|
||||
|
||||
# Retourner les valeurs mises à jour pour tous les éléments de l'interface
|
||||
return [
|
||||
f"# {translations['title']}", # Titre
|
||||
gr.update(placeholder=translations["placeholder"]), # Placeholder du message
|
||||
gr.update(value=translations["send_btn"]), # Texte du bouton d'envoi
|
||||
gr.update(value=translations["clear_btn"]), # Texte du bouton d'effacement
|
||||
gr.update(label=translations["ui_language_label"], info=translations["ui_language_info"]), # Label sélecteur langue UI
|
||||
|
||||
# IMPORTANT : Conserver les choices=AVAILABLE_MODELS ici
|
||||
gr.update(label=translations["model_selector"], info=translations["model_info"], choices=AVAILABLE_MODELS),
|
||||
|
||||
f"{translations['model_current']}: **{DEFAULT_MODEL}**", # Statut du modèle
|
||||
gr.update(label=translations["language_selector"], info=translations["language_info"], choices=list(LANGUAGE_MAPPING.keys())), # Langue réponses
|
||||
gr.update(label=translations["collection_input"], info=translations["collection_info"]), # Label du champ de collection
|
||||
f"{translations['collection_current']}: **{QDRANT_COLLECTION_NAME}**", # Statut de la collection
|
||||
gr.update(value=translations["apply_btn"]), # Texte du bouton d'application
|
||||
gr.update(label=translations["streaming_label"], info=translations["streaming_info"]), # Label du mode streaming
|
||||
gr.update(label=translations["sources_label"]), # Label de l'affichage des sources
|
||||
gr.update(label=translations["max_images_label"]), # Label du nombre max d'images
|
||||
f"### {translations['images_title']}", # Titre des images
|
||||
f"### {translations['tables_title']}" # Titre des tableaux
|
||||
]
|
||||
|
||||
def main():
|
||||
"""Main entry point for the chatbot application"""
|
||||
# Initialize the RAG chatbot
|
||||
initialize_rag_bot()
|
||||
|
||||
# Construire l'interface
|
||||
# Dans app.py, corriger l'appel à build_interface
|
||||
interface = build_interface(
|
||||
process_query_fn=process_query,
|
||||
reset_conversation_fn=reset_conversation,
|
||||
change_model_fn=change_model,
|
||||
change_collection_fn=change_collection,
|
||||
update_ui_language_fn=update_ui_language_elements # Ajout du paramètre manquant
|
||||
update_ui_language_fn=update_ui_language # Utiliser update_ui_language, pas update_ui_language_elements
|
||||
)
|
||||
|
||||
# Lancer l'appli Gradio
|
||||
@ -28,4 +65,5 @@ def main():
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
|
||||
@ -155,9 +155,36 @@ def change_collection(collection_name, language="Français"):
|
||||
return f"❌ Erreur: {str(e)}"
|
||||
|
||||
# Fonction de traitement de requête
|
||||
def convert_to_messages_format(history):
|
||||
"""Convertit différents formats d'historique au format messages."""
|
||||
messages = []
|
||||
|
||||
# Vérifier si nous avons déjà le format messages
|
||||
if history and isinstance(history[0], dict) and "role" in history[0]:
|
||||
return history
|
||||
|
||||
# Format tuples [(user_msg, assistant_msg), ...]
|
||||
try:
|
||||
for item in history:
|
||||
if isinstance(item, tuple) and len(item) == 2:
|
||||
user_msg, assistant_msg = item
|
||||
messages.append({"role": "user", "content": user_msg})
|
||||
if assistant_msg: # Éviter les messages vides
|
||||
messages.append({"role": "assistant", "content": assistant_msg})
|
||||
except ValueError:
|
||||
# Journaliser l'erreur pour le débogage
|
||||
print(f"Format d'historique non reconnu: {history}")
|
||||
# Retourner un historique vide en cas d'erreur
|
||||
return []
|
||||
|
||||
return messages
|
||||
|
||||
def process_query(message, history, streaming, show_sources, max_images, language):
|
||||
global current_images, current_tables
|
||||
|
||||
# Debug plus clair
|
||||
print(f"Langue sélectionnée pour la réponse: {language} -> {LANGUAGE_MAPPING.get(language, 'français')}")
|
||||
|
||||
if not message.strip():
|
||||
return history, "", None, None
|
||||
|
||||
@ -168,8 +195,10 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
||||
|
||||
try:
|
||||
if streaming:
|
||||
# Version avec streaming dans Gradio
|
||||
history = history + [(message, "")]
|
||||
# Convertir history en format messages pour l'affichage
|
||||
messages_history = convert_to_messages_format(history)
|
||||
messages_history.append({"role": "user", "content": message})
|
||||
messages_history.append({"role": "assistant", "content": ""})
|
||||
|
||||
# 1. Récupérer les documents pertinents
|
||||
docs = rag_bot._retrieve_relevant_documents(message)
|
||||
@ -180,50 +209,39 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
||||
|
||||
# 3. Préparer le prompt
|
||||
prompt_template = ChatPromptTemplate.from_template("""
|
||||
Tu es un assistant documentaire spécialisé qui utilise toutes les informations disponibles dans le contexte fourni.
|
||||
Tu es un assistant documentaire spécialisé qui utilise le contexte fourni.
|
||||
|
||||
TRÈS IMPORTANT: Tu dois répondre EXCLUSIVEMENT en {language}. Ne réponds JAMAIS dans une autre langue.
|
||||
===== INSTRUCTION CRUCIALE SUR LA LANGUE =====
|
||||
RÉPONDS UNIQUEMENT EN {language}. C'est une exigence ABSOLUE.
|
||||
NE RÉPONDS JAMAIS dans une autre langue que {language}, quelle que soit la langue de la question.
|
||||
==============================================
|
||||
|
||||
Instructions spécifiques:
|
||||
1. Pour chaque image mentionnée dans le contexte, inclue TOUJOURS dans ta réponse:
|
||||
- La légende/caption exacte de l'image
|
||||
- La source et le numéro de page
|
||||
- Une description brève de ce qu'elle montre
|
||||
|
||||
2. Pour chaque tableau mentionné dans le contexte, inclue TOUJOURS:
|
||||
- Le titre/caption exact du tableau
|
||||
- La source et le numéro de page
|
||||
- Ce que contient et signifie le tableau
|
||||
|
||||
3. Lorsque tu cites des équations mathématiques:
|
||||
- Utilise la syntaxe LaTeX exacte comme dans le document ($...$ ou $$...$$)
|
||||
- Reproduis-les fidèlement sans modification
|
||||
|
||||
4. IMPORTANT: Ne pas inventer d'informations - si une donnée n'est pas explicitement fournie dans le contexte,
|
||||
indique clairement que cette information n'est pas disponible dans les documents fournis.
|
||||
|
||||
5. Cite précisément les sources pour chaque élément d'information (format: [Source, Page]).
|
||||
|
||||
6. CRUCIAL: Ta réponse doit être UNIQUEMENT et INTÉGRALEMENT en {language}, quelle que soit la langue de la question.
|
||||
1. Pour chaque image mentionnée: inclure la légende, source, page et description
|
||||
2. Pour chaque tableau: inclure titre, source, page et signification
|
||||
3. Pour les équations: utiliser la syntaxe LaTeX exacte
|
||||
4. Ne pas inventer d'informations hors du contexte fourni
|
||||
5. Citer précisément les sources
|
||||
|
||||
Historique de conversation:
|
||||
{chat_history}
|
||||
|
||||
Contexte (à utiliser pour répondre):
|
||||
Contexte:
|
||||
{context}
|
||||
|
||||
Question: {question}
|
||||
|
||||
Réponds de façon structurée et précise en intégrant activement les images, tableaux et équations disponibles dans le contexte.
|
||||
Ta réponse doit être exclusivement en {language}.
|
||||
Réponds de façon structurée en intégrant les images, tableaux et équations disponibles.
|
||||
TA RÉPONSE DOIT ÊTRE UNIQUEMENT ET ENTIÈREMENT EN {language}. CETTE RÈGLE EST ABSOLUE.
|
||||
""")
|
||||
|
||||
# 4. Formater les messages pour le LLM
|
||||
# Assurer que la langue est bien passée dans le format du prompt
|
||||
selected_language = LANGUAGE_MAPPING.get(language, "français")
|
||||
messages = prompt_template.format_messages(
|
||||
chat_history=history_text,
|
||||
context=context,
|
||||
question=message,
|
||||
language=LANGUAGE_MAPPING.get(language, "français")
|
||||
language=selected_language
|
||||
)
|
||||
|
||||
# 5. Créer un handler de streaming personnalisé
|
||||
@ -255,8 +273,9 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
||||
|
||||
# Nettoyer la réponse uniquement pour l'affichage (pas pour l'historique interne)
|
||||
clean_response = clean_llm_response(partial_response)
|
||||
history[-1] = (message, clean_response)
|
||||
yield history, "", None, None
|
||||
# Mettre à jour le dernier message (assistant)
|
||||
messages_history[-1]["content"] = clean_response
|
||||
yield messages_history, "", None, None
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
@ -310,17 +329,25 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
||||
# 13. Retourner les résultats finaux
|
||||
images_display = display_images()
|
||||
tables_display = display_tables()
|
||||
yield history, source_info, images_display, tables_display
|
||||
yield messages_history, source_info, images_display, tables_display
|
||||
|
||||
else:
|
||||
# Version sans streaming
|
||||
print("Mode non-streaming activé")
|
||||
source_info = ""
|
||||
|
||||
result = rag_bot.chat(message, stream=False)
|
||||
result = rag_bot.chat(
|
||||
message,
|
||||
stream=False,
|
||||
language=LANGUAGE_MAPPING.get(language, "français") # Vérifiez que cette ligne existe
|
||||
)
|
||||
# Nettoyer la réponse des balises <think>
|
||||
result["response"] = clean_llm_response(result["response"])
|
||||
history = history + [(message, result["response"])]
|
||||
|
||||
# Convertir l'historique au format messages
|
||||
messages_history = convert_to_messages_format(history)
|
||||
messages_history.append({"role": "user", "content": message})
|
||||
messages_history.append({"role": "assistant", "content": result["response"]})
|
||||
|
||||
# Mise à jour de l'historique interne
|
||||
rag_bot.chat_history.append({"role": "user", "content": message})
|
||||
@ -364,7 +391,7 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
||||
"description": table.get("description", "")
|
||||
})
|
||||
|
||||
yield history, source_info, display_images(), display_tables()
|
||||
yield messages_history, source_info, display_images(), display_tables()
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Une erreur est survenue: {str(e)}"
|
||||
@ -382,4 +409,5 @@ def reset_conversation():
|
||||
|
||||
rag_bot.clear_history()
|
||||
|
||||
# Retourner une liste vide au format messages
|
||||
return [], "", None, None
|
||||
184
components/ui.py
184
components/ui.py
@ -1,11 +1,58 @@
|
||||
import gradio as gr
|
||||
from config.settings import DEFAULT_MODEL, QDRANT_COLLECTION_NAME, AVAILABLE_MODELS
|
||||
from translations.lang_mappings import UI_TRANSLATIONS, UI_SUPPORTED_LANGUAGES
|
||||
from translations.lang_mappings import UI_TRANSLATIONS, UI_SUPPORTED_LANGUAGES, LANGUAGE_MAPPING
|
||||
from utils.katex_script import KATEX_CSS_JS
|
||||
|
||||
def update_ui_language_elements(language):
|
||||
"""Met à jour les éléments de l'interface utilisateur en fonction de la langue sélectionnée"""
|
||||
pass # Implémentez selon vos besoins
|
||||
"""Met à jour tous les éléments de l'interface avec la langue sélectionnée"""
|
||||
|
||||
# Vérifier si la langue est supportée par l'interface
|
||||
if language not in UI_SUPPORTED_LANGUAGES:
|
||||
language = "Français" # Langue par défaut
|
||||
|
||||
# Récupérer les traductions pour la langue sélectionnée
|
||||
translations = UI_TRANSLATIONS[language]
|
||||
|
||||
# Créer un dictionnaire pour stocker tous les éléments modifiés
|
||||
ui_elements = {}
|
||||
|
||||
# Mettre à jour le titre
|
||||
ui_elements["title"] = translations["title"]
|
||||
|
||||
# Mettre à jour le placeholder et les boutons
|
||||
ui_elements["placeholder"] = translations["placeholder"]
|
||||
ui_elements["send_btn"] = translations["send_btn"]
|
||||
ui_elements["clear_btn"] = translations["clear_btn"]
|
||||
|
||||
# Ajouter les traductions pour la langue de l'interface
|
||||
ui_elements["ui_language_label"] = translations["ui_language_label"]
|
||||
ui_elements["ui_language_info"] = translations["ui_language_info"]
|
||||
|
||||
# Mettre à jour les libellés des options
|
||||
ui_elements["options_label"] = "Options" # Ce texte pourrait aussi être traduit
|
||||
ui_elements["model_label"] = translations["model_selector"]
|
||||
ui_elements["model_info"] = translations["model_info"]
|
||||
ui_elements["model_current_prefix"] = translations["model_current"]
|
||||
|
||||
ui_elements["language_label"] = translations["language_selector"]
|
||||
ui_elements["language_info"] = translations["language_info"]
|
||||
|
||||
ui_elements["collection_label"] = translations["collection_input"]
|
||||
ui_elements["collection_info"] = translations["collection_info"]
|
||||
ui_elements["collection_current_prefix"] = translations["collection_current"]
|
||||
ui_elements["apply_btn"] = translations["apply_btn"]
|
||||
|
||||
ui_elements["streaming_label"] = translations["streaming_label"]
|
||||
ui_elements["streaming_info"] = translations["streaming_info"]
|
||||
ui_elements["sources_label"] = translations["sources_label"]
|
||||
ui_elements["max_images_label"] = translations["max_images_label"]
|
||||
|
||||
ui_elements["images_title"] = translations["images_title"]
|
||||
ui_elements["tables_title"] = translations["tables_title"]
|
||||
|
||||
return ui_elements
|
||||
|
||||
|
||||
|
||||
def build_interface(
|
||||
process_query_fn,
|
||||
@ -14,102 +61,139 @@ def build_interface(
|
||||
change_collection_fn,
|
||||
update_ui_language_fn
|
||||
):
|
||||
"""Construit l'interface utilisateur avec Gradio."""
|
||||
"""Construit l'interface utilisateur avec Gradio"""
|
||||
print("Initialisation de l'interface")
|
||||
print("AVAILABLE_MODELS chargé dans ui.py:", AVAILABLE_MODELS)
|
||||
# Initialiser avec la langue par défaut (Français)
|
||||
ui_elements = update_ui_language_elements("Français")
|
||||
|
||||
with gr.Blocks(css=KATEX_CSS_JS, theme=gr.themes.Soft(primary_hue="blue")) as interface:
|
||||
gr.Markdown("# 📚 Assistant documentaire intelligent")
|
||||
title_md = gr.Markdown(f"# {ui_elements['title']}")
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column(scale=2):
|
||||
# Chatbot principal
|
||||
chat_interface = gr.Chatbot(
|
||||
height=600,
|
||||
show_label=False,
|
||||
layout="bubble",
|
||||
elem_id="chatbot"
|
||||
elem_id="chatbot",
|
||||
type="messages" # Ajoutez cette ligne
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
msg = gr.Textbox(
|
||||
show_label=False,
|
||||
placeholder="Posez votre question...",
|
||||
placeholder=ui_elements['placeholder'],
|
||||
container=False,
|
||||
scale=4
|
||||
)
|
||||
submit_btn = gr.Button("Envoyer", variant="primary", scale=1)
|
||||
submit_btn = gr.Button(ui_elements['send_btn'], variant="primary", scale=1)
|
||||
|
||||
clear_btn = gr.Button("Effacer la conversation")
|
||||
clear_btn = gr.Button(ui_elements['clear_btn'])
|
||||
source_info = gr.Markdown("", elem_id="sources_info")
|
||||
|
||||
with gr.Column(scale=1):
|
||||
with gr.Accordion("Options", open=True):
|
||||
# Sélecteur de modèle
|
||||
# Sélecteur de langue pour l'interface
|
||||
language_ui_selector = gr.Dropdown(
|
||||
choices=UI_SUPPORTED_LANGUAGES,
|
||||
value="Français",
|
||||
label=ui_elements['ui_language_label'], # Utiliser une clé différente
|
||||
info=ui_elements['ui_language_info']
|
||||
)
|
||||
# Sélecteur de modèle - assurez-vous que cette section est présente
|
||||
model_selector = gr.Dropdown(
|
||||
choices=AVAILABLE_MODELS,
|
||||
value=DEFAULT_MODEL,
|
||||
label="Modèle Ollama",
|
||||
info="Choisir le modèle de language à utiliser"
|
||||
label=ui_elements['model_label'],
|
||||
info=ui_elements['model_info']
|
||||
)
|
||||
model_status = gr.Markdown(f"Modèle actuel: **{DEFAULT_MODEL}**")
|
||||
model_status = gr.Markdown(f"{ui_elements['model_current_prefix']}: **{DEFAULT_MODEL}**")
|
||||
|
||||
# Sélecteur de langue
|
||||
# Sélecteur de langue pour les réponses
|
||||
language_selector = gr.Dropdown(
|
||||
choices=UI_SUPPORTED_LANGUAGES,
|
||||
value=UI_SUPPORTED_LANGUAGES[0],
|
||||
label="Langue des réponses",
|
||||
info="Choisir la langue dans laquelle l'assistant répondra"
|
||||
choices=list(LANGUAGE_MAPPING.keys()),
|
||||
value="Français",
|
||||
label=ui_elements['language_label'],
|
||||
info=ui_elements['language_info']
|
||||
)
|
||||
|
||||
# Sélecteur de collection Qdrant
|
||||
collection_name_input = gr.Textbox(
|
||||
value=QDRANT_COLLECTION_NAME,
|
||||
label="Collection Qdrant",
|
||||
info="Nom de la collection de documents à utiliser"
|
||||
label=ui_elements['collection_label'],
|
||||
info=ui_elements['collection_info']
|
||||
)
|
||||
collection_status = gr.Markdown(f"Collection actuelle: **{QDRANT_COLLECTION_NAME}**")
|
||||
collection_status = gr.Markdown(f"{ui_elements['collection_current_prefix']}: **{QDRANT_COLLECTION_NAME}**")
|
||||
|
||||
# Bouton d'application de la collection
|
||||
apply_collection_btn = gr.Button("Appliquer la collection")
|
||||
# Bouton pour appliquer la collection
|
||||
apply_collection_btn = gr.Button(ui_elements['apply_btn'])
|
||||
|
||||
# Options de streaming et sources
|
||||
streaming = gr.Checkbox(
|
||||
label="Mode streaming",
|
||||
label=ui_elements['streaming_label'],
|
||||
value=True,
|
||||
info="Voir les réponses s'afficher progressivement"
|
||||
info=ui_elements['streaming_info']
|
||||
)
|
||||
show_sources = gr.Checkbox(label="Afficher les sources", value=True)
|
||||
show_sources = gr.Checkbox(label=ui_elements['sources_label'], value=True)
|
||||
max_images = gr.Slider(
|
||||
minimum=1,
|
||||
maximum=10,
|
||||
value=3,
|
||||
step=1,
|
||||
label="Nombre max d'images"
|
||||
label=ui_elements['max_images_label']
|
||||
)
|
||||
|
||||
gr.Markdown("---")
|
||||
|
||||
gr.Markdown("### 🖼️ Images pertinentes")
|
||||
images_title = gr.Markdown(f"### {ui_elements['images_title']}")
|
||||
image_gallery = gr.Gallery(
|
||||
label="Images pertinentes",
|
||||
label=ui_elements['images_title'],
|
||||
show_label=False,
|
||||
columns=2,
|
||||
height=300,
|
||||
object_fit="contain"
|
||||
)
|
||||
|
||||
gr.Markdown("### 📊 Tableaux")
|
||||
tables_title = gr.Markdown(f"### {ui_elements['tables_title']}")
|
||||
tables_display = gr.HTML()
|
||||
|
||||
# Connecter le changement de modèle
|
||||
model_selector.change(
|
||||
fn=change_model_fn,
|
||||
inputs=model_selector,
|
||||
outputs=model_status
|
||||
)
|
||||
|
||||
# Connecter le changement de collection
|
||||
apply_collection_btn.click(
|
||||
fn=change_collection_fn,
|
||||
inputs=collection_name_input,
|
||||
outputs=collection_status
|
||||
# Ajouter cette fonction juste avant de connecter le changement de langue
|
||||
def preserve_models_wrapper(language):
|
||||
"""Préserve la liste des modèles lors du changement de langue"""
|
||||
# Obtenir les mises à jour depuis la fonction d'origine
|
||||
updates = update_ui_language_fn(language)
|
||||
|
||||
# Force la liste complète des modèles disponibles (position 5 dans les sorties)
|
||||
# Cela garantit que quelles que soient les mises à jour, la liste des modèles reste intacte
|
||||
if isinstance(updates[5], dict) and "choices" in updates[5]:
|
||||
print("Préservation de la liste des modèles:", AVAILABLE_MODELS)
|
||||
updates[5]["choices"] = AVAILABLE_MODELS
|
||||
|
||||
return updates
|
||||
|
||||
# Puis modifier la connexion du language_ui_selector.change comme suit :
|
||||
language_ui_selector.change(
|
||||
fn=preserve_models_wrapper, # Utiliser notre wrapper au lieu de la fonction directe
|
||||
inputs=language_ui_selector,
|
||||
outputs=[
|
||||
title_md,
|
||||
msg,
|
||||
submit_btn,
|
||||
clear_btn,
|
||||
language_ui_selector,
|
||||
model_selector,
|
||||
model_status,
|
||||
language_selector,
|
||||
collection_name_input,
|
||||
collection_status,
|
||||
apply_collection_btn,
|
||||
streaming,
|
||||
show_sources,
|
||||
max_images,
|
||||
images_title,
|
||||
tables_title
|
||||
]
|
||||
)
|
||||
|
||||
# Fonction pour effacer l'entrée
|
||||
@ -134,6 +218,20 @@ def build_interface(
|
||||
outputs=[chat_interface, source_info, image_gallery, tables_display]
|
||||
)
|
||||
|
||||
# Connecter le changement de modèle
|
||||
model_selector.change(
|
||||
fn=change_model_fn,
|
||||
inputs=model_selector,
|
||||
outputs=model_status
|
||||
)
|
||||
|
||||
# Connecter le changement de collection
|
||||
apply_collection_btn.click(
|
||||
fn=change_collection_fn,
|
||||
inputs=collection_name_input,
|
||||
outputs=collection_status
|
||||
)
|
||||
|
||||
# Style KaTeX et amélioration du design
|
||||
gr.Markdown("""
|
||||
<style>
|
||||
|
||||
116
final_pdf.ipynb
116
final_pdf.ipynb
File diff suppressed because one or more lines are too long
328
services/rag_service.py
Normal file
328
services/rag_service.py
Normal file
@ -0,0 +1,328 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import traceback
|
||||
import threading
|
||||
import queue
|
||||
import time
|
||||
|
||||
from rag_chatbot import MultimodalRAGChatbot
|
||||
from langchain.prompts import ChatPromptTemplate
|
||||
from langchain_ollama import ChatOllama
|
||||
from langchain.callbacks.base import BaseCallbackHandler
|
||||
|
||||
# Handler personnalisé pour capturer les tokens en streaming
|
||||
class GradioStreamingHandler(BaseCallbackHandler):
|
||||
def __init__(self):
|
||||
self.tokens_queue = queue.Queue()
|
||||
self.full_text = ""
|
||||
|
||||
def on_llm_new_token(self, token, **kwargs):
|
||||
self.tokens_queue.put(token)
|
||||
self.full_text += token
|
||||
|
||||
# Fonction pour créer un objet Image à partir des données base64
|
||||
def base64_to_image(base64_data):
|
||||
"""Convertit une image base64 en objet Image pour l'affichage direct"""
|
||||
try:
|
||||
if not base64_data:
|
||||
return None
|
||||
image_bytes = base64.b64decode(base64_data)
|
||||
image = Image.open(BytesIO(image_bytes))
|
||||
return image
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la conversion d'image: {e}")
|
||||
return None
|
||||
|
||||
# Configuration pour initialiser le chatbot
|
||||
QDRANT_URL = "http://localhost:6333"
|
||||
QDRANT_COLLECTION_NAME = "my_custom_collection"
|
||||
EMBEDDING_MODEL = "mxbai-embed-large"
|
||||
OLLAMA_URL = "http://127.0.0.1:11434"
|
||||
DEFAULT_MODEL = "llama3.2"
|
||||
|
||||
# Liste des modèles disponibles
|
||||
AVAILABLE_MODELS = ["llama3.1", "llama3.2", "deepseek-r1:7b", "deepseek-r1:14b"]
|
||||
|
||||
# Mapping des langues pour une meilleure compréhension par le LLM
|
||||
LANGUAGE_MAPPING = {
|
||||
"Français": "français",
|
||||
"English": "English",
|
||||
"Español": "español",
|
||||
"Deutsch": "Deutsch",
|
||||
"Italiano": "italiano",
|
||||
"中文": "Chinese",
|
||||
"日本語": "Japanese",
|
||||
"العربية": "Arabic"
|
||||
}
|
||||
|
||||
# Variables globales pour stocker les images et tableaux de la dernière requête
|
||||
current_images = []
|
||||
current_tables = []
|
||||
|
||||
# Initialiser le chatbot RAG avec le modèle par défaut
|
||||
def initialize_rag_bot():
|
||||
global rag_bot
|
||||
rag_bot = MultimodalRAGChatbot(
|
||||
qdrant_url=QDRANT_URL,
|
||||
qdrant_collection_name=QDRANT_COLLECTION_NAME,
|
||||
ollama_model=DEFAULT_MODEL,
|
||||
embedding_model=EMBEDDING_MODEL,
|
||||
ollama_url=OLLAMA_URL
|
||||
)
|
||||
print(f"Chatbot initialisé avec modèle: {DEFAULT_MODEL}")
|
||||
|
||||
# Fonction pour changer de modèle
|
||||
def change_model(model_name):
|
||||
global rag_bot
|
||||
|
||||
try:
|
||||
# Réinitialiser le chatbot avec le nouveau modèle
|
||||
rag_bot = MultimodalRAGChatbot(
|
||||
qdrant_url=QDRANT_URL,
|
||||
qdrant_collection_name=QDRANT_COLLECTION_NAME,
|
||||
ollama_model=model_name,
|
||||
embedding_model=EMBEDDING_MODEL,
|
||||
ollama_url=OLLAMA_URL
|
||||
)
|
||||
print(f"Modèle changé pour: {model_name}")
|
||||
return f"✅ Modèle changé pour: {model_name}"
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du changement de modèle: {e}")
|
||||
return f"❌ Erreur: {str(e)}"
|
||||
|
||||
# Fonction pour changer de collection
|
||||
def change_collection(collection_name):
|
||||
global rag_bot, QDRANT_COLLECTION_NAME
|
||||
|
||||
try:
|
||||
# Mise à jour de la variable globale
|
||||
QDRANT_COLLECTION_NAME = collection_name
|
||||
|
||||
# Réinitialiser le chatbot avec la nouvelle collection
|
||||
rag_bot = MultimodalRAGChatbot(
|
||||
qdrant_url=QDRANT_URL,
|
||||
qdrant_collection_name=collection_name,
|
||||
ollama_model=rag_bot.llm.model, # Conserver le modèle actuel
|
||||
embedding_model=EMBEDDING_MODEL,
|
||||
ollama_url=OLLAMA_URL
|
||||
)
|
||||
print(f"Collection changée pour: {collection_name}")
|
||||
return f"✅ Collection changée pour: {collection_name}"
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du changement de collection: {e}")
|
||||
return f"❌ Erreur: {str(e)}"
|
||||
|
||||
# Fonction de traitement des requêtes avec support du streaming dans Gradio
|
||||
def process_query(message, history, streaming, show_sources, max_images, language):
|
||||
global current_images, current_tables
|
||||
|
||||
if not message.strip():
|
||||
return history, "", None, None
|
||||
|
||||
current_images = []
|
||||
current_tables = []
|
||||
|
||||
try:
|
||||
if streaming:
|
||||
# Version avec streaming dans Gradio
|
||||
history = history + [(message, "")]
|
||||
|
||||
# 1. Récupérer les documents pertinents
|
||||
docs = rag_bot._retrieve_relevant_documents(message)
|
||||
|
||||
# 2. Préparer le contexte et l'historique
|
||||
context = rag_bot._format_documents(docs)
|
||||
history_text = rag_bot._format_chat_history()
|
||||
|
||||
# 3. Préparer le prompt
|
||||
prompt_template = ChatPromptTemplate.from_template("""
|
||||
Tu es un assistant documentaire spécialisé qui utilise toutes les informations disponibles dans le contexte fourni.
|
||||
|
||||
TRÈS IMPORTANT: Tu dois répondre EXCLUSIVEMENT en {language}. Ne réponds JAMAIS dans une autre langue.
|
||||
|
||||
Instructions spécifiques:
|
||||
1. Pour chaque image mentionnée dans le contexte, inclue TOUJOURS dans ta réponse:
|
||||
- La légende/caption exacte de l'image
|
||||
- La source et le numéro de page
|
||||
- Une description brève de ce qu'elle montre
|
||||
|
||||
2. Pour chaque tableau mentionné dans le contexte, inclue TOUJOURS:
|
||||
- Le titre/caption exact du tableau
|
||||
- La source et le numéro de page
|
||||
- Ce que contient et signifie le tableau
|
||||
|
||||
3. Lorsque tu cites des équations mathématiques:
|
||||
- Utilise la syntaxe LaTeX exacte comme dans le document ($...$ ou $$...$$)
|
||||
- Reproduis-les fidèlement sans modification
|
||||
|
||||
4. IMPORTANT: Ne pas inventer d'informations - si une donnée n'est pas explicitement fournie dans le contexte,
|
||||
indique clairement que cette information n'est pas disponible dans les documents fournis.
|
||||
|
||||
5. Cite précisément les sources pour chaque élément d'information (format: [Source, Page]).
|
||||
|
||||
6. CRUCIAL: Ta réponse doit être UNIQUEMENT et INTÉGRALEMENT en {language}, quelle que soit la langue de la question.
|
||||
|
||||
Historique de conversation:
|
||||
{chat_history}
|
||||
|
||||
Contexte (à utiliser pour répondre):
|
||||
{context}
|
||||
|
||||
Question: {question}
|
||||
|
||||
Réponds de façon structurée et précise en intégrant activement les images, tableaux et équations disponibles dans le contexte.
|
||||
Ta réponse doit être exclusivement en {language}.
|
||||
""")
|
||||
|
||||
# 4. Formater les messages pour le LLM
|
||||
messages = prompt_template.format_messages(
|
||||
chat_history=history_text,
|
||||
context=context,
|
||||
question=message,
|
||||
language=LANGUAGE_MAPPING.get(language, "français") # Use the mapped language value
|
||||
)
|
||||
|
||||
# 5. Créer un handler de streaming personnalisé
|
||||
handler = GradioStreamingHandler()
|
||||
|
||||
# 6. Créer un modèle LLM avec notre handler
|
||||
streaming_llm = ChatOllama(
|
||||
model=rag_bot.llm.model,
|
||||
base_url=rag_bot.llm.base_url,
|
||||
streaming=True,
|
||||
callbacks=[handler]
|
||||
)
|
||||
|
||||
# 7. Lancer la génération dans un thread pour ne pas bloquer l'UI
|
||||
def generate_response():
|
||||
streaming_llm.invoke(messages)
|
||||
|
||||
thread = threading.Thread(target=generate_response)
|
||||
thread.start()
|
||||
|
||||
# 8. Récupérer les tokens et mettre à jour l'interface
|
||||
partial_response = ""
|
||||
|
||||
# Attendre les tokens avec un timeout
|
||||
while thread.is_alive() or not handler.tokens_queue.empty():
|
||||
try:
|
||||
token = handler.tokens_queue.get(timeout=0.05)
|
||||
partial_response += token
|
||||
history[-1] = (message, partial_response)
|
||||
yield history, "", None, None
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
# 9. Thread terminé, mettre à jour l'historique de conversation du chatbot
|
||||
rag_bot.chat_history.append({"role": "user", "content": message})
|
||||
rag_bot.chat_history.append({"role": "assistant", "content": partial_response})
|
||||
|
||||
# 10. Récupérer les sources, images, tableaux
|
||||
texts, images, tables = rag_bot._process_documents(docs)
|
||||
|
||||
# Préparer les informations sur les sources
|
||||
source_info = ""
|
||||
if texts:
|
||||
source_info += f"📚 {len(texts)} textes • "
|
||||
if images:
|
||||
source_info += f"🖼️ {len(images)} images • "
|
||||
if tables:
|
||||
source_info += f"📊 {len(tables)} tableaux"
|
||||
|
||||
if source_info:
|
||||
source_info = "Sources trouvées: " + source_info
|
||||
|
||||
# 11. Traiter les images
|
||||
if show_sources and images:
|
||||
images = images[:max_images]
|
||||
for img in images:
|
||||
img_data = img.get("image_data")
|
||||
if img_data:
|
||||
image = base64_to_image(img_data)
|
||||
if image:
|
||||
current_images.append({
|
||||
"image": image,
|
||||
"caption": img.get("caption", ""),
|
||||
"source": img.get("source", ""),
|
||||
"page": img.get("page", ""),
|
||||
"description": img.get("description", "")
|
||||
})
|
||||
|
||||
# 12. Traiter les tableaux
|
||||
if show_sources and tables:
|
||||
for table in tables:
|
||||
current_tables.append({
|
||||
"data": rag_bot.format_table(table.get("table_data", "")),
|
||||
"caption": table.get("caption", ""),
|
||||
"source": table.get("source", ""),
|
||||
"page": table.get("page", ""),
|
||||
"description": table.get("description", "")
|
||||
})
|
||||
|
||||
# 13. Retourner les résultats finaux
|
||||
yield history, source_info, display_images(current_images), display_tables(current_tables, language)
|
||||
|
||||
else:
|
||||
# Version sans streaming (code existant)
|
||||
result = rag_bot.chat(message, stream=False)
|
||||
history = history + [(message, result["response"])]
|
||||
|
||||
# Préparer les informations sur les sources
|
||||
source_info = ""
|
||||
if "texts" in result:
|
||||
source_info += f"📚 {len(result['texts'])} textes • "
|
||||
if "images" in result:
|
||||
source_info += f"🖼️ {len(result['images'])} images • "
|
||||
if "tables" in result:
|
||||
source_info += f"📊 {len(result['tables'])} tableaux"
|
||||
|
||||
if source_info:
|
||||
source_info = "Sources trouvées: " + source_info
|
||||
|
||||
# Traiter les images et tableaux
|
||||
if show_sources and "images" in result and result["images"]:
|
||||
images = result["images"][:max_images]
|
||||
for img in images:
|
||||
img_data = img.get("image_data")
|
||||
if img_data:
|
||||
image = base64_to_image(img_data)
|
||||
if image:
|
||||
current_images.append({
|
||||
"image": image,
|
||||
"caption": img.get("caption", ""),
|
||||
"source": img.get("source", ""),
|
||||
"page": img.get("page", ""),
|
||||
"description": img.get("description", "")
|
||||
})
|
||||
|
||||
if show_sources and "tables" in result and result["tables"]:
|
||||
tables = result["tables"]
|
||||
for table in tables:
|
||||
current_tables.append({
|
||||
"data": rag_bot.format_table(table.get("table_data", "")),
|
||||
"caption": table.get("caption", ""),
|
||||
"source": table.get("source", ""),
|
||||
"page": table.get("page", ""),
|
||||
"description": table.get("description", "")
|
||||
})
|
||||
|
||||
return history, source_info, display_images(current_images), display_tables(current_tables, language)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Une erreur est survenue: {str(e)}"
|
||||
traceback_text = traceback.format_exc()
|
||||
print(error_msg)
|
||||
print(traceback_text)
|
||||
history = history + [(message, error_msg)]
|
||||
return history, "Erreur lors du traitement de la requête", None, None
|
||||
|
||||
# Fonction pour réinitialiser la conversation
|
||||
def reset_conversation():
|
||||
global current_images, current_tables
|
||||
current_images = []
|
||||
current_tables = []
|
||||
|
||||
rag_bot.clear_history()
|
||||
|
||||
return [], "", None, None
|
||||
@ -7,8 +7,7 @@ LANGUAGE_MAPPING = {
|
||||
"Italiano": "italiano",
|
||||
"中文": "Chinese",
|
||||
"日本語": "Japanese",
|
||||
"العربية": "Arabic"
|
||||
}
|
||||
}
|
||||
|
||||
# Dictionnaire de traductions pour l'interface
|
||||
UI_TRANSLATIONS = {
|
||||
@ -39,7 +38,9 @@ UI_TRANSLATIONS = {
|
||||
"error_msg": "Une erreur est survenue",
|
||||
"processing_error": "Erreur lors du traitement de la requête",
|
||||
"table_translation": "Traduction",
|
||||
"table_description": "Ce tableau présente des données sur"
|
||||
"table_description": "Ce tableau présente des données sur",
|
||||
"ui_language_label": "Langue de l'interface",
|
||||
"ui_language_info": "Changer la langue de l'interface uniquement"
|
||||
},
|
||||
"English": {
|
||||
"title": "📚 Intelligent Document Assistant",
|
||||
@ -68,7 +69,9 @@ UI_TRANSLATIONS = {
|
||||
"error_msg": "An error occurred",
|
||||
"processing_error": "Error processing request",
|
||||
"table_translation": "Translation",
|
||||
"table_description": "This table presents data on"
|
||||
"table_description": "This table presents data on",
|
||||
"ui_language_label": "UI Language",
|
||||
"ui_language_info": "Change only the interface language"
|
||||
},
|
||||
"Español": {
|
||||
"title": "📚 Asistente documental inteligente",
|
||||
@ -97,7 +100,9 @@ UI_TRANSLATIONS = {
|
||||
"error_msg": "Se ha producido un error",
|
||||
"processing_error": "Error al procesar la solicitud",
|
||||
"table_translation": "Traducción",
|
||||
"table_description": "Esta tabla presenta datos sobre"
|
||||
"table_description": "Esta tabla presenta datos sobre",
|
||||
"ui_language_label": "Idioma de la interfaz",
|
||||
"ui_language_info": "Cambiar solo el idioma de la interfaz"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user