jaimalklyde commited on
Commit
1fc3fd2
·
verified ·
1 Parent(s): 63928cb

Update src/module/AIP.py

Browse files
Files changed (1) hide show
  1. src/module/AIP.py +106 -163
src/module/AIP.py CHANGED
@@ -1,7 +1,6 @@
1
  """
2
- MODULE JASMINE AGENT - V FINAL (CASCADE MULTI-MODÈLES + FUNCTIONS)
3
  ==================================================================
4
- Responsabilité : Traduire le langage naturel en actions + appeler des fonctions métier.
5
  """
6
  import google.generativeai as genai
7
  from groq import Groq
@@ -20,12 +19,45 @@ try:
20
  get_patient_stats
21
  )
22
  except ImportError as e:
23
- # Debug affiché seulement si l'import échoue
24
- st.error(f"Erreur interne dans AIP.py : impossible de trouver AIP_Function. {e}")
25
- # On crée des fonctions "bouchons" pour éviter que la classe ne plante si l'import échoue
26
  calculate_occupancy_rate = audit_data_quality = get_stock_alerts = get_patient_stats = lambda x: "Fonction non chargée"
27
 
28
- # [SUPPRIMÉ] L'import redondant qui faisait crasher a été retiré ici.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  class JasmineAgent:
31
  def __init__(self, rdf_store, ontology_rules, dataframes=None):
@@ -38,7 +70,7 @@ class JasmineAgent:
38
 
39
  self.rdf_store = rdf_store
40
  self.ontology_rules = ontology_rules
41
- self.dataframes = dataframes or {} # Dictionnaire des DataFrames disponibles
42
 
43
  self.MODEL_CASCADE = [
44
  "gemini-2.0-flash-exp",
@@ -47,7 +79,6 @@ class JasmineAgent:
47
  "llama-3.1-8b-instant"
48
  ]
49
 
