Spaces:
Sleeping
Sleeping
Оновлено логіку парсингу захопленого тексту в MAIDxConversationLogger для покращення надійності. Додано обробку структурованих викликів функцій з JSON-аргументами. Внесено зміни в інтерфейс для коректного завершення сесій та покращення формату виводу. Виправлено помилки в обробці логів та оновлено документацію для відображення нових функцій.
Browse files- enhanced_mai_dx_logger.py +121 -88
- updated_mai_dx_interface.py +74 -78
enhanced_mai_dx_logger.py
CHANGED
@@ -177,114 +177,90 @@ class MAIDxConversationLogger:
|
|
177 |
raise e
|
178 |
|
179 |
def _parse_captured_output(self, case_id: str, captured_text: str):
|
180 |
-
"""Детальний парсинг захопленого тексту"""
|
181 |
session = self.sessions[case_id]
|
182 |
lines = captured_text.split('\n')
|
183 |
|
184 |
-
current_round = None
|
185 |
-
current_agent = None
|
186 |
-
buffer = []
|
187 |
in_agent_output = False
|
188 |
round_number = 0
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
if
|
|
|
|
|
|
|
|
|
|
|
193 |
continue
|
194 |
-
|
195 |
-
#
|
196 |
if "Starting Diagnostic Loop" in line:
|
197 |
-
|
198 |
-
|
199 |
-
self._save_buffered_conversation(session, current_round, buffer)
|
200 |
-
buffer = []
|
201 |
-
|
202 |
-
# Витягуємо номер раунду
|
203 |
round_match = re.search(r'Starting Diagnostic Loop (\d+)/\d+', line)
|
204 |
-
if round_match
|
205 |
-
|
206 |
-
else:
|
207 |
-
round_number += 1
|
208 |
-
|
209 |
current_round = AgentConversation(
|
210 |
round_number=round_number,
|
211 |
start_time=datetime.now().isoformat()
|
212 |
)
|
213 |
session.conversations.append(current_round)
|
214 |
continue
|
215 |
-
|
216 |
-
#
|
217 |
if "Agent Name" in line and ("╭" in line or "┌" in line):
|
218 |
-
|
219 |
-
agent_match = re.search(r'Agent Name (\w+\.?\s*\w+)', line)
|
220 |
if agent_match:
|
221 |
-
if buffer and current_agent and current_round:
|
222 |
-
# Зберігаємо повідомлення попереднього агента
|
223 |
-
self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
|
224 |
-
buffer = []
|
225 |
current_agent = agent_match.group(1).strip()
|
226 |
in_agent_output = True
|
|
|
227 |
continue
|
228 |
-
|
229 |
-
#
|
230 |
-
|
231 |
-
in_agent_output = False
|
232 |
if buffer and current_agent and current_round:
|
233 |
self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
|
234 |
-
buffer = []
|
235 |
-
current_agent = None
|
236 |
-
continue
|
237 |
|
238 |
-
|
239 |
-
|
240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
clean_line = re.sub(r'^[│|]\s*', '', line)
|
242 |
-
clean_line = re.sub(r'\s*[│|]\s
|
243 |
-
if clean_line
|
244 |
-
buffer.append(clean_line
|
245 |
continue
|
246 |
-
|
247 |
-
#
|
248 |
-
|
249 |
-
|
250 |
-
info_match
|
251 |
-
if info_match and current_round:
|
252 |
action = info_match.group(1)
|
253 |
-
content = info_match.group(2)
|
254 |
|
255 |
-
#
|
256 |
-
agent_name = self._extract_agent_from_log(content)
|
257 |
-
if agent_name:
|
258 |
-
self._add_agent_message(
|
259 |
-
current_round,
|
260 |
-
agent_name,
|
261 |
-
content,
|
262 |
-
message_type="log"
|
263 |
-
)
|
264 |
-
|
265 |
-
# Спеціальні маркери
|
266 |
if "Panel decision:" in content:
|
267 |
-
decision_match = re.search(r'Panel decision: (\w+) -> (
|
268 |
if decision_match:
|
269 |
current_round.decision = f"{decision_match.group(1)}: {decision_match.group(2)}"
|
270 |
|
271 |
-
|
272 |
cost_match = re.search(r'Current cost: \$(\d+(?:\.\d+)?)', content)
|
273 |
if cost_match:
|
274 |
current_round.cost_incurred = float(cost_match.group(1))
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
# Зберігаємо останній буфер
|
281 |
-
if buffer and current_round and current_agent:
|
282 |
-
self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
|
283 |
-
|
284 |
-
# Завершуємо останній раунд
|
285 |
-
if current_round:
|
286 |
-
current_round.end_time = datetime.now().isoformat()
|
287 |
-
|
288 |
def _extract_agent_from_log(self, log_content: str) -> Optional[str]:
|
289 |
"""Витягти ім'я агента з лог повідомлення"""
|
290 |
agent_patterns = {
|
@@ -340,23 +316,80 @@ class MAIDxConversationLogger:
|
|
340 |
return float(match.group(1)) if match else None
|
341 |
|
342 |
def _add_agent_message(self, conversation: AgentConversation, agent_name: str, content: str, message_type: Optional[str] = None):
|
343 |
-
"""Додати повідомлення агента до розмови"""
|
|
|
|
|
|
|
344 |
if not conversation or not content.strip():
|
345 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
346 |
|
347 |
-
|
348 |
-
|
349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
350 |
message = AgentMessage(
|
351 |
timestamp=datetime.now().isoformat(),
|
352 |
agent_name=agent_name,
|
353 |
message_type=message_type,
|
354 |
-
content=
|
355 |
-
metadata={}
|
356 |
)
|
357 |
|
358 |
conversation.messages.append(message)
|
359 |
-
|
360 |
def _determine_message_type(self, agent_name: str, content: str) -> str:
|
361 |
"""Визначити тип повідомлення"""
|
362 |
content_lower = content.lower()
|
@@ -376,10 +409,10 @@ class MAIDxConversationLogger:
|
|
376 |
conversation.end_time = datetime.now().isoformat()
|
377 |
# Можна додати додаткову обробку
|
378 |
|
379 |
-
|
380 |
-
|
381 |
if case_id not in self.sessions:
|
382 |
-
return
|
383 |
|
384 |
session = self.sessions[case_id]
|
385 |
|
@@ -396,9 +429,9 @@ class MAIDxConversationLogger:
|
|
396 |
|
397 |
# Зберігаємо у файл
|
398 |
self._save_session_to_file(session)
|
399 |
-
|
400 |
-
del self.sessions[case_id]
|
401 |
-
return
|
402 |
|
403 |
def _save_session_to_file(self, session: DiagnosisSession):
|
404 |
"""Зберегти сесію у JSON файл з повною структурою"""
|
|
|
177 |
raise e
|
178 |
|
179 |
def _parse_captured_output(self, case_id: str, captured_text: str):
|
180 |
+
"""Детальний парсинг захопленого тексту з надійною логікою."""
|
181 |
session = self.sessions[case_id]
|
182 |
lines = captured_text.split('\n')
|
183 |
|
184 |
+
current_round: Optional[AgentConversation] = None
|
185 |
+
current_agent: Optional[str] = None
|
186 |
+
buffer: List[str] = []
|
187 |
in_agent_output = False
|
188 |
round_number = 0
|
189 |
+
|
190 |
+
def finalize_round(round_obj: Optional[AgentConversation]):
|
191 |
+
"""Завершити поточний раунд, якщо він існує."""
|
192 |
+
if round_obj and not round_obj.end_time:
|
193 |
+
round_obj.end_time = datetime.now().isoformat()
|
194 |
+
|
195 |
+
for line in lines:
|
196 |
+
stripped_line = line.strip()
|
197 |
+
if not stripped_line or "rich.errors.NotRenderableError" in line or "Exception in thread" in line:
|
198 |
continue
|
199 |
+
|
200 |
+
# 1. Перевірка на початок нового раунду
|
201 |
if "Starting Diagnostic Loop" in line:
|
202 |
+
finalize_round(current_round) # Завершуємо попередній раунд
|
203 |
+
|
|
|
|
|
|
|
|
|
204 |
round_match = re.search(r'Starting Diagnostic Loop (\d+)/\d+', line)
|
205 |
+
round_number = int(round_match.group(1)) if round_match else round_number + 1
|
206 |
+
|
|
|
|
|
|
|
207 |
current_round = AgentConversation(
|
208 |
round_number=round_number,
|
209 |
start_time=datetime.now().isoformat()
|
210 |
)
|
211 |
session.conversations.append(current_round)
|
212 |
continue
|
213 |
+
|
214 |
+
# 2. Перевірка на початок блоку агента
|
215 |
if "Agent Name" in line and ("╭" in line or "┌" in line):
|
216 |
+
agent_match = re.search(r'Agent Name (.*?) \[', line)
|
|
|
217 |
if agent_match:
|
|
|
|
|
|
|
|
|
218 |
current_agent = agent_match.group(1).strip()
|
219 |
in_agent_output = True
|
220 |
+
buffer = [] # Очищуємо буфер для нового агента
|
221 |
continue
|
222 |
+
|
223 |
+
# 3. Перевірка на кінець блоку агента
|
224 |
+
elif in_agent_output and ("╰" in line or "└" in line):
|
|
|
225 |
if buffer and current_agent and current_round:
|
226 |
self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
|
|
|
|
|
|
|
227 |
|
228 |
+
# Скидаємо стан
|
229 |
+
in_agent_output = False
|
230 |
+
current_agent = None
|
231 |
+
buffer = []
|
232 |
+
continue
|
233 |
+
|
234 |
+
# 4. Збір вмісту всередині блоку агента
|
235 |
+
elif in_agent_output and current_agent:
|
236 |
+
# Очищуємо лінію від символів рамки
|
237 |
clean_line = re.sub(r'^[│|]\s*', '', line)
|
238 |
+
clean_line = re.sub(r'\s*[│|]\s*$', '', clean_line) # Видаляємо символ і в кінці
|
239 |
+
if clean_line: # Додаємо лише непусті рядки
|
240 |
+
buffer.append(clean_line)
|
241 |
continue
|
242 |
+
|
243 |
+
# 5. Обробка звичайних логів INFO (якщо ми не всередині блоку агента)
|
244 |
+
elif " | INFO " in line and current_round:
|
245 |
+
info_match = re.search(r'mai_dx\.main:([^:]+):.*? - (.*)', line)
|
246 |
+
if info_match:
|
|
|
247 |
action = info_match.group(1)
|
248 |
+
content = info_match.group(2).strip()
|
249 |
|
250 |
+
# Обробка ключових INFO-повідомлень
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
251 |
if "Panel decision:" in content:
|
252 |
+
decision_match = re.search(r'Panel decision: (\w+) -> (.*)', content)
|
253 |
if decision_match:
|
254 |
current_round.decision = f"{decision_match.group(1)}: {decision_match.group(2)}"
|
255 |
|
256 |
+
elif "Current cost:" in content:
|
257 |
cost_match = re.search(r'Current cost: \$(\d+(?:\.\d+)?)', content)
|
258 |
if cost_match:
|
259 |
current_round.cost_incurred = float(cost_match.group(1))
|
260 |
+
|
261 |
+
# Завершуємо останній раунд після виходу з циклу
|
262 |
+
finalize_round(current_round)
|
263 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
def _extract_agent_from_log(self, log_content: str) -> Optional[str]:
|
265 |
"""Витягти ім'я агента з лог повідомлення"""
|
266 |
agent_patterns = {
|
|
|
316 |
return float(match.group(1)) if match else None
|
317 |
|
318 |
def _add_agent_message(self, conversation: AgentConversation, agent_name: str, content: str, message_type: Optional[str] = None):
|
319 |
+
"""Додати повідомлення агента до розмови з надійним парсингом JSON."""
|
320 |
+
import re
|
321 |
+
import json
|
322 |
+
|
323 |
if not conversation or not content.strip():
|
324 |
return
|
325 |
+
|
326 |
+
formatted_content = ""
|
327 |
+
original_content = content
|
328 |
+
|
329 |
+
# Перевіряємо, чи це структурований вивід з викликом функції
|
330 |
+
if "Structured Output - Attempting Function Call Execution" in content:
|
331 |
+
message_type = "function_call"
|
332 |
|
333 |
+
func_name_match = re.search(r"name:\s*(\w+)", content)
|
334 |
+
func_name = func_name_match.group(1) if func_name_match else "unknown_function"
|
335 |
+
|
336 |
+
# Надійний regex для витягнення JSON, що працює з багаторядковим текстом
|
337 |
+
# і стандартним модулем 're'. Він шукає блок, що починається з "arguments: {"
|
338 |
+
# і закінчується на "}" перед "name:".
|
339 |
+
args_match = re.search(r"arguments:\s*(\{.*?\})\s*,\s*\"name\":", content, re.DOTALL)
|
340 |
+
# Альтернативний варіант, якщо "name" не знайдено
|
341 |
+
if not args_match:
|
342 |
+
args_match = re.search(r"arguments:\s*(\{.*\})", content, re.DOTALL)
|
343 |
+
|
344 |
+
|
345 |
+
formatted_content = f"🤖 **Дія:** Виклик функції `{func_name}`\n\n"
|
346 |
+
|
347 |
+
if args_match:
|
348 |
+
try:
|
349 |
+
json_str = args_match.group(1)
|
350 |
+
args_data = json.loads(json_str)
|
351 |
+
|
352 |
+
if func_name in ['update_differential_diagnosis', 'UpdateDifferentialDiagnosisDifferentialDiagnoses']:
|
353 |
+
formatted_content += f"**Резюме:** {args_data.get('summary', 'N/A')}\n"
|
354 |
+
if 'differential_diagnoses' in args_data:
|
355 |
+
formatted_content += "**Основний діагноз:**\n"
|
356 |
+
for diag in args_data.get('differential_diagnoses', []):
|
357 |
+
prob = diag.get('probability', 0) * 100
|
358 |
+
formatted_content += f"- {diag.get('diagnosis')} ({prob:.0f}%)\n"
|
359 |
+
else: # Обробка іншого формату
|
360 |
+
prob = args_data.get('probability', 0) * 100
|
361 |
+
formatted_content += f"- {args_data.get('diagnosis')} ({prob:.0f}%)\n"
|
362 |
+
|
363 |
+
|
364 |
+
elif func_name == 'make_consensus_decision':
|
365 |
+
formatted_content += f"**Рішення:** {args_data.get('content', 'N/A')}\n"
|
366 |
+
formatted_content += f"**Обґрунтування:** {args_data.get('reasoning', 'N/A')}"
|
367 |
+
|
368 |
+
else:
|
369 |
+
formatted_content += "**Аргументи:**\n"
|
370 |
+
formatted_content += f"```json\n{json.dumps(args_data, indent=2, ensure_ascii=False)}\n```"
|
371 |
+
|
372 |
+
except json.JSONDecodeError:
|
373 |
+
formatted_content += "Помилка парсингу JSON аргументів."
|
374 |
+
else:
|
375 |
+
formatted_content += "Не вдалося витягти аргументи з блоку."
|
376 |
+
|
377 |
+
else:
|
378 |
+
# Це звичайне текстове повідомлення
|
379 |
+
formatted_content = content.strip()
|
380 |
+
if not message_type:
|
381 |
+
message_type = self._determine_message_type(agent_name, formatted_content)
|
382 |
+
|
383 |
message = AgentMessage(
|
384 |
timestamp=datetime.now().isoformat(),
|
385 |
agent_name=agent_name,
|
386 |
message_type=message_type,
|
387 |
+
content=formatted_content.strip(),
|
388 |
+
metadata={'raw_content': original_content}
|
389 |
)
|
390 |
|
391 |
conversation.messages.append(message)
|
392 |
+
|
393 |
def _determine_message_type(self, agent_name: str, content: str) -> str:
|
394 |
"""Визначити тип повідомлення"""
|
395 |
content_lower = content.lower()
|
|
|
409 |
conversation.end_time = datetime.now().isoformat()
|
410 |
# Можна додати додаткову обробку
|
411 |
|
412 |
+
# У файлі enhanced_mai_dx_logger.py
|
413 |
+
def end_session(self, case_id: str, **kwargs) -> Optional[DiagnosisSession]:
|
414 |
if case_id not in self.sessions:
|
415 |
+
return None
|
416 |
|
417 |
session = self.sessions[case_id]
|
418 |
|
|
|
429 |
|
430 |
# Зберігаємо у файл
|
431 |
self._save_session_to_file(session)
|
432 |
+
|
433 |
+
del self.sessions[case_id]
|
434 |
+
return session
|
435 |
|
436 |
def _save_session_to_file(self, session: DiagnosisSession):
|
437 |
"""Зберегти сесію у JSON файл з повною структурою"""
|
updated_mai_dx_interface.py
CHANGED
@@ -37,7 +37,7 @@ except ImportError:
|
|
37 |
IMPORT_ERROR = str(e)
|
38 |
|
39 |
# Імпорт покращеного логгера
|
40 |
-
from enhanced_mai_dx_logger import MAIDxConversationLogger, DiagnosisSession
|
41 |
|
42 |
# Перевірка доступності Plotly
|
43 |
try:
|
@@ -111,17 +111,12 @@ class UpdatedMAIDXInterface:
|
|
111 |
conversation_log = ""
|
112 |
structured_conversations = ""
|
113 |
case_id = None
|
114 |
-
|
115 |
-
|
116 |
-
original_show_panel = os.environ.get("SWARMS_SHOW_PANEL", "false")
|
117 |
-
original_auto_print = os.environ.get("SWARMS_AUTO_PRINT", "false")
|
118 |
-
os.environ["SWARMS_SHOW_PANEL"] = "false"
|
119 |
-
os.environ["SWARMS_AUTO_PRINT"] = "false"
|
120 |
-
|
121 |
try:
|
122 |
progress(0.1, desc="🚀 Ініціалізація...")
|
123 |
|
124 |
-
# Створюємо сесію
|
125 |
if enable_logging:
|
126 |
case_id = self.conversation_logger.start_session(
|
127 |
case_name=case_name,
|
@@ -129,7 +124,7 @@ class UpdatedMAIDXInterface:
|
|
129 |
mode=mode,
|
130 |
budget=budget
|
131 |
)
|
132 |
-
conversation_log += f"📝 Розпочато
|
133 |
|
134 |
progress(0.2, desc="🤖 Створення AI-панелі...")
|
135 |
|
@@ -144,7 +139,7 @@ class UpdatedMAIDXInterface:
|
|
144 |
progress(0.3, desc="🔍 Запуск діагностики...")
|
145 |
start_time = time.time()
|
146 |
|
147 |
-
# Виконуємо діагностику з повним захопленням
|
148 |
if enable_logging and case_id:
|
149 |
result = self.conversation_logger.capture_orchestrator_output(
|
150 |
case_id=case_id,
|
@@ -153,31 +148,6 @@ class UpdatedMAIDXInterface:
|
|
153 |
full_case_details=patient_info,
|
154 |
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
155 |
)
|
156 |
-
|
157 |
-
# Отримуємо захоплений вивід
|
158 |
-
session = self.conversation_logger.sessions.get(case_id)
|
159 |
-
if session:
|
160 |
-
conversation_log += "🤖 Повний лог виконання:\n" + "="*60 + "\n"
|
161 |
-
conversation_log += session.raw_output + "\n" + "="*60 + "\n\n"
|
162 |
-
|
163 |
-
# Оновлюємо метрики з розмов
|
164 |
-
for conv in session.conversations:
|
165 |
-
self.current_metrics.add_conversation_round({
|
166 |
-
'round': conv.round_number,
|
167 |
-
'agents': len(set(msg.agent_name for msg in conv.messages)),
|
168 |
-
'messages': len(conv.messages),
|
169 |
-
'cost': conv.cost_incurred
|
170 |
-
})
|
171 |
-
|
172 |
-
# Оновлюємо активність агентів
|
173 |
-
for msg in conv.messages:
|
174 |
-
self.current_metrics.update_agent_activity(msg.agent_name)
|
175 |
-
|
176 |
-
# Додаємо точки вартості
|
177 |
-
if conv.cost_incurred > 0:
|
178 |
-
elapsed_time = (datetime.fromisoformat(conv.end_time or conv.start_time) -
|
179 |
-
datetime.fromisoformat(session.timestamp)).total_seconds()
|
180 |
-
self.current_metrics.add_cost_point(conv.cost_incurred)
|
181 |
else:
|
182 |
# Виконуємо без логування
|
183 |
result = orchestrator.run(
|
@@ -189,48 +159,73 @@ class UpdatedMAIDXInterface:
|
|
189 |
duration = time.time() - start_time
|
190 |
progress(0.9, desc="📊 Обробка результатів...")
|
191 |
|
192 |
-
# Завершуємо сесію
|
193 |
if enable_logging and case_id:
|
194 |
-
|
195 |
case_id=case_id,
|
196 |
final_diagnosis=result.final_diagnosis,
|
197 |
confidence=result.accuracy_score,
|
198 |
cost=result.total_cost,
|
199 |
reasoning=getattr(result, 'accuracy_reasoning', 'N/A')
|
200 |
)
|
201 |
-
conversation_log += f"💾 Сесію збережено як: {saved_case_id}\n"
|
202 |
-
|
203 |
-
# Генеруємо HTML звіт
|
204 |
-
html_path = self.conversation_logger.export_conversation_html(case_id)
|
205 |
-
conversation_log += f"📄 HTML звіт: {html_path}\n"
|
206 |
-
|
207 |
-
# Форматуємо структуровані розмови
|
208 |
-
structured_conversations = self._format_structured_conversations(case_id)
|
209 |
|
210 |
-
#
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
|
|
227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
progress(1.0, desc="✅ Готово!")
|
229 |
|
230 |
# Створюємо візуалізації
|
231 |
metrics_plot = self._create_enhanced_metrics_visualization()
|
232 |
agent_plot = self._create_conversation_flow_chart()
|
233 |
|
|
|
234 |
return (
|
235 |
self._format_main_result(session),
|
236 |
self._format_detailed_analysis(session),
|
@@ -242,20 +237,15 @@ class UpdatedMAIDXInterface:
|
|
242 |
)
|
243 |
|
244 |
except Exception as e:
|
245 |
-
|
|
|
|
|
246 |
if case_id:
|
247 |
-
error_msg += f"\n🗂️
|
248 |
-
return self._format_error(error_msg)
|
249 |
-
|
250 |
-
finally:
|
251 |
-
# Відновлюємо оригінальні налаштування
|
252 |
-
os.environ["SWARMS_SHOW_PANEL"] = original_show_panel
|
253 |
-
os.environ["SWARMS_AUTO_PRINT"] = original_auto_print
|
254 |
|
255 |
-
def _format_structured_conversations(self,
|
256 |
"""Форматує структуровані розмови у читабельний вигляд"""
|
257 |
-
conversations = self.conversation_logger.get_session_conversations(case_id)
|
258 |
-
|
259 |
if not conversations:
|
260 |
return "📭 Розмови не знайдено"
|
261 |
|
@@ -263,19 +253,25 @@ class UpdatedMAIDXInterface:
|
|
263 |
|
264 |
for conv in conversations:
|
265 |
output += f"### 🔄 Раунд {conv.round_number}\n"
|
266 |
-
|
|
|
267 |
|
268 |
for msg in conv.messages:
|
269 |
emoji = {
|
|
|
270 |
'reasoning': '🤔',
|
271 |
'decision': '💡',
|
272 |
'input': '❓',
|
273 |
'output': '📊'
|
274 |
}.get(msg.message_type, '💬')
|
275 |
|
276 |
-
output += f"{emoji} **{msg.agent_name}** [{msg.message_type}]
|
277 |
-
|
278 |
-
|
|
|
|
|
|
|
|
|
279 |
if conv.decision:
|
280 |
output += f"**🎯 Рішення:** {conv.decision}\n"
|
281 |
|
|
|
37 |
IMPORT_ERROR = str(e)
|
38 |
|
39 |
# Імпорт покращеного логгера
|
40 |
+
from enhanced_mai_dx_logger import MAIDxConversationLogger, DiagnosisSession, AgentConversation
|
41 |
|
42 |
# Перевірка доступності Plotly
|
43 |
try:
|
|
|
111 |
conversation_log = ""
|
112 |
structured_conversations = ""
|
113 |
case_id = None
|
114 |
+
session: Optional[DiagnosisSession] = None
|
115 |
+
|
|
|
|
|
|
|
|
|
|
|
116 |
try:
|
117 |
progress(0.1, desc="🚀 Ініціалізація...")
|
118 |
|
119 |
+
# Створюємо сесію логування, якщо увімкнено
|
120 |
if enable_logging:
|
121 |
case_id = self.conversation_logger.start_session(
|
122 |
case_name=case_name,
|
|
|
124 |
mode=mode,
|
125 |
budget=budget
|
126 |
)
|
127 |
+
conversation_log += f"📝 Розпочато логування сесії: {case_id}\n\n"
|
128 |
|
129 |
progress(0.2, desc="🤖 Створення AI-панелі...")
|
130 |
|
|
|
139 |
progress(0.3, desc="🔍 Запуск діагностики...")
|
140 |
start_time = time.time()
|
141 |
|
142 |
+
# Виконуємо діагностику з повним захопленням або без
|
143 |
if enable_logging and case_id:
|
144 |
result = self.conversation_logger.capture_orchestrator_output(
|
145 |
case_id=case_id,
|
|
|
148 |
full_case_details=patient_info,
|
149 |
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
150 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
else:
|
152 |
# Виконуємо без логування
|
153 |
result = orchestrator.run(
|
|
|
159 |
duration = time.time() - start_time
|
160 |
progress(0.9, desc="📊 Обробка результатів...")
|
161 |
|
162 |
+
# Завершуємо сесію і отримуємо фінальний, заповнений об'єкт сесії
|
163 |
if enable_logging and case_id:
|
164 |
+
session = self.conversation_logger.end_session(
|
165 |
case_id=case_id,
|
166 |
final_diagnosis=result.final_diagnosis,
|
167 |
confidence=result.accuracy_score,
|
168 |
cost=result.total_cost,
|
169 |
reasoning=getattr(result, 'accuracy_reasoning', 'N/A')
|
170 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
+
# Якщо логування було вимкнене або щось пішло не так,
|
173 |
+
# створюємо об'єкт сесії вручну для відображення
|
174 |
+
if not session:
|
175 |
+
session = DiagnosisSession(
|
176 |
+
case_id=case_id or "no_logging",
|
177 |
+
timestamp=datetime.now().isoformat(),
|
178 |
+
case_name=case_name,
|
179 |
+
patient_info=patient_info,
|
180 |
+
mode=mode,
|
181 |
+
budget=budget,
|
182 |
+
diagnosis=result.final_diagnosis,
|
183 |
+
confidence=result.accuracy_score,
|
184 |
+
cost=result.total_cost,
|
185 |
+
iterations=getattr(result, 'iterations', 0),
|
186 |
+
duration=duration,
|
187 |
+
status="✅ Успішно" if result.accuracy_score >= 3.0 else "⚠️ Потребує перегляду",
|
188 |
+
reasoning=getattr(result, 'accuracy_reasoning', 'N/A')
|
189 |
+
)
|
190 |
|
191 |
+
# Тепер, коли у нас є гарантовано існуючий об'єкт session,
|
192 |
+
# ми можемо заповнити решту даних для виводу
|
193 |
+
if session:
|
194 |
+
self.sessions_history.append(session)
|
195 |
+
|
196 |
+
# Додаємо сирий вивід у лог інтерфейсу
|
197 |
+
conversation_log += "🤖 Повний сирий вивід системи:\n" + "="*60 + "\n"
|
198 |
+
conversation_log += session.raw_output + "\n" + "="*60 + "\n\n"
|
199 |
+
conversation_log += f"💾 Сесію збережено як: {session.case_id}\n"
|
200 |
+
|
201 |
+
# Генеруємо HTML звіт і додаємо посилання
|
202 |
+
if enable_logging:
|
203 |
+
html_path = self.conversation_logger.export_conversation_html(session.case_id)
|
204 |
+
conversation_log += f"📄 HTML звіт: {html_path}\n"
|
205 |
+
|
206 |
+
# Форматуємо структуровані розмови, передаючи список напряму
|
207 |
+
structured_conversations = self._format_structured_conversations(session.conversations)
|
208 |
+
|
209 |
+
# Оновлюємо метрики з розмов
|
210 |
+
for conv in session.conversations:
|
211 |
+
self.current_metrics.add_conversation_round({
|
212 |
+
'round': conv.round_number,
|
213 |
+
'agents': len(set(msg.agent_name for msg in conv.messages)),
|
214 |
+
'messages': len(conv.messages),
|
215 |
+
'cost': conv.cost_incurred
|
216 |
+
})
|
217 |
+
for msg in conv.messages:
|
218 |
+
self.current_metrics.update_agent_activity(msg.agent_name)
|
219 |
+
if conv.cost_incurred > 0:
|
220 |
+
self.current_metrics.add_cost_point(conv.cost_incurred)
|
221 |
+
|
222 |
progress(1.0, desc="✅ Готово!")
|
223 |
|
224 |
# Створюємо візуалізації
|
225 |
metrics_plot = self._create_enhanced_metrics_visualization()
|
226 |
agent_plot = self._create_conversation_flow_chart()
|
227 |
|
228 |
+
# Передаємо ПРАВИЛЬНИЙ об'єкт сесії у форматери
|
229 |
return (
|
230 |
self._format_main_result(session),
|
231 |
self._format_detailed_analysis(session),
|
|
|
237 |
)
|
238 |
|
239 |
except Exception as e:
|
240 |
+
import traceback
|
241 |
+
traceback.print_exc() # Друкуємо повний traceback у консоль для налагодження
|
242 |
+
error_msg = f"❌ Критична помилка діагностики: {str(e)}"
|
243 |
if case_id:
|
244 |
+
error_msg += f"\n🗂️ ID сесії: {case_id}"
|
245 |
+
return self._format_error(error_msg)
|
|
|
|
|
|
|
|
|
|
|
246 |
|
247 |
+
def _format_structured_conversations(self, conversations: List[AgentConversation]) -> str:
|
248 |
"""Форматує структуровані розмови у читабельний вигляд"""
|
|
|
|
|
249 |
if not conversations:
|
250 |
return "📭 Розмови не знайдено"
|
251 |
|
|
|
253 |
|
254 |
for conv in conversations:
|
255 |
output += f"### 🔄 Раунд {conv.round_number}\n"
|
256 |
+
if conv.start_time and conv.end_time:
|
257 |
+
output += f"**Час:** {conv.start_time} - {conv.end_time}\n\n"
|
258 |
|
259 |
for msg in conv.messages:
|
260 |
emoji = {
|
261 |
+
'function_call': '🤖',
|
262 |
'reasoning': '🤔',
|
263 |
'decision': '💡',
|
264 |
'input': '❓',
|
265 |
'output': '📊'
|
266 |
}.get(msg.message_type, '💬')
|
267 |
|
268 |
+
output += f"{emoji} **{msg.agent_name}** `[{msg.message_type}]`\n"
|
269 |
+
# Використовуємо Markdown-блоки для кращого відображення
|
270 |
+
if msg.message_type == 'function_call':
|
271 |
+
output += f"{msg.content}\n\n"
|
272 |
+
else:
|
273 |
+
output += f"```\n{msg.content}\n```\n\n"
|
274 |
+
|
275 |
if conv.decision:
|
276 |
output += f"**🎯 Рішення:** {conv.decision}\n"
|
277 |
|