import streamlit as st from graph_agentA import agent as agent_A from graph_agentB import agent as agent_B from graph_agentC import agent as agent_C from config import * from dotenv import load_dotenv from pinecone_utilsB import * from langchain_core.messages import AIMessageChunk from typing import Literal import re from carbon.empreinte_carbone import initCarbon, traceImpact, display_cf_comparison # Charger les variables d'environnement load_dotenv() # Initialiser l'état de chat globalement if "chat_history" not in st.session_state: st.session_state.chat_history = [] def check_indexes_ready(): """Vérifie que les index Pinecone sont prêts.""" try: # Vérifier que les index existent existing_indexes = pc.list_indexes().names() if sparse_index_name not in existing_indexes or dense_index_name not in existing_indexes: st.error("Les index Pinecone ne sont pas prêts. Veuillez exécuter 'initialize_indexes.py'.") return False return True except Exception as e: st.error(f"Erreur lors de la vérification des index Pinecone : {e}") return False def process_query(query, architecture: Literal["A", "B", "C"]): """Traite la requête de l'utilisateur avec l'architecture A, B ou C.""" # Reload conversation display_chat_history() config = {"metadata": {"architecture": architecture}, "tags": ["arch_" + architecture]} # Récupération des paramètres dynamiques uniquement k = st.session_state.get("k", 30) # Nombre de documents similarity_threshold = st.session_state.get("similarity_threshold", 0.7) # similarité cosinus if architecture == "A": agent = agent_A initial_state = { "query": query, "messages": [], "relevant_docs": [], "response": "", "k": k, "similarity_threshold": similarity_threshold, } elif architecture in ["B", "C"]: agent = agent_B if architecture == "B" else agent_C # Récupération du paramétre alpha uniquement pour B et C alpha = st.session_state.get("alpha", 0.5) # Pondération hybride initial_state = { "query": query, "messages": [], "relevant_docs": [], "response": "", "k": k, "alpha": alpha, "similarity_threshold": similarity_threshold, } st.session_state.chat_history.append({"role": "user", "content": query}) with st.chat_message("user"): st.markdown(query) full_response = "" usages = None start_time = time.time() # Start timing events = agent.stream(initial_state, config=config, stream_mode="messages") # Ajouter le message du chatbot avec streaming with st.chat_message("assistant"): response_placeholder = st.empty() for event in events: for message in event: if isinstance(message, AIMessageChunk): if hasattr(message, 'content'): full_response += message.content # Supprimer les requêtes Cypher avant l'affichage full_response = re.sub(r"(?i)(MATCH|CREATE|MERGE|DELETE|CALL)[\s\S]+?;", "", full_response).strip() response_placeholder.markdown(full_response) if hasattr(message, 'usage_metadata'): usages = message.usage_metadata latency = time.time() - start_time # Calculate elapsed time # if isinstance(message, AIMessageChunk) and hasattr(message, 'content'): # full_response += message.content # Accumuler les morceaux # 🛑 Vérification si la réponse contient une requête Cypher if re.search(r"(?i)(MATCH|CREATE|MERGE|DELETE|CALL)[\s\S]+;", full_response): full_response = full_response = re.sub(r"(?i)(MATCH|CREATE|MERGE|DELETE|CALL)[\s\S]+?;", "", full_response).strip() # 🔄 Affichage final # response_placeholder.markdown(full_response) st.session_state.chat_history.append({"role": "assistant", "content": full_response}) traceImpact( st.session_state.current_model, trace={ 'completion_tokens' : usages.get('output_tokens', None), 'latency' : latency # Use the measured latency } ) # Mise à jour des tokens + coût st.session_state['tokens_metrics']['input_tokens'] += usages.get('input_tokens', 0) st.session_state['tokens_metrics']['output_tokens'] += usages.get('output_tokens', 0) st.session_state['tokens_metrics']['total_tokens'] += usages.get('total_tokens', 0) calculate_tokens_cost() st.rerun() def display_sidebar(): """Affiche la barre latérale.""" with st.sidebar: #st.title("📄 La confession muette") #st.write("Posez vos questions sur le document.") st.markdown("### Document de référence 📄") st.markdown("

La confession muette (2025)