50
- # Mapping des fonctions disponibles
51
  self.available_functions = {
52
  "calculate_occupancy_rate": calculate_occupancy_rate,
53
  "audit_data_quality": audit_data_quality,
@@ -57,224 +88,136 @@ class JasmineAgent:
57
 
58
  def build_base_system_prompt(self):
59
  return """
60
- ROLE: Tu es un agent intelligent qui analyse les requêtes utilisateur.
61
- MISSION: Comprendre l'intention et générer les actions appropriées.
62
 
63
- --- FONCTIONS DISPONIBLES ---
64
- 1. calculate_occupancy_rate : Calcule le taux d'occupation (nécessite df_ressources)
65
- 2. audit_data_quality : Analyse la qualité des données (nécessite un DataFrame avec colonnes dq_)
66
- 3. get_stock_alerts : Identifie les ruptures de stock (nécessite df_stock)
67
- 4. get_patient_stats : Statistiques démographiques patients (nécessite df_patients)
68
 
69
- CONTRAINTES DE SORTIE (JSON STRICT) :
70
  {
71
- "thought_trace": "1. ANALYSE: ... 2. EXTRACTION: ... 3. STRATÉGIE: ... 4. ACTION: ...",
72
- "tool": "calculate_occupancy_rate" | "audit_data_quality" | "get_stock_alerts" | "get_patient_stats" | "none",
73
- "args": { "dataframe_name": "df_ressources" }
74
  }
75
-
76
- IMPORTANT: Pour les fonctions de calcul, tu DOIS spécifier le nom du DataFrame dans args.
77
  """
78
 
79
  def execute_function(self, function_name, args):
80
- """Exécute une fonction métier avec les arguments fournis"""
81
  if function_name not in self.available_functions:
82
  return {"error": f"Fonction {function_name} inconnue"}
83
 
84
- # Récupération du DataFrame demandé
85
  df_name = args.get("dataframe_name")
86
  if not df_name or df_name not in self.dataframes:
87
- return {"error": f"DataFrame {df_name} non disponible. DataFrames disponibles: {list(self.dataframes.keys())}"}
88
 
89
- df = self.dataframes[df_name]
90
-
91
- # Exécution de la fonction
92
  try:
93
  func = self.available_functions[function_name]
94
- result = func(df)
95
  return {"success": True, "result": result}
96
  except Exception as e:
97
- return {"error": f"Erreur lors de l'exécution: {str(e)}"}
98
 
99
  def ask(self, user_message, chat_history):
100
- """Essaie les modèles en cascade jusqu'à obtenir une réponse valide"""
101
-
102
  system_prompt = self.build_base_system_prompt()
103
  last_error = None
104
 
105
  for model_name in self.MODEL_CASCADE:
106
  try:
107
- response_text = ""
108
-
109
  if "gemini" in model_name:
110
- if not self.google_key:
111
- continue
112
-
113
- msgs = self._format_messages_for_gemini(system_prompt, chat_history, user_message)
114
  model = genai.GenerativeModel(model_name)
115
- res = model.generate_content(
116
- msgs,
117
- generation_config={"response_mime_type": "application/json"}
118
- )
119
  response_text = res.text
120
-
121
- else: # Groq (Llama)
122
- if not self.groq_client:
123
- continue
124
-
125
- msgs = self._format_messages_for_groq(system_prompt, chat_history, user_message)
126
  completion = self.groq_client.chat.completions.create(
127
  model=model_name,
128
- messages=msgs,
129
- temperature=0.0,
130
  response_format={"type": "json_object"}
131
  )
132
  response_text = completion.choices[0].message.content
133
 
134
- # Nettoyage de la réponse
135
- clean_text = response_text.strip()
136
- clean_text = re.sub(r'```json\s*|\s*```', '', clean_text)
137
 
138
- # Tentative de parsing JSON
139
- try:
140
- action = json.loads(clean_text)
141
-
142
- # Récupération du Chain of Thought
143
- thought_trace = action.get("thought_trace", "")
144
-
145
- # Exécution de la fonction si demandée
146
- tool = action.get("tool")
147
- if tool and tool != "none":
148
- args = action.get("args", {})
149
- function_result = self.execute_function(tool, args)
150
-
151
- return "", action, thought_trace, function_result
152
-
153
- return "", action, thought_trace, None
154
-
155
- except json.JSONDecodeError:
156
- continue
157
 
 
158
  except Exception as e:
159
  last_error = str(e)
160
  continue
 
161
 
162
- # Si tous les modèles ont échoué
163
- return f"⚠️ ERREUR SYSTEME : {last_error}", None, "", None
164
-
165
- def _format_messages_for_groq(self, system_prompt, chat_history, user_message):
166
- """Formate les messages pour Groq (OpenAI format)"""
167
- msgs = [{"role": "system", "content": system_prompt}]
168
-
169
- for m in chat_history[-4:]:
170
- role = "assistant" if m["role"] in ["model", "assistant"] else "user"
171
- content = str(m.get("content", ""))
172
- if "🛠️" not in content and content.strip():
173
- msgs.append({"role": role, "content": content})
174
-
175
- msgs.append({"role": "user", "content": user_message})
176
- return msgs
177
-
178
- def _format_messages_for_gemini(self, system_prompt, chat_history, user_message):
179
- """Formate les messages pour Gemini"""
180
- msgs = [{"role": "user", "parts": [system_prompt]}]
181
-
182
- for m in chat_history[-4:]:
183
- role = "user" if m["role"] == "user" else "model"
184
- content = str(m.get("content", ""))
185
- if "🛠️" not in content and content.strip():
186
- msgs.append({"role": role, "parts": [content]})
187
-
188
- msgs.append({"role": "user", "parts": [user_message]})
189
- return msgs
190
-
191
- # --- 2. FONCTION D'AFFICHAGE (SHOW_TERMINAL) ---
192
  def show_terminal(gspread_client, sheet_name="bdd_mvp_faker"):
193
- """
194
- Fonction principale appelée par Streamlit pour afficher l'interface de l'agent
195
- """
196
  st.markdown("## 🤖 AIP COMMAND TERMINAL")
197
- st.caption("Agent Jasmine - Analyse Hospitalière Intelligente")
198
-
199
- # 1. Vérification des données (Lien avec Data Explorer)
200
- # On récupère les dataframes chargés dans le session_state par l'autre module
201
  dataframes = {}
202
  if 'data_explorer' in st.session_state:
203
  dataframes = st.session_state.data_explorer.dataframes
204
 
205
  if not dataframes:
206
- st.warning("⚠️ Aucune donnée n'est chargée en mémoire.")
207
- st.info("👉 Veuillez d'abord aller dans le menu **Data Explorer** et cliquer sur 'Charger les Données'.")
208
  return
209
 
210
- # 2. Initialisation de l'Agent (Singleton dans la session)
211
  if 'jasmine_agent' not in st.session_state:
212
- # On passe dataframes à l'agent pour qu'il puisse exécuter les fonctions
213
- st.session_state.jasmine_agent = JasmineAgent(
214
- rdf_store=None, # Pas encore utilisé
215
- ontology_rules=None, # Pas encore utilisé
216
- dataframes=dataframes
217
- )
218
 
219
- # 3. Initialisation de l'historique de chat
220
  if "messages" not in st.session_state:
221
  st.session_state.messages = []
222
 
223
- # 4. Affichage de l'historique
224
  for message in st.session_state.messages:
225
  with st.chat_message(message["role"]):
226
- st.markdown(message["content"])
227
- # Si un résultat de fonction est stocké, on l'affiche proprement
228
- if "function_result" in message and message["function_result"]:
229
- with st.expander("🛠️ Résultat technique"):
230
- st.json(message["function_result"])
 
 
 
 
 
 
 
 
231
 
232
- # 5. Zone de saisie utilisateur
233
- if prompt := st.chat_input("Ex: Calcule le taux d'occupation des lits"):
234
- # Ajout message utilisateur
235
  st.session_state.messages.append({"role": "user", "content": prompt})
236
- with st.chat_message("user"):
237
- st.markdown(prompt)
238
 
239
- # Réponse de l'agent
 
 
 
240
  with st.chat_message("assistant"):
241
- message_placeholder = st.empty()
242
- with st.spinner("Jasmine analyse la demande..."):
243
- # Appel à la méthode ask de la classe JasmineAgent
244
  err, action, thought, func_res = st.session_state.jasmine_agent.ask(
245
- prompt,
246
- st.session_state.messages
247
  )
248
 
249
  if err:
250
- st.error(err)
251
- response_text = "Désolé, une erreur technique est survenue."
252
  else:
253
- # Affichage de la pensée (Thought Trace)
254
- if thought:
255
- with st.expander("💭 Raisonnement de l'IA"):
256
- st.write(thought)
257
-
258
- # Construction de la réponse textuelle
259
  if action.get("tool") and action.get("tool") != "none":
260
- response_text = f"J'ai exécuté la commande `{action['tool']}` pour répondre à votre besoin."
261
  else:
262
- response_text = "Analyse terminée (aucune action technique requise)."
263
-
264
- message_placeholder.markdown(response_text)
265
-
266
- # Affichage du résultat de la fonction
267
- if func_res:
268
- if "success" in func_res:
269
- st.success("✅ Exécution réussie")
270
- st.json(func_res["result"])
271
- else:
272
- st.error(f"❌ Erreur fonction : {func_res.get('error')}")
273
-
274
- # Sauvegarde dans l'historique
275
- st.session_state.messages.append({
276
- "role": "assistant",
277
- "content": response_text,
278
- "thought": thought,
279
- "function_result": func_res
280
- })
 
1
  """
2
+ MODULE JASMINE AGENT - V FINAL (STYLE GOTHAM APPLIQUÉ)
3
  ==================================================================
 
4
  """
5
  import google.generativeai as genai
6
  from groq import Groq
 
19
  get_patient_stats
20
  )
