#================imports============== import uuid import requests import os os.environ["USER_AGENT"] = "RAG-App/1.0" from typing import Dict, List, Any, Generator from dotenv import load_dotenv from bs4 import BeautifulSoup from langchain_core.globals import set_llm_cache from langchain_core.caches import InMemoryCache from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import Weaviate from langchain_community.vectorstores import FAISS from langchain_groq import ChatGroq from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_classic.chains.combine_documents import create_stuff_documents_chain from langchain_classic.chains import create_retrieval_chain from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.chat_history import BaseChatMessageHistory import gradio as gr #================== CONFIG================== load_dotenv() set_llm_cache(InMemoryCache()) api_key = os.environ["GROQ_API_KEY"] print("api chargée:" if api_key else "y'a probleme!!") #========== charger et decouper documents================= urls = [ "https://fr.wikipedia.org/wiki/%C3%89levage", "https://fr.wikipedia.org/wiki/La_P%C3%AAche" ] loader = WebBaseLoader(urls, requests_kwargs={ "headers": { "User-Agent": "RAG-App/1.0" } } ) docs = loader.load() splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) chunks = splitter.split_documents(docs) #============embeding et indexation vers faiss_db================ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") faiss_db = FAISS.from_documents( documents=chunks, embedding=embeddings ) retriever = faiss_db.as_retriever(search_type="similarity", search_kwargs={"k": 3}) #=============== LLM et Prompt================= llm = ChatGroq( model="llama-3.3-70b-versatile", temperature=0.0, max_tokens=1200, streaming=True # Activer le streaming pour une réponse en temps réel ) prompt = ChatPromptTemplate.from_messages([ ("system", """Tu es un assistant expert en dans le domaine de l'elevage et la pêche. Réponds clairement. Si tu ne connais pas, n'invente pas. Garde un ton amical. Contexte : {context}"""), MessagesPlaceholder(variable_name="chat_history"), ("human", "{input}"), ]) #============= CHAINE DE RECUPERATION======= stuff_chain = create_stuff_documents_chain(llm, prompt) rag_chain = create_retrieval_chain(retriever, stuff_chain) # ====== GESTION DE L'HISTORIQUE ====== store = {} def get_session_history(session_id: str) -> BaseChatMessageHistory: if session_id not in store: store[session_id] = ChatMessageHistory() return store[session_id] # ====== CHAIN AVEC MÉMOIRE ====== convers_chain = RunnableWithMessageHistory( rag_chain, get_session_history, input_messages_key="input", history_messages_key="chat_history", output_messages_key="answer" ) SESSION_ID = str(uuid.uuid4()) # session globale # ================= CSS PERSONNALISÉ POUR LE STYLE CHATGPT ==================== custom_css = """ /* Style global */ .gradio-container { max-width: 100% !important; margin: 0 !important; padding: 0 !important; } /* Style de la sidebar */ .sidebar { background: #202123; color: white; height: 100vh; overflow-y: auto; } .sidebar-header { padding: 12px 16px; border-bottom: 1px solid #4d4d4f; margin-bottom: 8px; } .new-chat-btn { width: 100%; padding: 8px 16px; background: transparent; border: 1px solid #4d4d4f; color: white; border-radius: 8px; cursor: pointer; font-size: 14px; text-align: left; transition: background 0.3s; } .new-chat-btn:hover { background: #2b2c2f; } .history-item { padding: 12px 16px; cursor: pointer; border-radius: 8px; margin: 4px 8px; transition: background 0.3s; word-wrap: break-word; color: #ececec; } .history-item:hover { background: #2b2c2f; } .history-item.active { background: #343541; } .main-chat-area { background: #343541; height: 100vh; } .chatbot-container { height: calc(100vh - 180px) !important; } .chatbot-container > div { border: none !important; background: #343541 !important; } """ # ================= INTERFACE GRADIO STYLE CHATGPT ==================== with gr.Blocks(css=custom_css, theme="soft") as demo: with gr.Row(equal_height=True): # ==================== SIDEBAR GAUCHE (Style ChatGPT) ==================== with gr.Column(scale=1, min_width=260, elem_classes="sidebar"): gr.HTML(""" """) # Bouton Nouvelle conversation new_chat_btn = gr.Button("➕ Nouvelle conversation", elem_classes="new-chat-btn") gr.HTML('
HISTORIQUE
') # Liste des conversations précédentes history_radio = gr.Radio( choices=[], label=None, interactive=True, elem_classes="history-radio", container=False ) # Variables d'état pour stocker les conversations conversations_state = gr.State([]) current_conversation_id = gr.State("") # Message pour confirmer les actions status_msg = gr.HTML(visible=False) # ==================== ZONE PRINCIPALE DE CHAT (Style ChatGPT) ==================== with gr.Column(scale=4, elem_classes="main-chat-area"): # Header gr.HTML("""

