#================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)