", unsafe_allow_html=True) lien_ressource = "https://www.fnac.com/livre-numerique/a21290809/Gaspard-Boreal-La-Confession-muette" st.markdown("*Avec l'aimable autorisation de Gaspard Boréal :* \n[*Récit d'origine*]({})".format(lien_ressource)) # Token metrics containers # st.sidebar.markdown("### Tokens") st.markdown("### Paramètres de la recherche RAG") # Sélection du nombre de documents (k) st.markdown(""" **Nombre de documents à récupérer (k)** 🛈 Détermine combien de documents seront récupérés lors de la recherche. """, unsafe_allow_html=True) st.number_input(" ", min_value=1, max_value=100, value=30, step=1, key="k") # Sélection du score de similarité cosinus st.markdown(""" **Score de similarité cosinus** 🛈 Ce paramètre définit le seuil minimal de similarité entre deux vecteurs. Plus il est élevé, plus seuls les éléments très similaires seront considérés comme correspondants. """, unsafe_allow_html=True) st.slider(" ", 0.0, 1.0, value=0.7, step=0.05, key="similarity_threshold") # Afficher alpha uniquement pour B et C if st.session_state.get("architecture") in ["B", "C"]: st.markdown(""" **Équilibre entre recherche sémantique et syntaxique** 🛈 Pour `alpha = 0.0`, la recherche est purement syntaxique. Pour `alpha = 1.0`, elle est purement sémantique. """, unsafe_allow_html=True) st.slider(" ", 0.0, 1.0, value=0.5, step=0.05, key="alpha") # Sauvegarde de la valeur alpha else: # Réinitialiser alpha si l'architecture est A st.session_state['alpha'] = None st.sidebar.markdown("### API Mistral AI") # HTML pour le tableau sans index ni colonnes inutiles table_html = f"""
Input Tokens{st.session_state['tokens_metrics'].get('input_tokens', 0)}
Output Tokens{st.session_state['tokens_metrics'].get('output_tokens', 0)}
Total Tokens{st.session_state['tokens_metrics'].get('total_tokens', 0)}
Coût{st.session_state['tokens_cost']} €
""" st.sidebar.markdown(table_html, unsafe_allow_html=True) st.sidebar.markdown("### Empreinte Carbone") display_cf_comparison(st.sidebar) st.sidebar.markdown("---") st.sidebar.markdown("### Documentation") st.markdown("*Livre de référence :* La confession muette (2025)") st.markdown("*Test Q&A :* [*Lien*]({})".format("https://docs.google.com/spreadsheets/d/1dkMgv1MM9ZQjY8EIsR3V8DpMjlmSG37ydFD9TyTHtt4/edit?usp=sharing")) st.markdown("*Documentation RAG :* [*Lien*]({})".format("https://docs.google.com/presentation/d/17Q3qt0-p9feSxSY1AgLjoDcitwg_TfNlnzDSynkt8ew/edit?usp=sharing")) st.sidebar.markdown("---") st.sidebar.image("./assets/bziiit.png", width=100) st.markdown("• *Contributeur AFNOR SPEC IA FRUGAL*") st.markdown("• *Certifié AFNOR COMPETENCES*") st.markdown("• *Membre HUB FRANCE IA*") st.markdown("• *Membre OSFARM*") st.sidebar.markdown("") st.sidebar.markdown("2025 : Open source en Licence MIT") st.sidebar.markdown("info@bziiit.com") def display_chat_history(): """Affiche l'historique de chat.""" for message in st.session_state.chat_history: if message["role"] == "user": with st.chat_message("user"): st.markdown(message["content"]) elif message["role"] == "assistant": with st.chat_message("assistant"): st.markdown(message["content"]) def is_neo4j_aura_active(retries: int, wait_seconds: int): """ Tente de se connecter à l'instance Neo4j. Si elle est en veille, la fonction essaiera de se reconnecter jusqu'à ce que l'instance soit réveillée. Args: retries (int): Nombre de tentatives avant d'abandonner. wait_seconds (int): Temps d'attente entre les tentatives. """ for attempt in range(1, retries + 1): try: with neo4j_driver.session() as session: session.run("RETURN 1") # Simple requête pour réveiller l’instance return True except Exception as e: print(f"⚠️ Tentative {attempt}/{retries} : l'instance est en veille. Nouvel essai dans {wait_seconds}s...") time.sleep(wait_seconds) raise RuntimeError("❌ Impossible de réveiller l'instance Neo4j après plusieurs tentatives.") def main(): initCarbon() st.session_state['current_model'] = "mistral-large-latest" if "tokens_metrics" not in st.session_state or "chat_history" not in st.session_state: initialize_conversation() if not check_indexes_ready(): return st.title("LaConfessionMuette Chatbot") st.markdown(""" """, unsafe_allow_html=True) st.markdown("• Basique : *Recherche sémantique*") st.markdown("• Intermédiaire : *Rechercher hybride (sémantique + mots clés)*") st.markdown("• Avancée : *Graph de connaissance + Recherche hybride*") # Initialisation des états if "architecture" not in st.session_state: st.session_state["architecture"] = "A" if "selected_arch" not in st.session_state: st.session_state["selected_arch"] = "Basic" if "first_time_advanced" not in st.session_state: st.session_state["first_time_advanced"] = True if "advanced_info_shown" not in st.session_state: st.session_state["advanced_info_shown"] = False # Selectbox new_selection = st.selectbox( "Sélectionnez une architecture RAG :", ["Basic", "Intermédiaire", "Avancée"], index=["Basic", "Intermédiaire", "Avancée"].index(st.session_state["selected_arch"]), key="arch_selection" ) # Si la sélection change if new_selection != st.session_state["selected_arch"]: if new_selection == "Avancée": # Message de confirmation pour première fois if st.session_state["first_time_advanced"]: st.info("⚠️ Attention, ce type de fonctionnement est plus précis mais consomme plus de carbone. Souhaitez-vous réellement l'utiliser ?") col1, col2 = st.columns(2) if col1.button("✅ Confirmer"): st.session_state["architecture"] = "C" st.session_state["selected_arch"] = "Avancée" st.session_state["first_time_advanced"] = False st.session_state["advanced_info_shown"] = True st.toast("Mode avancé activé") st.rerun() if col2.button("❌ Annuler"): st.session_state["selected_arch"] = "Intermédiaire" st.rerun() return else: st.session_state["architecture"] = "C" st.session_state["selected_arch"] = "Avancée" st.session_state["advanced_info_shown"] = True else: st.session_state["architecture"] = "A" if new_selection == "Basic" else "B" st.session_state["selected_arch"] = new_selection st.rerun() # Affichage du message d'information persistant if st.session_state["selected_arch"] == "Avancée" and st.session_state["advanced_info_shown"]: st.info("ℹ️ Vous utilisez actuellement l'architecture avancée. Cette option est optimisée pour la précision mais nécessite davantage de ressources.") display_sidebar() if st.session_state.chat_history: display_chat_history() query = st.chat_input("Posez votre question ici:") if query: if st.session_state.get("architecture") == "C": #with st.spinner("⏳ Activation de l'instance Neo4j AuraDB en cours..."): if is_neo4j_aura_active(3, 10): # 3 retries, 10 seconds each st.toast("✅ Neo4j AuraDB est actif.") process_query(query, "C") else: st.error("❌ Échec de l'activation de Neo4j. Veuillez réessayer plus tard.") else: process_query(query, st.session_state["architecture"]) def calculate_tokens_cost(): input_tokens = st.session_state['tokens_metrics'].get('input_tokens', 0) output_tokens = st.session_state['tokens_metrics'].get('output_tokens', 0) cost_input = (input_tokens / 1_000_000) * 1.80 cost_output = (output_tokens / 1_000_000) * 5.40 total_cost = round(cost_input + cost_output, 3) st.session_state['tokens_cost'] = total_cost def initialize_conversation(): # st.session_state["messages"] = [] st.session_state["chat_history"] = [] st.session_state["tokens_cost"] = 0 st.session_state['tokens_metrics'] = { 'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0 } if "k" not in st.session_state: st.session_state['k'] = 30 # Valeur par défaut pour k if "similarity_threshold" not in st.session_state: st.session_state['similarity_threshold'] = 0.7 # Valeur par défaut pour similarity_threshold if "alpha" not in st.session_state: st.session_state['alpha'] = 0.5 calculate_tokens_cost() if __name__ == "__main__": main()