Assistant RAG - Élevage & Pêche

""") # Chatbot chatbot = gr.Chatbot( label=None, height=500, elem_classes="chatbot-container", show_label=False, bubble_full_width=False, avatar_images=(None, "🐟") ) # Zone de saisie style ChatGPT with gr.Row(): msg_input = gr.Textbox( show_label=False, placeholder="Envoyez un message...", scale=9, container=False, lines=1, max_lines=5 ) send_btn = gr.Button("↑", variant="primary", scale=1, min_width=40) # Texte de copyright en bas gr.HTML("""
RAG Assistant peut faire des erreurs. Vérifiez les informations importantes.
""") # ==================== FONCTIONS DE GESTION ==================== def create_new_conversation(conversations): """Crée une nouvelle conversation et retourne l'ID.""" conversation_id = str(uuid.uuid4()) title = "Nouvelle conversation" conversations.append({"id": conversation_id, "title": title, "messages": []}) return conversations, conversation_id, title, gr.update(choices=[c["title"] for c in conversations], value=None) def load_conversation(selected_title, conversations): """Charge une conversation existante.""" if not selected_title: return [], "", "" for conv in conversations: if conv["title"] == selected_title: chat_history = [] for msg in conv["messages"]: if msg["role"] == "user": chat_history.append((msg["content"], None)) else: if chat_history: chat_history[-1] = (chat_history[-1][0], msg["content"]) else: chat_history.append((None, msg["content"])) return chat_history, conv["id"], conv["title"] return [], "", "" def send_message(message, chat_history, conversations, current_conv_id, chat_state): """Envoie un message et met à jour la conversation.""" if not message or not message.strip(): return "", chat_history, conversations, current_conv_id, chat_state # Créer une nouvelle conversation si nécessaire if not current_conv_id: current_conv_id = str(uuid.uuid4()) # Utiliser les premiers mots comme titre title = message[:50] + "..." if len(message) > 50 else message conversations.append({ "id": current_conv_id, "title": title, "messages": [{"role": "user", "content": message}] }) else: # Ajouter le message à la conversation existante for conv in conversations: if conv["id"] == current_conv_id: conv["messages"].append({"role": "user", "content": message}) if conv["title"] == "Nouvelle conversation": conv["title"] = message[:50] + "..." if len(message) > 50 else message break # Ajouter le message utilisateur au chatbot chat_history.append((message, None)) # Obtenir la réponse de l'assistant result = convers_chain.invoke( {"input": message}, config={"configurable": {"session_id": SESSION_ID}} ) response = result.get("answer", str(result)) # Ajouter la réponse à la conversation for conv in conversations: if conv["id"] == current_conv_id: conv["messages"].append({"role": "assistant", "content": response}) break # Mettre à jour le chatbot chat_history[-1] = (message, response) # Mettre à jour les choix du radio choices = [conv["title"] for conv in conversations] return "", chat_history, conversations, current_conv_id, gr.update(choices=choices, value=conversations[-1]["title"] if conversations else None) def clear_chat(): """Efface le chat et commence une nouvelle conversation.""" return [], "", None, gr.update(choices=[], value=None) # ==================== GESTIONNAIRES D'ÉVÉNEMENTS ==================== # Envoi de message msg_input.submit( send_message, inputs=[msg_input, chatbot, conversations_state, current_conversation_id, gr.State([])], outputs=[msg_input, chatbot, conversations_state, current_conversation_id, history_radio] ).then( lambda: gr.update(value=""), outputs=[msg_input] ) send_btn.click( send_message, inputs=[msg_input, chatbot, conversations_state, current_conversation_id, gr.State([])], outputs=[msg_input, chatbot, conversations_state, current_conversation_id, history_radio] ).then( lambda: gr.update(value=""), outputs=[msg_input] ) # Nouvelle conversation new_chat_btn.click( create_new_conversation, inputs=[conversations_state], outputs=[conversations_state, current_conversation_id, gr.State(""), history_radio] ).then( clear_chat, outputs=[chatbot, current_conversation_id, history_radio, history_radio] ) # Charger une conversation depuis l'historique history_radio.change( load_conversation, inputs=[history_radio, conversations_state], outputs=[chatbot, current_conversation_id, gr.State("")] ) # Support de la touche Entrée (Shift+Entrée pour nouvelle ligne) def handle_enter_key(text, event: gr.EventData): if event.key == "Enter" and not event.shift: return text, gr.update() return text, gr.update() msg_input.key_up( handle_enter_key, inputs=[msg_input], outputs=[msg_input, chatbot] ) # ===================LANCEMENT ================ demo.launch(share=False)