21
  except ImportError as e:
22
+ st.error(f"Erreur interne : impossible de trouver AIP_Function. {e}")
 
 
23
  calculate_occupancy_rate = audit_data_quality = get_stock_alerts = get_patient_stats = lambda x: "Fonction non chargée"
24
 
25
+ # --- STYLE EXTRAIT DU CODE 01 (GOTHAM THEME) ---
26
+ def apply_custom_styles():
27
+ st.markdown("""
28
+ <style>
29
+ /* Chat Styling - Palantir Style */
30
+ .user-msg {
31
+ background: rgba(30, 136, 229, 0.1);
32
+ border-left: 3px solid #1E88E5;
33
+ padding: 12px; margin: 5px 0;
34
+ border-radius: 0 4px 4px 0;
35
+ font-family: 'Inter', sans-serif;
36
+ color: #c9d1d9;
37
+ }
38
+
39
+ .bot-msg {
40
+ background: rgba(48, 54, 61, 0.3);
41
+ border-left: 3px solid #00E676;
42
+ padding: 12px; margin: 5px 0;
43
+ border-radius: 0 4px 4px 0;
44
+ font-family: 'Inter', sans-serif;
45
+ color: #c9d1d9;
46
+ }
47
+
48
+ /* Debugger Log Style (Thought Trace) */
49
+ .debug-block {
50
+ font-family: 'JetBrains Mono', monospace;
51
+ font-size: 0.85rem;
52
+ color: #8b949e;
53
+ background: #0d1117;
54
+ border: 1px solid #30363d;
55
+ padding: 10px;
56
+ margin-top: 5px;
57
+ border-radius: 4px;
58
+ }
59
+ </style>
60
+ """, unsafe_allow_html=True)
61
 
