datacipen commited on
Commit
5301792
·
verified ·
1 Parent(s): dad1b12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +185 -71
app.py CHANGED
@@ -57,19 +57,21 @@ def auth_callback(username: str, password: str) -> Optional[cl.User]:
57
  """
58
  # Exemple simple (à remplacer par votre logique)
59
  auth = json.loads(os.environ.get("CHAINLIT_AUTH_LOGIN"))
60
- ident = auth[0]['ident']
61
- pwd = auth[0]['pwd']
62
- resultLogAdmin = bcrypt.checkpw(username.encode('utf-8'), bcrypt.hashpw(ident.encode('utf-8'), bcrypt.gensalt()))
63
- resultPwdAdmin = bcrypt.checkpw(password.encode('utf-8'), bcrypt.hashpw(pwd.encode('utf-8'), bcrypt.gensalt()))
64
- resultRole = auth[0]['role']
65
-
66
- if resultLogAdmin == True and resultPwdAdmin == True and resultRole == "adminavid":
67
- return cl.User(
68
- identifier=pwd,
69
- metadata={"role": "adminavid", "provider": "credentials"}
70
- )
71
- else:
72
- return None
 
 
73
 
74
  @cl.data_layer
75
  def get_data_layer():
@@ -189,7 +191,6 @@ async def display_similar_info(similar_info: List[Dict[str, Any]]):
189
 
190
  # Créer un élément Chainlit
191
  element = cl.Text(
192
- name=f"similar_{db_name}",
193
  content="\n".join(content_parts),
194
  display="side"
195
  )
@@ -201,13 +202,55 @@ async def display_similar_info(similar_info: List[Dict[str, Any]]):
201
  elements=elements
202
  ).send()
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  # =============================================================================
205
  # FONCTION PRINCIPALE TRACÉE AVEC LANGSMITH
206
  # =============================================================================
207
 
208
  @traceable(name="agent_collaboratif_query", project_name=LANGSMITH_PROJECT)
209
  async def process_query_with_tracing(query: str, thread_id: str) -> Dict[str, Any]:
210
- """Traite la requête avec traçage LangSmith."""
211
 
212
  # Import du workflow
213
  from agent_collaboratif_avid import AgentState, create_agent_workflow
@@ -224,30 +267,58 @@ async def process_query_with_tracing(query: str, thread_id: str) -> Dict[str, An
224
  "final_response": "",
225
  "iteration_count": 0,
226
  "errors": [],
227
- "additional_information": []
 
228
  }
229
 
230
- # Analyse de la requête
231
- await send_cot_step("🔄 Démarrage", "Initialisation du workflow...", "running")
232
 
233
- final_state = await app.ainvoke(initial_state)
 
234
 
235
-
236
- # Affichage progressif
237
- if final_state.get("query_analysis"):
238
- await display_query_analysis(final_state["query_analysis"])
239
-
240
- if final_state.get("collected_information"):
241
- await send_cot_step("📊 Collecte d'informations", "Collecte d'informations...", "running")
242
- await display_collection(final_state["collected_information"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- if final_state.get("validation_results"):
245
- for idx, validation in enumerate(final_state["validation_results"], 1):
246
- await display_validation(validation, idx)
 
247
 
248
- # Informations similaires
249
- if final_state.get("additional_information"):
250
- await display_similar_info(final_state["additional_information"])
251
 
252
  result = {
253
  "query": query,
@@ -258,6 +329,7 @@ async def process_query_with_tracing(query: str, thread_id: str) -> Dict[str, An
258
  "iteration_count": final_state.get("iteration_count", 0),
259
  "errors": final_state.get("errors", []),
260
  "additional_information": final_state.get("additional_information", []),
 
261
  "sources_used": [
262
  info["database"]
263
  for info in final_state.get("collected_information", [])
@@ -271,41 +343,14 @@ async def process_query_with_tracing(query: str, thread_id: str) -> Dict[str, An
271
  # CALLBACKS CHAINLIT
272
  # =============================================================================
273
 
274
- #@cl.on_chat_start
275
- #async def start():
276
- # """Initialisation de la session chat."""
277
-
278
- # Message de bienvenue avec style
279
- # welcome_msg = f"""# 🎓 Agent Collaboratif - Université Gustave Eiffel
280
-
281
- #Bienvenue ! Je suis votre assistant spécialisé en **Ville Durable**.
282
-
283
- ## 🔧 Configuration
284
- #- **Index Pinecone:** `{PINECONE_INDEX_NAME}`
285
- #- **Modèle:** `{OPENAI_MODEL_NAME}`
286
- #- **Top K résultats:** `{SIMILARITY_TOP_K}`
287
- #- **Max validations:** `{MAX_VALIDATION_LOOPS}`
288
-
289
- ## 💡 Fonctionnalités
290
- #✅ Recherche multi-bases vectorielles
291
- #✅ Validation anti-hallucination
292
- #✅ Suggestions d'informations connexes
293
- #✅ Traçage LangSmith actif
294
-
295
- #**Choisissez un starter ou posez votre question !**
296
- #"""
297
-
298
- # await cl.Message(content=welcome_msg).send()
299
-
300
- # Sauvegarder les métadonnées de session
301
- # cl.user_session.set("session_started", True)
302
- # cl.user_session.set("query_count", 0)
303
-
304
- @cl.set_starters
305
- async def set_starters():
306
- """Configure les starters avec icônes."""
307
- #return [cl.Starter(label=s["label"], message=s["message"], icon=s["icon"]) for s in STARTERS]
308
  return [
 
 
 
 
 
309
  cl.Starter(
310
  label= "🔬 Laboratoires & Mobilité",
311
  message= "Quels sont les laboratoires de l'université Gustave Eiffel travaillant sur la mobilité urbaine durable?",
@@ -357,6 +402,49 @@ async def set_starters():
357
  #icon= "/public/icons/circular.svg"
358
  )
359
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  @cl.on_message
362
  async def main(message: cl.Message):
@@ -377,9 +465,29 @@ async def main(message: cl.Message):
377
  # Traitement avec affichage du COT
378
  result = await process_query_with_tracing(query, thread_id)
379
 
380
- # Réponse finale
381
  final_response = result["final_response"]
382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  # Métadonnées
384
  metadata_parts = [
385
  f"\n\n---\n### 📊 Métadonnées du traitement",
@@ -393,10 +501,12 @@ async def main(message: cl.Message):
393
 
394
  metadata_parts.append(f"**Requête n°:** {query_count}")
395
 
396
- full_response = final_response + "\n".join(metadata_parts)
 
 
397
 
398
- # Mise à jour du message
399
- processing_msg.content = full_response
400
  await processing_msg.update()
401
 
402
  # Sauvegarder dans l'historique de session
@@ -420,6 +530,10 @@ async def main(message: cl.Message):
420
  # comment=str(e)
421
  # )
422
 
 
 
 
 
423
  @cl.on_chat_resume
424
  async def on_chat_resume(thread: ThreadDict):
425
  """Reprise d'une conversation existante."""
 
57
  """
58
  # Exemple simple (à remplacer par votre logique)
59
  auth = json.loads(os.environ.get("CHAINLIT_AUTH_LOGIN"))
60
+
61
+ auth_iter = iter(auth)
62
+
63
+ while True:
64
+ # item will be "end" if iteration is complete
65
+ connexion = next(auth_iter, "end")
66
+ if bcrypt.checkpw(username.encode('utf-8'), bcrypt.hashpw(connexion['ident'].encode('utf-8'), bcrypt.gensalt())) == True and bcrypt.checkpw(password.encode('utf-8'), bcrypt.hashpw(connexion['pwd'].encode('utf-8'), bcrypt.gensalt())) == True:
67
+ print("OK")
68
+ return cl.User(
69
+ identifier=connexion['ident'],
70
+ metadata={"role": connexion['role'], "provider": "credentials"}
71
+ )
72
+ if connexion == "end":
73
+ break
74
+ return None
75
 
76
  @cl.data_layer
77
  def get_data_layer():
 
191
 
192
  # Créer un élément Chainlit
193
  element = cl.Text(
 
194
  content="\n".join(content_parts),
195
  display="side"
196
  )
 
202
  elements=elements
203
  ).send()
204
 
205
+ # =============================================================================
206
+ # FONCTIONS D'AFFICHAGE STREAMING PAR NŒUD
207
+ # =============================================================================
208
+
209
+ async def stream_response(content: str, msg: cl.Message, chunk_size: int = 50):
210
+ """Stream du contenu progressivement dans un message."""
211
+ for i in range(0, len(content), chunk_size):
212
+ chunk = content[i:i + chunk_size]
213
+ msg.content += chunk
214
+ await msg.update()
215
+ await asyncio.sleep(0.25) # Petit délai pour un effet visuel
216
+
217
+ async def display_node_update(node_name: str, state: Dict[str, Any]):
218
+ """Affiche les mises à jour d'état après l'exécution d'un nœud."""
219
+
220
+ if node_name == "analyze_query":
221
+ if state.get("query_analysis"):
222
+ await display_query_analysis(state["query_analysis"])
223
+
224
+ elif node_name == "collect_information":
225
+ if state.get("collected_information"):
226
+ await display_collection(state["collected_information"])
227
+
228
+ elif node_name == "generate_response":
229
+ if state.get("final_response"):
230
+ content = f"**Réponse générée** ({len(state['final_response'])} caractères)\n\nLa réponse complète sera affichée à la fin du workflow."
231
+ await send_cot_step("✏️ Génération de la réponse", content, "done")
232
+
233
+ elif node_name == "validate_response":
234
+ if state.get("validation_results"):
235
+ iteration = state.get("iteration_count", len(state["validation_results"]))
236
+ last_validation = state["validation_results"][-1]
237
+ await display_validation(last_validation, iteration)
238
+
239
+ elif node_name == "refine_response":
240
+ content = f"**Itération:** {state.get('iteration_count', 0)}\n**Correction en cours...**"
241
+ await send_cot_step("⚙️ Refinement", content, "done")
242
+
243
+ elif node_name == "collect_similar_information":
244
+ if state.get("additional_information"):
245
+ await display_similar_info(state["additional_information"])
246
+
247
  # =============================================================================
248
  # FONCTION PRINCIPALE TRACÉE AVEC LANGSMITH
249
  # =============================================================================
250
 
251
  @traceable(name="agent_collaboratif_query", project_name=LANGSMITH_PROJECT)
252
  async def process_query_with_tracing(query: str, thread_id: str) -> Dict[str, Any]:
253
+ """Traite la requête avec traçage LangSmith et streaming en temps réel."""
254
 
255
  # Import du workflow
256
  from agent_collaboratif_avid import AgentState, create_agent_workflow
 
267
  "final_response": "",
268
  "iteration_count": 0,
269
  "errors": [],
270
+ "additional_information": [],
271
+ "similar_info_response":""
272
  }
273
 
274
+ # Message de démarrage
275
+ await send_cot_step("🔄 Démarrage", "Initialisation du workflow LangGraph...", "done")
276
 
277
+ # Variables pour suivre l'état
278
+ final_state = None
279
 
280
+ # STREAMING: Utilisation de app.astream() pour obtenir les mises à jour après chaque nœud
281
+ try:
282
+ async for event in app.astream(initial_state):
283
+ # event est un dictionnaire avec les nœuds comme clés
284
+ for node_name, node_state in event.items():
285
+ # Ignorer le nœud spécial __start__
286
+ if node_name == "__start__":
287
+ continue
288
+
289
+ # Afficher un message de progression pour le nœud actuel
290
+ node_display_names = {
291
+ "analyze_query": "🔍 Analyse de la requête",
292
+ "collect_information": "📊 Collecte d'informations",
293
+ "generate_response": "✏️ Génération de la réponse",
294
+ "validate_response": "✅ Validation anti-hallucination",
295
+ "refine_response": "⚙️ Refinement de la réponse",
296
+ "collect_similar_information": "🔗 Collecte d'informations similaires"
297
+ }
298
+
299
+ display_name = node_display_names.get(node_name, f"⚙️ {node_name}")
300
+
301
+ # Message de progression
302
+ await send_cot_step(
303
+ f"🔄 {display_name}",
304
+ f"Nœud exécuté avec succès",
305
+ "done"
306
+ )
307
+
308
+ # Afficher les détails spécifiques du nœud
309
+ await display_node_update(node_name, node_state)
310
+
311
+ # Sauvegarder l'état final
312
+ final_state = node_state
313
 
314
+ except Exception as e:
315
+ error_msg = f"Erreur lors du streaming: {str(e)}"
316
+ await send_cot_step("❌ Erreur", error_msg, "error")
317
+ raise
318
 
319
+ # Si le streaming n'a pas retourné d'état final, utiliser la méthode classique
320
+ if final_state is None:
321
+ final_state = initial_state
322
 
323
  result = {
324
  "query": query,
 
329
  "iteration_count": final_state.get("iteration_count", 0),
330
  "errors": final_state.get("errors", []),
331
  "additional_information": final_state.get("additional_information", []),
332
+ "similar_info_response": final_state.get("similar_info_response", ""),
333
  "sources_used": [
334
  info["database"]
335
  for info in final_state.get("collected_information", [])
 
343
  # CALLBACKS CHAINLIT
344
  # =============================================================================
345
 
346
+ @cl.set_chat_profiles
347
+ async def chat_profile(current_user: cl.User):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  return [
349
+ cl.ChatProfile(
350
+ name="Avid Agent",
351
+ markdown_description="🎓 Avid Agent permet de converser avec un agent collaboratif entre 4 bases de données pour extraire les informations pertinentes afin de générer une réponse en réduisant les hallucations, par relecture et redéfinition des éléments.",
352
+ icon="/public/sparkles-gustaveia.png",
353
+ starters=[
354
  cl.Starter(
355
  label= "🔬 Laboratoires & Mobilité",
356
  message= "Quels sont les laboratoires de l'université Gustave Eiffel travaillant sur la mobilité urbaine durable?",
 
402
  #icon= "/public/icons/circular.svg"
403
  )
404
  ]
405
+ ),cl.ChatProfile(
406
+ name="Avid Dataviz",
407
+ markdown_description="💡 Avid Dataviz permet d'avoir recours à des éléments statistiques et de corrélation entre les données laboratoires et les thématiques Ville Durable",
408
+ )
409
+ ]
410
+
411
+
412
+ @cl.on_chat_start
413
+ async def start():
414
+ """Initialisation de la session chat."""
415
+ user = cl.user_session.get("user")
416
+ chat_profile = cl.user_session.get("chat_profile")
417
+ if chat_profile == "Avid Dataviz":
418
+ await cl.Message(
419
+ content=f"Bienvenue {user.identifier}!\n\nL'environnement {chat_profile} vous restitue les données sous forme d'objets statistiques."
420
+ ).send()
421
+
422
+ # Message de bienvenue avec style
423
+ # welcome_msg = f"""# 🎓 Agent Collaboratif - Université Gustave Eiffel
424
+
425
+ #Bienvenue ! Je suis votre assistant spécialisé en **Ville Durable**.
426
+
427
+ ## 🔧 Configuration
428
+ #- **Index Pinecone:** `{PINECONE_INDEX_NAME}`
429
+ #- **Modèle:** `{OPENAI_MODEL_NAME}`
430
+ #- **Top K résultats:** `{SIMILARITY_TOP_K}`
431
+ #- **Max validations:** `{MAX_VALIDATION_LOOPS}`
432
+
433
+ ## 💡 Fonctionnalités
434
+ #✅ Recherche multi-bases vectorielles
435
+ #✅ Validation anti-hallucination
436
+ #✅ Suggestions d'informations connexes
437
+ #✅ Traçage LangSmith actif
438
+
439
+ #**Choisissez un starter ou posez votre question !**
440
+ #"""
441
+
442
+ # await cl.Message(content=welcome_msg).send()
443
+
444
+ # Sauvegarder les métadonnées de session
445
+ # cl.user_session.set("session_started", True)
446
+ # cl.user_session.set("query_count", 0)
447
+
448
 
449
  @cl.on_message
450
  async def main(message: cl.Message):
 
465
  # Traitement avec affichage du COT
466
  result = await process_query_with_tracing(query, thread_id)
467
 
468
+ # Réponse finale en streaming
469
  final_response = result["final_response"]
470
 
471
+ # Afficher un séparateur
472
+ await send_cot_step("📝 Réponse finale", "Affichage de la réponse complète en streaming...", "done")
473
+
474
+ # Créer un nouveau message pour la réponse finale
475
+ response_msg = cl.Message(content="")
476
+ await response_msg.send()
477
+
478
+ # Streamer la réponse complète
479
+ await stream_response(final_response, response_msg, chunk_size=50)
480
+
481
+ # Afficher les informations similaires collectées par le nœud 6
482
+ if result.get("similar_info_response"):
483
+ similar_msg = cl.Message(content="")
484
+ await similar_msg.send()
485
+
486
+ # Streamer la réponse similaire
487
+ await stream_response(result["similar_info_response"], similar_msg, chunk_size=50)
488
+
489
+ #await display_similar_info(result["similar_info_response"])
490
+
491
  # Métadonnées
492
  metadata_parts = [
493
  f"\n\n---\n### 📊 Métadonnées du traitement",
 
501
 
502
  metadata_parts.append(f"**Requête n°:** {query_count}")
503
 
504
+ # Ajouter les métadonnées en streaming
505
+ metadata_text = "\n".join(metadata_parts)
506
+ await stream_response(metadata_text, response_msg, chunk_size=100)
507
 
508
+ # Supprimer le message de traitement initial vide
509
+ processing_msg.content = "✅ Traitement terminé"
510
  await processing_msg.update()
511
 
512
  # Sauvegarder dans l'historique de session
 
530
  # comment=str(e)
531
  # )
532
 
533
+ @cl.on_shared_thread_view
534
+ async def on_shared_thread_view(thread: ThreadDict, viewer: Optional[cl.User]) -> bool:
535
+ return True
536
+
537
  @cl.on_chat_resume
538
  async def on_chat_resume(thread: ThreadDict):
539
  """Reprise d'une conversation existante."""