Spaces:
Running
Running
File size: 15,272 Bytes
4e4a3e2 649bfa0 3e0117c d3f072a c880feb 10cc803 78d89bf 1ab6efe a594a20 94e2eeb d3f072a 94e2eeb d3f072a 94e2eeb 3e0117c 438d4f9 10cc803 6be2a9d 438d4f9 9760e1f 10cc803 438d4f9 cba7f8e 438d4f9 9760e1f 438d4f9 9760e1f 438d4f9 10cc803 94e2eeb 10cc803 6be2a9d a594a20 6be2a9d 438d4f9 6be2a9d 10cc803 a594a20 0b8887a 438d4f9 236cc74 a594a20 0b8887a 78d89bf 0b8887a 78d89bf a594a20 78d89bf a594a20 78d89bf 6be2a9d 94e2eeb e4d59e7 1205e0b e4d59e7 94e2eeb a594a20 0b8887a 438d4f9 9760e1f 438d4f9 9760e1f 438d4f9 0b8887a a594a20 0b8887a a594a20 0b8887a 236cc74 a594a20 94e2eeb edadca0 1ab6efe a594a20 c8f14dd a594a20 94e2eeb d3f072a 2abc46c c8f14dd e4d59e7 c8f14dd 236cc74 c8f14dd edadca0 c8f14dd edadca0 c8f14dd edadca0 c8f14dd edadca0 10cc803 edadca0 c8f14dd edadca0 c8f14dd edadca0 c8f14dd edadca0 c8f14dd 9760e1f 1ab6efe a594a20 9760e1f a594a20 9e46f5e 1ab6efe edadca0 c8f14dd edadca0 10cc803 a594a20 9760e1f a594a20 94e2eeb e4d59e7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
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("<h2 style='text-align: center;margin-top:20px;margin-bottom:20px;'>La confession muette (2025)</h2>", 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)**
<small>🛈 Détermine combien de documents seront récupérés lors de la recherche.</small>
""", 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**
<small>🛈 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.</small>
""", 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**
<small>🛈 Pour `alpha = 0.0`, la recherche est purement syntaxique. Pour `alpha = 1.0`, elle est purement sémantique.</small>
""", 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"""
<table style="width:100%">
<tr><td>Input Tokens</td><td style="text-align: right;"><b>{st.session_state['tokens_metrics'].get('input_tokens', 0)}</b></td></tr>
<tr><td>Output Tokens</td><td style="text-align: right;"><b>{st.session_state['tokens_metrics'].get('output_tokens', 0)}</b></td></tr>
<tr><td>Total Tokens</td><td style="text-align: right;"><b>{st.session_state['tokens_metrics'].get('total_tokens', 0)}</b></td></tr>
<tr><td>Coût</td><td style="text-align: right;"><b>{st.session_state['tokens_cost']} €</b></td></tr>
</table>
"""
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("""
<style>
div[data-testid="stSelectbox"] {
width: 200px !important;
}
</style>
""", 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() |