62
  class JasmineAgent:
63
  def __init__(self, rdf_store, ontology_rules, dataframes=None):
 
70
 
71
  self.rdf_store = rdf_store
72
  self.ontology_rules = ontology_rules
73
+ self.dataframes = dataframes or {}
74
 
75
  self.MODEL_CASCADE = [
76
  "gemini-2.0-flash-exp",
 
79
  "llama-3.1-8b-instant"
80
  ]
81
 
 
82
  self.available_functions = {
83
  "calculate_occupancy_rate": calculate_occupancy_rate,
84
  "audit_data_quality": audit_data_quality,
 
88
 
89
  def build_base_system_prompt(self):
90
  return """
91
+ ROLE: Tu es Jasmine, un agent intelligent d'analyse hospitalière.
92
+ MISSION: Comprendre l'intention et générer les actions appropriées au format JSON.
93
 
94
+ --- FONCTIONS ---
95
+ 1. calculate_occupancy_rate (df_ressources)
96
+ 2. audit_data_quality (df avec colonnes dq_)
97
+ 3. get_stock_alerts (df_stock)
98
+ 4. get_patient_stats (df_patients)
99
 
100
+ FORMAT JSON STRICT :
101
  {
102
+ "thought_trace": "Analyse étape par étape...",
103
+ "tool": "nom_du_tool" | "none",
104
+ "args": { "dataframe_name": "nom_du_df" }
105
  }
 
 
106
  """
107
 
108
  def execute_function(self, function_name, args):
 
109
  if function_name not in self.available_functions:
110
  return {"error": f"Fonction {function_name} inconnue"}
111
 
 
112
  df_name = args.get("dataframe_name")
113
  if not df_name or df_name not in self.dataframes:
114
+ return {"error": f"Données {df_name} indisponibles."}
115
 
 
 
 
116
  try:
117
  func = self.available_functions[function_name]
118
+ result = func(self.dataframes[df_name])
119
  return {"success": True, "result": result}
120
  except Exception as e:
121
+ return {"error": str(e)}
122
 
123
  def ask(self, user_message, chat_history):
 
 
124
  system_prompt = self.build_base_system_prompt()
125
  last_error = None
126
 
127
  for model_name in self.MODEL_CASCADE:
128
  try:
 
 
129
  if "gemini" in model_name:
130
+ if not self.google_key: continue
 
 
 
131
  model = genai.GenerativeModel(model_name)
132
+ res = model.generate_content(user_message, generation_config={"response_mime_type": "application/json"})
 
 
 
133
  response_text = res.text
134
+ else:
135
+ if not self.groq_client: continue
 
 
 
 
136
  completion = self.groq_client.chat.completions.create(
137
  model=model_name,
138
+ messages=[{"role":"system","content":system_prompt},{"role":"user","content":user_message}],
 
139
  response_format={"type": "json_object"}
140
  )
141
  response_text = completion.choices[0].message.content
142
 
143
+ action = json.loads(re.sub(r'```json\s*|\s*```', '', response_text).strip())
144
+ thought_trace = action.get("thought_trace", "")
145
+ tool = action.get("tool")
146
 
147
+ func_res = None
148
+ if tool and tool != "none":
149
+ func_res = self.execute_function(tool, action.get("args", {}))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ return "", action, thought_trace, func_res
152
  except Exception as e:
153
  last_error = str(e)
154
  continue
155
+ return f"⚠️ Erreur : {last_error}", None, "", None
156
 
157
+ # --- 2. FONCTION D'AFFICHAGE MISE À JOUR ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  def show_terminal(gspread_client, sheet_name="bdd_mvp_faker"):
159
+ apply_custom_styles() # Application du style Gotham
160
+
 
161
  st.markdown("## 🤖 AIP COMMAND TERMINAL")
162
+
 
 
 
163
  dataframes = {}
164
  if 'data_explorer' in st.session_state:
165
  dataframes = st.session_state.data_explorer.dataframes
166
 
167
  if not dataframes:
168
+ st.warning("⚠️ Aucune donnée chargée. Allez dans le Data Explorer.")
 
169
  return
170
 
 
171
  if 'jasmine_agent' not in st.session_state:
172
+ st.session_state.jasmine_agent = JasmineAgent(None, None, dataframes=dataframes)
 
 
 
 
 
173
 
 
174
  if "messages" not in st.session_state:
175
  st.session_state.messages = []
176
 
177
+ # Affichage de l'historique avec les styles de Code 01
178
  for message in st.session_state.messages:
179
  with st.chat_message(message["role"]):
180
+ if message["role"] == "user":
181
+ st.markdown(f'<div class="user-msg">{message["content"]}</div>', unsafe_allow_html=True)
182
+ else:
183
+ # Bloc pensée (Chain of Thought)
184
+ if "thought" in message and message["thought"]:
185
+ with st.expander("🕵️ Jasmine Thought Trace", expanded=False):
186
+ st.markdown(f'<div class="debug-block">{message["thought"]}</div>', unsafe_allow_html=True)
187
+
188
+ st.markdown(f'<div class="bot-msg">{message["content"]}</div>', unsafe_allow_html=True)
189
+
190
+ if "function_result" in message and message["function_result"]:
191
+ with st.expander("🛠️ Résultat technique"):
192
+ st.json(message["function_result"])
193
 
194
+ # Zone de saisie
195
+ if prompt := st.chat_input("Ex: Analyse la qualité des données"):
 
196
  st.session_state.messages.append({"role": "user", "content": prompt})
197
+ st.rerun()
 
198
 
199
+ # Logique de réponse
200
+ if st.session_state.messages and st.session_state.messages[-1]["role"] == "user":
201
+ last_prompt = st.session_state.messages[-1]["content"]
202
+
203
  with st.chat_message("assistant"):
204
+ with st.spinner("Jasmine analyse..."):
 
 
205
  err, action, thought, func_res = st.session_state.jasmine_agent.ask(
206
+ last_prompt, st.session_state.messages[:-1]
 
207
  )
208
 
209
  if err:
210
+ response_text = f"Erreur système : {err}"
 
211
  else:
 
 
 
 
 
 
212
  if action.get("tool") and action.get("tool") != "none":
213
+ response_text = f"Action exécutée : `{action['tool']}`. Voici l'analyse des résultats."
214
  else:
215
+ response_text = "Analyse terminée. Comment puis-je vous aider davantage ?"
216
+
217
+ st.session_state.messages.append({
218
+ "role": "assistant",
219
+ "content": response_text,
220
+ "thought": thought,
221
+ "function_result": func_res
222
+ })
223
+ st.rerun()