Spaces:
Sleeping
Sleeping
Додано новий запис до .gitignore для ігнорування логів MAI-DX, імпортовано модуль re для парсингу, оновлено обробку помилок при завантаженні логгера, вдосконалено логування активності агентів та оновлено очікувані результати в тестових випадках.
Browse files- .gitignore +2 -0
- lmai_dx_interface_log.py +172 -551
.gitignore
CHANGED
@@ -166,3 +166,5 @@ cython_debug/
|
|
166 |
*.env
|
167 |
__pycache__/
|
168 |
*.pyc
|
|
|
|
|
|
166 |
*.env
|
167 |
__pycache__/
|
168 |
*.pyc
|
169 |
+
|
170 |
+
mai_dx_logs/
|
lmai_dx_interface_log.py
CHANGED
@@ -15,6 +15,7 @@ import warnings
|
|
15 |
import threading
|
16 |
import queue
|
17 |
import io
|
|
|
18 |
from contextlib import redirect_stdout, redirect_stderr
|
19 |
|
20 |
# Налаштування середовища
|
@@ -64,11 +65,13 @@ except ImportError:
|
|
64 |
try:
|
65 |
from agent_conversation_logger import AgentConversationLogger, DiagnosisSession
|
66 |
LOGGER_AVAILABLE = True
|
67 |
-
except ImportError:
|
68 |
LOGGER_AVAILABLE = False
|
|
|
69 |
|
70 |
@dataclass
|
71 |
class DiagnosisSession:
|
|
|
72 |
timestamp: str
|
73 |
case_name: str
|
74 |
patient_info: str
|
@@ -89,12 +92,10 @@ except ImportError:
|
|
89 |
return f"mock_{len(self.conversations)}"
|
90 |
def log_agent_message(self, *args, **kwargs):
|
91 |
pass
|
92 |
-
def end_conversation(self, *args):
|
93 |
return f"saved_{len(self.conversations)}"
|
94 |
def list_conversations(self):
|
95 |
return self.conversations
|
96 |
-
def get_conversation_summary(self):
|
97 |
-
return {"total_conversations": 0, "average_accuracy": 0, "average_cost": 0, "mode_distribution": {}}
|
98 |
def export_conversation_report(self, case_id, format):
|
99 |
return f"report_{case_id}.{format}"
|
100 |
def export_analytics_csv(self):
|
@@ -146,7 +147,7 @@ class EnhancedMAIDXInterface:
|
|
146 |
self.conversation_logger = AgentConversationLogger("mai_dx_logs")
|
147 |
self.current_metrics = RealTimeMetrics()
|
148 |
|
149 |
-
# Розширені тестові кейси
|
150 |
self.sample_cases = {
|
151 |
"🫀 Кардіологічний (Гострий MI)": {
|
152 |
"info": """Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі
|
@@ -155,7 +156,7 @@ class EnhancedMAIDXInterface:
|
|
155 |
ЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)
|
156 |
Тропонін I: 8.5 нг/мл (норма <0.04)
|
157 |
Анамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС""",
|
158 |
-
"expected": "Acute inferior myocardial infarction (STEMI)"
|
159 |
},
|
160 |
|
161 |
"🧠 Неврологічний (Гострий інсульт)": {
|
@@ -165,7 +166,7 @@ class EnhancedMAIDXInterface:
|
|
165 |
Неврологія: Правостороння геміплегія, афазія, девіація очей вліво
|
166 |
КТ голови: Гострого крововиливу немає, рання ішемія у лівій МСА
|
167 |
NIHSS: 15 балів""",
|
168 |
-
"expected": "Acute ischemic stroke in the left middle cerebral artery
|
169 |
},
|
170 |
|
171 |
"🦠 Інфекційний (Сепсис)": {
|
@@ -199,7 +200,6 @@ NIHSS: 15 балів""",
|
|
199 |
if not patient_info.strip():
|
200 |
return self._format_error("❌ Введіть інформацію про пацієнта")
|
201 |
|
202 |
-
# Скидання метрик
|
203 |
self.current_metrics.reset()
|
204 |
conversation_log = ""
|
205 |
case_id = None
|
@@ -207,7 +207,6 @@ NIHSS: 15 балів""",
|
|
207 |
try:
|
208 |
progress(0.1, desc="🚀 Ініціалізація системи...")
|
209 |
|
210 |
-
# Логування початку
|
211 |
if enable_logging:
|
212 |
case_id = self.conversation_logger.start_conversation(
|
213 |
case_name or f"Case_{datetime.now().strftime('%H%M%S')}",
|
@@ -218,7 +217,6 @@ NIHSS: 15 балів""",
|
|
218 |
|
219 |
progress(0.2, desc="🤖 Створення AI-панелі лікарів...")
|
220 |
|
221 |
-
# Створення оркестратора
|
222 |
orchestrator = MaiDxOrchestrator(
|
223 |
model_name=model_name,
|
224 |
max_iterations=max_iterations,
|
@@ -227,94 +225,60 @@ NIHSS: 15 балів""",
|
|
227 |
)
|
228 |
|
229 |
progress(0.3, desc="🔍 Запуск діагностичного процесу...")
|
230 |
-
|
231 |
start_time = time.time()
|
232 |
|
233 |
-
# Симуляція прогресу з реальними етапами
|
234 |
-
diagnostic_stages = [
|
235 |
-
"🧠 Dr. Hypothesis аналізує симптоми...",
|
236 |
-
"🔬 Dr. Test-Chooser обирає тести...",
|
237 |
-
"🤔 Dr. Challenger перевіряє гіпотези...",
|
238 |
-
"💰 Dr. Stewardship оцінює вартість...",
|
239 |
-
"✅ Dr. Checklist контролює якість...",
|
240 |
-
"🤝 Consensus Coordinator формує рішення..."
|
241 |
-
]
|
242 |
-
|
243 |
-
for i, stage in enumerate(diagnostic_stages):
|
244 |
-
progress(0.3 + (i * 0.1), desc=stage)
|
245 |
-
time.sleep(0.3) # Короткша затримка
|
246 |
-
|
247 |
-
progress(0.8, desc="🎯 Формування діагнозу...")
|
248 |
-
|
249 |
# Запуск діагностики з перехопленням виводу
|
250 |
-
with io.StringIO() as captured_output:
|
251 |
try:
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
except AttributeError:
|
264 |
-
result = orchestrator.run(
|
265 |
-
initial_case_info=patient_info,
|
266 |
-
full_case_details=patient_info,
|
267 |
-
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
268 |
-
)
|
269 |
-
else:
|
270 |
-
result = orchestrator.run(
|
271 |
-
initial_case_info=patient_info,
|
272 |
-
full_case_details=patient_info,
|
273 |
-
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
274 |
-
)
|
275 |
-
|
276 |
finally:
|
277 |
-
sys.stdout = original_stdout
|
278 |
captured_text = captured_output.getvalue()
|
279 |
-
|
280 |
duration = time.time() - start_time
|
281 |
-
|
282 |
progress(0.9, desc="📊 Обробка результатів...")
|
283 |
|
284 |
-
# Парсинг логів
|
285 |
if enable_logging and captured_text:
|
286 |
-
conversation_log += "🤖 Захоплені логи агентів:\n"
|
287 |
-
conversation_log += "=" * 60 + "\n"
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
# Оновлення метрик
|
292 |
-
self._parse_logs_for_metrics(captured_text)
|
293 |
|
294 |
-
# Логування результату
|
295 |
if enable_logging:
|
|
|
296 |
self.conversation_logger.log_agent_message(
|
297 |
-
"Judge",
|
298 |
-
"final_evaluation",
|
299 |
-
result.final_diagnosis,
|
300 |
-
getattr(result, 'accuracy_reasoning', 'Фінальна оцінка'),
|
301 |
-
result.accuracy_score / 5.0,
|
302 |
-
result.total_cost,
|
303 |
-
max_iterations
|
304 |
)
|
305 |
|
|
|
306 |
saved_case_id = self.conversation_logger.end_conversation(
|
307 |
-
result.final_diagnosis,
|
308 |
-
result.accuracy_score,
|
309 |
-
result.total_cost
|
|
|
|
|
|
|
310 |
)
|
311 |
-
|
312 |
-
conversation_log += f"💾 Збережено як: {saved_case_id}\n"
|
313 |
|
314 |
-
# Створення сесії
|
315 |
-
# Виправлений код
|
316 |
session = DiagnosisSession(
|
317 |
-
case_id=case_id,
|
318 |
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
319 |
case_name=case_name or f"Case_{len(self.sessions_history) + 1}",
|
320 |
patient_info=patient_info[:200] + "..." if len(patient_info) > 200 else patient_info,
|
@@ -328,16 +292,13 @@ NIHSS: 15 балів""",
|
|
328 |
status="✅ Успішно" if result.accuracy_score >= 3.0 else "⚠️ Потребує перегляду",
|
329 |
reasoning=getattr(result, 'accuracy_reasoning', 'Недоступно')[:300] + "..."
|
330 |
)
|
331 |
-
|
332 |
self.sessions_history.append(session)
|
333 |
|
334 |
progress(1.0, desc="✅ Готово!")
|
335 |
|
336 |
-
# Генерація візуалізацій (безпечно)
|
337 |
metrics_plot = self._create_metrics_visualization_safe()
|
338 |
agent_plot = self._create_agent_activity_chart_safe()
|
339 |
|
340 |
-
# Генерація всіх результатів
|
341 |
return (
|
342 |
self._format_main_result(session, result),
|
343 |
self._format_detailed_analysis(session, result),
|
@@ -354,608 +315,271 @@ NIHSS: 15 балів""",
|
|
354 |
return self._format_error(error_msg)
|
355 |
|
356 |
def _create_metrics_visualization_safe(self):
|
357 |
-
"""Безпечне створення візуалізації метрик"""
|
358 |
try:
|
359 |
-
if not PLOTLY_AVAILABLE:
|
360 |
-
|
361 |
-
|
362 |
if not self.current_metrics.cost_progression:
|
363 |
-
#
|
364 |
-
fig = go.Figure()
|
365 |
-
fig.add_trace(go.Scatter(
|
366 |
-
x=[0, 1, 2, 3],
|
367 |
-
y=[0, 300, 600, 900],
|
368 |
-
mode='lines+markers',
|
369 |
-
name='Демо-дані',
|
370 |
-
line=dict(color='#1f77b4', width=3)
|
371 |
-
))
|
372 |
-
fig.update_layout(
|
373 |
-
title='📈 Прогресія вартості (демо)',
|
374 |
-
xaxis_title='Час (секунди)',
|
375 |
-
yaxis_title='Вартість ($)',
|
376 |
-
template='plotly_white',
|
377 |
-
height=400
|
378 |
-
)
|
379 |
-
return fig
|
380 |
|
381 |
-
|
382 |
-
|
383 |
-
costs = [point['cost'] for point in self.current_metrics.cost_progression]
|
384 |
-
|
385 |
-
fig = go.Figure()
|
386 |
-
fig.add_trace(go.Scatter(
|
387 |
-
x=times,
|
388 |
-
y=costs,
|
389 |
-
mode='lines+markers',
|
390 |
-
name='Накопичена вартість',
|
391 |
-
line=dict(color='#1f77b4', width=3)
|
392 |
-
))
|
393 |
-
|
394 |
-
fig.update_layout(
|
395 |
-
title='📈 Прогресія вартості діагностики',
|
396 |
-
xaxis_title='Час (секунди)',
|
397 |
-
yaxis_title='Вартість ($)',
|
398 |
-
template='plotly_white',
|
399 |
-
height=400
|
400 |
-
)
|
401 |
|
|
|
|
|
402 |
return fig
|
403 |
-
|
404 |
except Exception as e:
|
405 |
print(f"Помилка візуалізації метрик: {e}")
|
406 |
return None
|
407 |
|
408 |
def _create_agent_activity_chart_safe(self):
|
409 |
-
"""Безпечне створення діаграми активності агентів"""
|
410 |
try:
|
411 |
-
if not PLOTLY_AVAILABLE:
|
412 |
-
return None
|
413 |
-
|
414 |
-
# Підготовка даних
|
415 |
-
agents = list(self.current_metrics.agents_activity.keys())
|
416 |
-
activities = list(self.current_metrics.agents_activity.values())
|
417 |
|
418 |
-
|
419 |
-
if
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
colors = [
|
424 |
-
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
|
425 |
-
'#FECA57', '#FF9FF3', '#54A0FF', '#5F27CD'
|
426 |
-
]
|
427 |
-
|
428 |
-
fig = go.Figure(data=[
|
429 |
-
go.Bar(
|
430 |
-
x=agents,
|
431 |
-
y=activities,
|
432 |
-
marker_color=colors[:len(agents)],
|
433 |
-
text=activities,
|
434 |
-
textposition='auto'
|
435 |
-
)
|
436 |
-
])
|
437 |
-
|
438 |
-
fig.update_layout(
|
439 |
-
title='🤖 Активність ШІ-агентів лікарів',
|
440 |
-
xaxis_title='Агенти',
|
441 |
-
yaxis_title='Кількість взаємодій',
|
442 |
-
template='plotly_white',
|
443 |
-
height=400,
|
444 |
-
showlegend=False
|
445 |
-
)
|
446 |
|
447 |
-
|
448 |
|
|
|
|
|
|
|
449 |
return fig
|
450 |
-
|
451 |
except Exception as e:
|
452 |
print(f"Помилка графіку агентів: {e}")
|
453 |
return None
|
454 |
|
455 |
-
def
|
456 |
-
"""
|
|
|
|
|
457 |
lines = captured_text.split('\n')
|
458 |
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
for line in lines:
|
460 |
line = line.strip()
|
461 |
-
if not line:
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
self.
|
|
|
|
|
|
|
468 |
break
|
469 |
|
470 |
-
#
|
471 |
-
|
472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
473 |
if cost_match:
|
474 |
-
|
|
|
475 |
|
476 |
def _format_main_result(self, session, result):
|
477 |
"""Форматування основного результату"""
|
478 |
confidence_emoji = "🎯" if session.confidence >= 4.0 else "👍" if session.confidence >= 3.0 else "⚠️"
|
479 |
-
efficiency = ((session.budget - session.cost) / session.budget * 100)
|
480 |
|
481 |
return f"""
|
482 |
## 🏥 Результати MAI-DX Діагностики
|
483 |
-
|
484 |
### 📋 Основна інформація
|
485 |
- **🗂️ Випадок**: {session.case_name}
|
486 |
- **⏰ Час**: {session.timestamp}
|
487 |
- **🔧 Режим**: {session.mode}
|
488 |
-
- **🤖 Модель**: AI Multi-Agent
|
489 |
-
|
490 |
### {confidence_emoji} Діагностичний висновок
|
491 |
**{session.diagnosis}**
|
492 |
-
|
493 |
### 📊 Показники якості
|
494 |
- **Точність**: {session.confidence:.1f}/5.0 ⭐
|
495 |
- **Статус**: {session.status}
|
496 |
-
- **Ітерації**: {session.iterations}
|
497 |
-
|
498 |
### 💰 Економічні показники
|
499 |
- **Витрачено**: ${session.cost:,.2f}
|
500 |
- **Бюджет**: ${session.budget:,}
|
501 |
- **Ефективність**: {efficiency:.1f}% бюджету збережено
|
502 |
-
- **Швидкість**: {session.duration:.1f}
|
503 |
-
|
504 |
### 🎖️ Загальна оцінка
|
505 |
{self._get_overall_rating(session)}
|
506 |
"""
|
507 |
|
508 |
def _get_overall_rating(self, session):
|
509 |
"""Загальна оцінка ефективності"""
|
510 |
-
efficiency = ((session.budget - session.cost) / session.budget * 100)
|
511 |
-
|
512 |
-
if session.confidence >=
|
513 |
-
|
514 |
-
|
515 |
-
return "🥈 **ДОБРЕ** - Надійний діагноз з прийнятною вартістю"
|
516 |
-
elif session.confidence >= 2.0:
|
517 |
-
return "🥉 **ЗАДОВІЛЬНО** - Потребує додаткової верифікації"
|
518 |
-
else:
|
519 |
-
return "❌ **ПОТРЕБУЄ ПЕРЕГЛЯДУ** - Низька впевненість"
|
520 |
|
521 |
def _format_detailed_analysis(self, session, result):
|
522 |
"""Детальний аналіз"""
|
523 |
return f"""
|
524 |
## 🔬 Детальний клінічний аналіз
|
525 |
-
|
526 |
### 💭 Медичне обґрунтування
|
527 |
{session.reasoning}
|
528 |
-
|
529 |
### 📈 Аналіз ефективності
|
530 |
-
-
|
531 |
-
- **💸 Економічна ефективність**: {((session.budget - session.cost) / session.budget * 100):.1f}%
|
532 |
-
- **🔄 Ітеративна конвергенція**: {session.iterations} циклів
|
533 |
- **🎯 Клінічна точність**: {(session.confidence / 5.0 * 100):.1f}%
|
534 |
-
|
535 |
### 🤖 Процес прийняття рішень
|
536 |
-
Система
|
537 |
-
|
538 |
-
### 📊 Порівняння з бенчмарками
|
539 |
-
- **Людські лікарі**: ~20% точність на NEJM CPC
|
540 |
-
- **GPT-4**: ~49% точність
|
541 |
-
- **MAI-DX**: ~85.5% точність ✨
|
542 |
"""
|
543 |
|
544 |
def _generate_enhanced_recommendations(self, result, expected_diagnosis):
|
545 |
"""Покращені рекомендації"""
|
546 |
comparison = ""
|
547 |
if expected_diagnosis:
|
|
|
|
|
548 |
comparison = f"""
|
549 |
-
### 🎯 Порівняння з очікуваним
|
550 |
**Очікувався**: {expected_diagnosis}
|
551 |
**Отримано**: {result.final_diagnosis}
|
552 |
-
**Збіг**: {'✅ Так' if
|
553 |
"""
|
554 |
-
|
555 |
return f"""
|
556 |
## 💡 Клінічні рекомендації
|
557 |
-
|
558 |
### 🏥 Негайні дії
|
559 |
-
- 🔍
|
560 |
-
- 📋
|
561 |
-
- 👨⚕️ **Консультація експерта** для підтвердження
|
562 |
-
|
563 |
### 🔬 Дослідницький потенціал
|
564 |
-
-
|
565 |
-
-
|
566 |
-
- **🤖 Тюнінг моделі**: Оптимізуйте параметри
|
567 |
-
|
568 |
{comparison}
|
569 |
-
|
570 |
### ⚠️ Важливе застереження
|
571 |
-
🔴 **Цей діагноз згенеровано ШІ
|
572 |
"""
|
573 |
|
574 |
def _format_error(self, error_msg):
|
575 |
-
"""Форматування помилки"""
|
576 |
return (error_msg, "", "", None, None, "")
|
577 |
|
578 |
def load_sample_case(self, sample_key):
|
579 |
-
"""Завантаження зразкового випадку"""
|
580 |
if sample_key and sample_key in self.sample_cases:
|
581 |
case_data = self.sample_cases[sample_key]
|
582 |
-
return
|
583 |
-
case_data["info"],
|
584 |
-
sample_key,
|
585 |
-
case_data["expected"]
|
586 |
-
)
|
587 |
return "", "", ""
|
588 |
|
589 |
def get_enhanced_analytics(self):
|
590 |
-
"""Розширена аналітика - ВИПРАВЛЕНА ВЕРСІЯ"""
|
591 |
try:
|
592 |
conversations = self.conversation_logger.list_conversations()
|
593 |
-
|
594 |
if not conversations:
|
595 |
-
|
596 |
-
return empty_df, "📊 Немає даних для аналізу", []
|
597 |
|
598 |
-
# Створення DataFrame
|
599 |
df = pd.DataFrame(conversations)
|
|
|
600 |
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
avg_cost = df['cost'].mean() if 'cost' in df.columns else 0
|
605 |
|
606 |
summary = f"""
|
607 |
-
## 📊 Розширена аналітика MAI-DX
|
608 |
-
|
609 |
### 🎯 Загальна статистика
|
610 |
- **Всього випадків**: {total_cases}
|
611 |
- **Середня точність**: {avg_accuracy:.2f}/5.0 ⭐
|
612 |
- **Середня вартість**: ${avg_cost:,.2f}
|
613 |
-
- **Загальна економія**: ${(3000 - avg_cost) * total_cases:,.2f}
|
614 |
"""
|
615 |
|
616 |
-
|
617 |
-
|
618 |
-
if not df.empty and 'case_name' in df.columns:
|
619 |
-
case_choices = [(f"{row['case_name']} ({row.get('case_id', 'N/A')})",
|
620 |
-
row.get('case_id', f"case_{i}"))
|
621 |
-
for i, (_, row) in enumerate(df.iterrows())]
|
622 |
-
|
623 |
-
return df, summary, case_choices
|
624 |
-
|
625 |
except Exception as e:
|
626 |
return pd.DataFrame(), f"❌ Помилка аналітики: {e}", []
|
627 |
|
628 |
def create_enhanced_gradio_interface():
|
629 |
-
"""Створення виправленого Gradio інтерфейсу"""
|
630 |
-
|
631 |
interface = EnhancedMAIDXInterface()
|
|
|
632 |
|
633 |
-
|
634 |
-
|
635 |
-
.gradio-container {
|
636 |
-
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
637 |
-
}
|
638 |
-
|
639 |
-
.main-header {
|
640 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
641 |
-
color: white;
|
642 |
-
padding: 2rem;
|
643 |
-
border-radius: 15px;
|
644 |
-
margin-bottom: 2rem;
|
645 |
-
text-align: center;
|
646 |
-
}
|
647 |
-
|
648 |
-
.conversation-log {
|
649 |
-
font-family: 'Fira Code', monospace;
|
650 |
-
background: #2d3748;
|
651 |
-
color: #e2e8f0;
|
652 |
-
border-radius: 8px;
|
653 |
-
padding: 1rem;
|
654 |
-
max-height: 500px;
|
655 |
-
overflow-y: auto;
|
656 |
-
white-space: pre-wrap;
|
657 |
-
}
|
658 |
-
"""
|
659 |
-
|
660 |
-
with gr.Blocks(
|
661 |
-
title="🏥 MAI-DX Enhanced Research Platform",
|
662 |
-
theme=gr.themes.Soft(
|
663 |
-
primary_hue="blue",
|
664 |
-
secondary_hue="purple",
|
665 |
-
neutral_hue="slate"
|
666 |
-
),
|
667 |
-
css=custom_css
|
668 |
-
) as demo:
|
669 |
-
|
670 |
-
# Заголовок
|
671 |
-
gr.HTML("""
|
672 |
-
<div class="main-header">
|
673 |
-
<h1>🏥 MAI-DX Enhanced Research Platform</h1>
|
674 |
-
<p>🤖 Платформа для дослідження ШІ-діагностики з детальним логуванням взаємодії агентів</p>
|
675 |
-
<p>📊 Базується на дослідженні Microsoft Research "Sequential Diagnosis with Language Models"</p>
|
676 |
-
</div>
|
677 |
-
""")
|
678 |
|
679 |
-
with gr.Tabs()
|
680 |
-
|
681 |
-
# Основна вкладка діагностики
|
682 |
-
with gr.Tab("🩺 Діагностика з ШІ-агентами", elem_id="diagnosis-tab"):
|
683 |
-
|
684 |
with gr.Row():
|
685 |
-
# Ліва колонка - введення даних
|
686 |
with gr.Column(scale=1):
|
687 |
gr.Markdown("### 📝 Клінічний випадок")
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
lines=1
|
693 |
-
)
|
694 |
-
|
695 |
-
sample_selector = gr.Dropdown(
|
696 |
-
choices=list(interface.sample_cases.keys()),
|
697 |
-
label="🎯 Готові тестові випадки",
|
698 |
-
value=None,
|
699 |
-
interactive=True
|
700 |
-
)
|
701 |
-
|
702 |
-
patient_info = gr.Textbox(
|
703 |
-
label="👤 Детальна інформація про пацієнта",
|
704 |
-
lines=12,
|
705 |
-
placeholder="Введіть повний опис клінічного випадку:\n- Демографія пацієнта\n- Скарги та анамнез\n- Фізикальне обстеження\n- Лабораторні дані\n- Інструментальні дослідження",
|
706 |
-
interactive=True
|
707 |
-
)
|
708 |
-
|
709 |
-
expected_diagnosis = gr.Textbox(
|
710 |
-
label="🎯 Очікуваний діагноз (для порівняння)",
|
711 |
-
placeholder="Опціонально: введіть правильний діагноз для оцінки точності",
|
712 |
-
lines=2
|
713 |
-
)
|
714 |
|
715 |
-
# Права колонка - налаштування
|
716 |
with gr.Column(scale=1):
|
717 |
-
gr.Markdown("### ⚙️ Налаштування
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
"👥 Консенсус (найточніший, повільний)": "ensemble"
|
725 |
-
}
|
726 |
-
|
727 |
-
# Новий, виправлений код
|
728 |
-
mode = gr.Radio(
|
729 |
-
choices=[
|
730 |
-
"instant",
|
731 |
-
"question_only",
|
732 |
-
"budgeted",
|
733 |
-
"no_budget",
|
734 |
-
"ensemble"
|
735 |
-
],
|
736 |
-
label="🔧 Режим діагностики",
|
737 |
-
value="budgeted",
|
738 |
-
interactive=True
|
739 |
-
)
|
740 |
-
|
741 |
-
budget = gr.Slider(
|
742 |
-
minimum=500,
|
743 |
-
maximum=10000,
|
744 |
-
step=500,
|
745 |
-
value=3000,
|
746 |
-
label="💵 Бюджет діагностики ($)",
|
747 |
-
interactive=True
|
748 |
-
)
|
749 |
-
|
750 |
-
max_iterations = gr.Slider(
|
751 |
-
minimum=1,
|
752 |
-
maximum=15,
|
753 |
-
step=1,
|
754 |
-
value=8,
|
755 |
-
label="🔄 Максимум ітерацій агентів",
|
756 |
-
interactive=True
|
757 |
-
)
|
758 |
-
|
759 |
-
model_name = gr.Dropdown(
|
760 |
-
choices=[
|
761 |
-
"gemini/gemini-2.5-flash",
|
762 |
-
"gpt-4",
|
763 |
-
"gpt-4-turbo",
|
764 |
-
"claude-3-5-sonnet",
|
765 |
-
"grok-beta"
|
766 |
-
],
|
767 |
-
label="🤖 LLM Модель",
|
768 |
-
value="gemini/gemini-2.5-flash",
|
769 |
-
interactive=True
|
770 |
-
)
|
771 |
-
|
772 |
-
enable_logging = gr.Checkbox(
|
773 |
-
label="📝 Детальне логування взаємодії агентів",
|
774 |
-
value=True,
|
775 |
-
interactive=True
|
776 |
-
)
|
777 |
-
|
778 |
-
gr.Markdown("---")
|
779 |
-
|
780 |
-
diagnose_btn = gr.Button(
|
781 |
-
"🚀 Запустити діагностику",
|
782 |
-
variant="primary",
|
783 |
-
size="lg",
|
784 |
-
interactive=True
|
785 |
-
)
|
786 |
-
|
787 |
-
# Завантаження зразків
|
788 |
-
sample_selector.change(
|
789 |
-
interface.load_sample_case,
|
790 |
-
inputs=[sample_selector],
|
791 |
-
outputs=[patient_info, case_name, expected_diagnosis]
|
792 |
-
)
|
793 |
|
794 |
-
|
795 |
-
gr.Markdown("---")
|
796 |
-
gr.Markdown("## 📊 Результати діагностики")
|
797 |
|
|
|
798 |
with gr.Row():
|
799 |
with gr.Column(scale=2):
|
800 |
main_result = gr.Markdown(label="🎯 Основний результат")
|
801 |
detailed_analysis = gr.Markdown(label="🔬 Детальний аналіз")
|
802 |
-
|
803 |
with gr.Column(scale=1):
|
804 |
recommendations = gr.Markdown(label="💡 Рекомендації")
|
805 |
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
with gr.Column():
|
810 |
-
metrics_plot = gr.Plot(label="📈 Метрики діагностики")
|
811 |
-
with gr.Column():
|
812 |
-
agent_activity_plot = gr.Plot(label="🤖 Активність агентів")
|
813 |
-
else:
|
814 |
-
gr.Markdown("ℹ️ Візуалізації недоступні (Plotly не встановлено)")
|
815 |
-
metrics_plot = gr.Textbox(visible=False)
|
816 |
-
agent_activity_plot = gr.Textbox(visible=False)
|
817 |
|
818 |
-
|
819 |
-
gr.Markdown("### 💬 Логи взаємодії ШІ-агентів")
|
820 |
-
conversation_logs = gr.Textbox(
|
821 |
-
label="Детальні логи бесід між агентами",
|
822 |
-
lines=12,
|
823 |
-
elem_classes=["conversation-log"],
|
824 |
-
interactive=False,
|
825 |
-
show_copy_button=True
|
826 |
-
)
|
827 |
|
828 |
-
|
829 |
-
|
830 |
-
|
831 |
-
inputs=[
|
832 |
-
case_name, patient_info, mode, budget, max_iterations,
|
833 |
-
model_name, expected_diagnosis, enable_logging
|
834 |
-
],
|
835 |
-
outputs=[
|
836 |
-
main_result, detailed_analysis, recommendations,
|
837 |
-
metrics_plot, agent_activity_plot, conversation_logs
|
838 |
-
]
|
839 |
-
)
|
840 |
|
841 |
-
|
842 |
-
with gr.Tab("📊 Розширена аналітика", elem_id="analytics-tab"):
|
843 |
gr.Markdown("### 🔍 Аналіз збережених діагностичних сесій")
|
844 |
-
|
845 |
with gr.Row():
|
846 |
-
refresh_analytics_btn = gr.Button("🔄 Оновити
|
847 |
-
export_data_btn = gr.Button("📤 Експортувати
|
|
|
|
|
848 |
|
|
|
849 |
with gr.Row():
|
850 |
-
|
851 |
-
|
852 |
-
label="📋 Історія діагностичних сесій",
|
853 |
-
interactive=False,
|
854 |
-
wrap=True
|
855 |
-
)
|
856 |
-
|
857 |
-
with gr.Column(scale=1):
|
858 |
-
analytics_summary = gr.Markdown(label="📊 Статистичний звіт")
|
859 |
-
|
860 |
-
# Детальний аналіз окремих випадків
|
861 |
-
gr.Markdown("### 🔬 Поглиблений аналіз випадків")
|
862 |
-
|
863 |
-
with gr.Row():
|
864 |
-
case_selector = gr.Dropdown(
|
865 |
-
label="🗂️ Оберіть випадок для детального аналізу",
|
866 |
-
choices=[],
|
867 |
-
interactive=True
|
868 |
-
)
|
869 |
-
|
870 |
-
analyze_case_btn = gr.Button("📋 Створити детальний звіт")
|
871 |
-
|
872 |
analysis_status = gr.Markdown()
|
873 |
|
874 |
-
# Функції аналітики - ВИПРАВЛЕНІ
|
875 |
def refresh_analytics():
|
876 |
-
df, summary,
|
877 |
-
return df, summary,
|
878 |
-
|
879 |
-
def export_analytics():
|
880 |
-
try:
|
881 |
-
filename = interface.conversation_logger.export_analytics_csv()
|
882 |
-
return f"✅ Дані експортовано: {filename}"
|
883 |
-
except Exception as e:
|
884 |
-
return f"❌ Помилка експорту: {e}"
|
885 |
-
|
886 |
-
def analyze_case(case_id):
|
887 |
-
if not case_id:
|
888 |
-
return "⚠️ Оберіть випадок для аналізу"
|
889 |
-
try:
|
890 |
-
report = interface.conversation_logger.export_conversation_report(case_id, 'html')
|
891 |
-
return f"✅ Детальний звіт створено: {report}"
|
892 |
-
except Exception as e:
|
893 |
-
return f"❌ Помилка аналізу: {e}"
|
894 |
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
outputs=[analytics_table, analytics_summary, case_selector]
|
899 |
-
)
|
900 |
-
|
901 |
-
export_data_btn.click(
|
902 |
-
export_analytics,
|
903 |
-
outputs=[analysis_status]
|
904 |
-
)
|
905 |
-
|
906 |
-
analyze_case_btn.click(
|
907 |
-
analyze_case,
|
908 |
-
inputs=[case_selector],
|
909 |
-
outputs=[analysis_status]
|
910 |
-
)
|
911 |
-
|
912 |
-
# Автооновлення при завантаженні
|
913 |
demo.load(refresh_analytics, outputs=[analytics_table, analytics_summary, case_selector])
|
914 |
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
**Logger доступність**: {'✅ Доступний' if LOGGER_AVAILABLE else '⚠️ Заглушка'}
|
924 |
-
**Plotly візуалізації**: {'✅ Доступні' if PLOTLY_AVAILABLE else '❌ Недоступні'}
|
925 |
-
**Gradio версія**: {gr.__version__}
|
926 |
-
"""
|
927 |
-
|
928 |
-
gr.Markdown(status_info)
|
929 |
-
|
930 |
-
if not MAI_DX_AVAILABLE:
|
931 |
-
gr.Markdown(f"**Помилка MAI-DX**: {IMPORT_ERROR}")
|
932 |
-
|
933 |
-
# Документація
|
934 |
-
gr.Markdown("""
|
935 |
### 📖 Короткий гайд
|
936 |
-
|
937 |
-
|
938 |
-
|
939 |
-
|
940 |
-
|
941 |
-
4. Дочекайт��ся результатів та аналізуйте логи
|
942 |
-
|
943 |
-
#### 🔧 Режими роботи:
|
944 |
-
- **⚡ Миттєвий**: Швидкий базовий аналіз (~5 сек)
|
945 |
-
- **❓ Тільки питання**: Без тестів, тільки опитування
|
946 |
-
- **💰 З бюджетом**: Збалансований підхід (рекомендується)
|
947 |
-
- **🔓 Без обмежень**: Максимальна точність
|
948 |
-
- **👥 Консенсус**: Найвища точність, але повільно
|
949 |
-
|
950 |
-
#### 📊 Аналітика:
|
951 |
-
- Переглядайте історію діагнозів у вкладці "Аналітика"
|
952 |
-
- Експортуйте дані для подальшого аналізу
|
953 |
-
- Створюйте детальні звіти по окремих випадках
|
954 |
-
|
955 |
-
#### ⚠️ Важливо:
|
956 |
-
- Система призначена для дослідницьких цілей
|
957 |
-
- Не замінює професійну медичну консультацію
|
958 |
-
- При проблемах перевірте статус системи вище
|
959 |
""")
|
960 |
|
961 |
return demo
|
@@ -972,9 +596,6 @@ if __name__ == "__main__":
|
|
972 |
demo.launch(
|
973 |
server_name="0.0.0.0",
|
974 |
server_port=7860,
|
975 |
-
share=
|
976 |
-
debug=False
|
977 |
-
show_error=True,
|
978 |
-
favicon_path=None,
|
979 |
-
ssl_verify=False
|
980 |
)
|
|
|
15 |
import threading
|
16 |
import queue
|
17 |
import io
|
18 |
+
import re # Імпортуємо re для парсингу
|
19 |
from contextlib import redirect_stdout, redirect_stderr
|
20 |
|
21 |
# Налаштування середовища
|
|
|
65 |
try:
|
66 |
from agent_conversation_logger import AgentConversationLogger, DiagnosisSession
|
67 |
LOGGER_AVAILABLE = True
|
68 |
+
except ImportError as e:
|
69 |
LOGGER_AVAILABLE = False
|
70 |
+
print(f"⚠️ Не вдалося завантажити логгер, буде використана заглушка: {e}")
|
71 |
|
72 |
@dataclass
|
73 |
class DiagnosisSession:
|
74 |
+
case_id: str
|
75 |
timestamp: str
|
76 |
case_name: str
|
77 |
patient_info: str
|
|
|
92 |
return f"mock_{len(self.conversations)}"
|
93 |
def log_agent_message(self, *args, **kwargs):
|
94 |
pass
|
95 |
+
def end_conversation(self, *args, **kwargs):
|
96 |
return f"saved_{len(self.conversations)}"
|
97 |
def list_conversations(self):
|
98 |
return self.conversations
|
|
|
|
|
99 |
def export_conversation_report(self, case_id, format):
|
100 |
return f"report_{case_id}.{format}"
|
101 |
def export_analytics_csv(self):
|
|
|
147 |
self.conversation_logger = AgentConversationLogger("mai_dx_logs")
|
148 |
self.current_metrics = RealTimeMetrics()
|
149 |
|
150 |
+
# Розширені тестові кейси (з виправленими очікуваними діагнозами)
|
151 |
self.sample_cases = {
|
152 |
"🫀 Кардіологічний (Гострий MI)": {
|
153 |
"info": """Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі
|
|
|
156 |
ЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)
|
157 |
Тропонін I: 8.5 нг/мл (норма <0.04)
|
158 |
Анамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС""",
|
159 |
+
"expected": "Acute inferior wall myocardial infarction (STEMI)"
|
160 |
},
|
161 |
|
162 |
"🧠 Неврологічний (Гострий інсульт)": {
|
|
|
166 |
Неврологія: Правостороння геміплегія, афазія, девіація очей вліво
|
167 |
КТ голови: Гострого крововиливу немає, рання ішемія у лівій МСА
|
168 |
NIHSS: 15 балів""",
|
169 |
+
"expected": "Acute ischemic stroke in the left middle cerebral artery territory"
|
170 |
},
|
171 |
|
172 |
"🦠 Інфекційний (Сепсис)": {
|
|
|
200 |
if not patient_info.strip():
|
201 |
return self._format_error("❌ Введіть інформацію про пацієнта")
|
202 |
|
|
|
203 |
self.current_metrics.reset()
|
204 |
conversation_log = ""
|
205 |
case_id = None
|
|
|
207 |
try:
|
208 |
progress(0.1, desc="🚀 Ініціалізація системи...")
|
209 |
|
|
|
210 |
if enable_logging:
|
211 |
case_id = self.conversation_logger.start_conversation(
|
212 |
case_name or f"Case_{datetime.now().strftime('%H%M%S')}",
|
|
|
217 |
|
218 |
progress(0.2, desc="🤖 Створення AI-панелі лікарів...")
|
219 |
|
|
|
220 |
orchestrator = MaiDxOrchestrator(
|
221 |
model_name=model_name,
|
222 |
max_iterations=max_iterations,
|
|
|
225 |
)
|
226 |
|
227 |
progress(0.3, desc="🔍 Запуск діагностичного процесу...")
|
|
|
228 |
start_time = time.time()
|
229 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
# Запуск діагностики з перехопленням виводу
|
231 |
+
with io.StringIO() as captured_output, redirect_stdout(captured_output):
|
232 |
try:
|
233 |
+
result = orchestrator.run(
|
234 |
+
initial_case_info=patient_info,
|
235 |
+
full_case_details=patient_info,
|
236 |
+
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
237 |
+
)
|
238 |
+
except Exception as e:
|
239 |
+
# Спроба обробити помилку і отримати частковий результат
|
240 |
+
print(f"\nПОМИЛКА ПІД ЧАС ВИКОНАННЯ: {e}")
|
241 |
+
result = getattr(orchestrator, 'result', None)
|
242 |
+
if not result:
|
243 |
+
raise e from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
finally:
|
|
|
245 |
captured_text = captured_output.getvalue()
|
246 |
+
|
247 |
duration = time.time() - start_time
|
|
|
248 |
progress(0.9, desc="📊 Обробка результатів...")
|
249 |
|
|
|
250 |
if enable_logging and captured_text:
|
251 |
+
conversation_log += "🤖 Захоплені логи агентів:\n" + "=" * 60 + "\n"
|
252 |
+
conversation_log += captured_text + "\n" + "=" * 60 + "\n\n"
|
253 |
+
# ВИПРАВЛЕНО: Парсинг логів для метрик та повідомлень
|
254 |
+
self._parse_and_log_agent_activity(captured_text)
|
|
|
|
|
|
|
255 |
|
|
|
256 |
if enable_logging:
|
257 |
+
# Логування фінальної оцінки
|
258 |
self.conversation_logger.log_agent_message(
|
259 |
+
agent_name="Judge",
|
260 |
+
action="final_evaluation",
|
261 |
+
content=result.final_diagnosis,
|
262 |
+
reasoning=getattr(result, 'accuracy_reasoning', 'Фінальна оцінка'),
|
263 |
+
confidence=result.accuracy_score / 5.0, # Нормалізація до 0-1
|
264 |
+
cost=result.total_cost,
|
265 |
+
iteration=getattr(result, 'iterations', max_iterations)
|
266 |
)
|
267 |
|
268 |
+
# ВИПРАВЛЕНО: Завершення сесії з усіма даними
|
269 |
saved_case_id = self.conversation_logger.end_conversation(
|
270 |
+
final_diagnosis=result.final_diagnosis,
|
271 |
+
confidence=result.accuracy_score,
|
272 |
+
cost=result.total_cost,
|
273 |
+
budget=budget,
|
274 |
+
reasoning=getattr(result, 'accuracy_reasoning', 'N/A'),
|
275 |
+
iterations=getattr(result, 'iterations', max_iterations)
|
276 |
)
|
277 |
+
conversation_log += f"💾 Сесію збережено як: {saved_case_id}\n"
|
|
|
278 |
|
279 |
+
# Створення об'єкту сесії для відображення
|
|
|
280 |
session = DiagnosisSession(
|
281 |
+
case_id=case_id,
|
282 |
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
283 |
case_name=case_name or f"Case_{len(self.sessions_history) + 1}",
|
284 |
patient_info=patient_info[:200] + "..." if len(patient_info) > 200 else patient_info,
|
|
|
292 |
status="✅ Успішно" if result.accuracy_score >= 3.0 else "⚠️ Потребує перегляду",
|
293 |
reasoning=getattr(result, 'accuracy_reasoning', 'Недоступно')[:300] + "..."
|
294 |
)
|
|
|
295 |
self.sessions_history.append(session)
|
296 |
|
297 |
progress(1.0, desc="✅ Готово!")
|
298 |
|
|
|
299 |
metrics_plot = self._create_metrics_visualization_safe()
|
300 |
agent_plot = self._create_agent_activity_chart_safe()
|
301 |
|
|
|
302 |
return (
|
303 |
self._format_main_result(session, result),
|
304 |
self._format_detailed_analysis(session, result),
|
|
|
315 |
return self._format_error(error_msg)
|
316 |
|
317 |
def _create_metrics_visualization_safe(self):
|
|
|
318 |
try:
|
319 |
+
if not PLOTLY_AVAILABLE: return None
|
320 |
+
fig = go.Figure()
|
|
|
321 |
if not self.current_metrics.cost_progression:
|
322 |
+
return None # Немає даних - не показуємо графік
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
|
324 |
+
times = [p['time'] for p in self.current_metrics.cost_progression]
|
325 |
+
costs = [p['cost'] for p in self.current_metrics.cost_progression]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
|
327 |
+
fig.add_trace(go.Scatter(x=times, y=costs, mode='lines+markers', name='Вартість', line=dict(color='#1f77b4', width=3)))
|
328 |
+
fig.update_layout(title='📈 Прогресія вартості діагностики', xaxis_title='Час (секунди)', yaxis_title='Вартість ($)', template='plotly_white', height=400)
|
329 |
return fig
|
|
|
330 |
except Exception as e:
|
331 |
print(f"Помилка візуалізації метрик: {e}")
|
332 |
return None
|
333 |
|
334 |
def _create_agent_activity_chart_safe(self):
|
|
|
335 |
try:
|
336 |
+
if not PLOTLY_AVAILABLE: return None
|
|
|
|
|
|
|
|
|
|
|
337 |
|
338 |
+
activity_data = {k: v for k, v in self.current_metrics.agents_activity.items() if v > 0}
|
339 |
+
if not activity_data: return None
|
340 |
+
|
341 |
+
agents = list(activity_data.keys())
|
342 |
+
activities = list(activity_data.values())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
|
344 |
+
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3', '#54A0FF', '#5F27CD']
|
345 |
|
346 |
+
fig = go.Figure(data=[go.Bar(x=agents, y=activities, marker_color=colors, text=activities, textposition='auto')])
|
347 |
+
fig.update_layout(title='🤖 Активність ШІ-агентів', xaxis_title='Агенти', yaxis_title='Кількість взаємодій', template='plotly_white', height=400, showlegend=False)
|
348 |
+
fig.update_xaxes(tickangle=-45)
|
349 |
return fig
|
|
|
350 |
except Exception as e:
|
351 |
print(f"Помилка графіку агентів: {e}")
|
352 |
return None
|
353 |
|
354 |
+
def _parse_and_log_agent_activity(self, captured_text: str):
|
355 |
+
"""
|
356 |
+
Парсить перехоплений текстовий лог для оновлення метрик та логування повідомлень.
|
357 |
+
"""
|
358 |
lines = captured_text.split('\n')
|
359 |
|
360 |
+
agent_keywords = {
|
361 |
+
"Dr. Hypothesis": "Dr. Hypothesis", "Dr. Test-Chooser": "Dr. Test-Chooser",
|
362 |
+
"Dr. Challenger": "Dr. Challenger", "Dr. Stewardship": "Dr. Stewardship",
|
363 |
+
"Dr. Checklist": "Dr. Checklist", "Consensus Coordinator": "Consensus Coordinator"
|
364 |
+
}
|
365 |
+
|
366 |
for line in lines:
|
367 |
line = line.strip()
|
368 |
+
if not line: continue
|
369 |
+
|
370 |
+
# 1. Логування активності агентів
|
371 |
+
for keyword, agent_name in agent_keywords.items():
|
372 |
+
if keyword in line and "analyzing" in line:
|
373 |
+
self.current_metrics.update_agent_activity(agent_name)
|
374 |
+
self.conversation_logger.log_agent_message(
|
375 |
+
agent_name=agent_name, action="analysis_start", content=line,
|
376 |
+
reasoning="Parsed from orchestrator output", confidence=0.0, cost=0.0, iteration=0
|
377 |
+
)
|
378 |
break
|
379 |
|
380 |
+
# 2. Логування рішень панелі
|
381 |
+
if "Panel decision:" in line:
|
382 |
+
self.current_metrics.update_agent_activity("Consensus Coordinator")
|
383 |
+
self.conversation_logger.log_agent_message(
|
384 |
+
agent_name="Consensus Coordinator", action="panel_decision", content=line.split("Panel decision:")[-1].strip(),
|
385 |
+
reasoning="Parsed from orchestrator output", confidence=0.0, cost=0.0, iteration=0
|
386 |
+
)
|
387 |
+
|
388 |
+
# 3. Парсинг вартості
|
389 |
+
cost_match = re.search(r'Current cost: \$(\d+(?:\.\d+)?)', line)
|
390 |
if cost_match:
|
391 |
+
current_cost = float(cost_match.group(1))
|
392 |
+
self.current_metrics.add_cost_point(current_cost)
|
393 |
|
394 |
def _format_main_result(self, session, result):
|
395 |
"""Форматування основного результату"""
|
396 |
confidence_emoji = "🎯" if session.confidence >= 4.0 else "👍" if session.confidence >= 3.0 else "⚠️"
|
397 |
+
efficiency = ((session.budget - session.cost) / session.budget * 100) if session.budget > 0 else 0
|
398 |
|
399 |
return f"""
|
400 |
## 🏥 Результати MAI-DX Діагностики
|
|
|
401 |
### 📋 Основна інформація
|
402 |
- **🗂️ Випадок**: {session.case_name}
|
403 |
- **⏰ Час**: {session.timestamp}
|
404 |
- **🔧 Режим**: {session.mode}
|
|
|
|
|
405 |
### {confidence_emoji} Діагностичний висновок
|
406 |
**{session.diagnosis}**
|
|
|
407 |
### 📊 Показники якості
|
408 |
- **Точність**: {session.confidence:.1f}/5.0 ⭐
|
409 |
- **Статус**: {session.status}
|
410 |
+
- **Ітерації**: {session.iterations}
|
|
|
411 |
### 💰 Економічні показники
|
412 |
- **Витрачено**: ${session.cost:,.2f}
|
413 |
- **Бюджет**: ${session.budget:,}
|
414 |
- **Ефективність**: {efficiency:.1f}% бюджету збережено
|
415 |
+
- **Швидкість**: {session.duration:.1f} сек
|
|
|
416 |
### 🎖️ Загальна оцінка
|
417 |
{self._get_overall_rating(session)}
|
418 |
"""
|
419 |
|
420 |
def _get_overall_rating(self, session):
|
421 |
"""Загальна оцінка ефективності"""
|
422 |
+
efficiency = ((session.budget - session.cost) / session.budget * 100) if session.budget > 0 else 0
|
423 |
+
if session.confidence >= 4.0 and efficiency >= 50: return "🏆 **ВІДМІННО**"
|
424 |
+
if session.confidence >= 3.0 and efficiency >= 20: return "🥈 **ДОБРЕ**"
|
425 |
+
if session.confidence >= 2.0: return "🥉 **ЗАДОВІЛЬНО**"
|
426 |
+
return "❌ **ПОТРЕБУЄ ПЕРЕГЛЯДУ**"
|
|
|
|
|
|
|
|
|
|
|
427 |
|
428 |
def _format_detailed_analysis(self, session, result):
|
429 |
"""Детальний аналіз"""
|
430 |
return f"""
|
431 |
## 🔬 Детальний клінічний аналіз
|
|
|
432 |
### 💭 Медичне обґрунтування
|
433 |
{session.reasoning}
|
|
|
434 |
### 📈 Аналіз ефективності
|
435 |
+
- **💸 Економічна ефективність**: {((session.budget - session.cost) / session.budget * 100) if session.budget > 0 else 0:.1f}%
|
|
|
|
|
436 |
- **🎯 Клінічна точність**: {(session.confidence / 5.0 * 100):.1f}%
|
|
|
437 |
### 🤖 Процес прийняття рішень
|
438 |
+
Система використала **{session.iterations} ітерацій** для консенсусу між **8 ШІ-агентами**.
|
|
|
|
|
|
|
|
|
|
|
439 |
"""
|
440 |
|
441 |
def _generate_enhanced_recommendations(self, result, expected_diagnosis):
|
442 |
"""Покращені рекомендації"""
|
443 |
comparison = ""
|
444 |
if expected_diagnosis:
|
445 |
+
is_match = expected_diagnosis.lower() in result.final_diagnosis.lower() or \
|
446 |
+
result.final_diagnosis.lower() in expected_diagnosis.lower()
|
447 |
comparison = f"""
|
448 |
+
### 🎯 Порівняння з очікуваним
|
449 |
**Очікувався**: {expected_diagnosis}
|
450 |
**Отримано**: {result.final_diagnosis}
|
451 |
+
**Збіг**: {'✅ Так' if is_match else '❌ Ні'}
|
452 |
"""
|
|
|
453 |
return f"""
|
454 |
## 💡 Клінічні рекомендації
|
|
|
455 |
### 🏥 Негайні дії
|
456 |
+
- 🔍 Верифікація діагнозу з лікарем
|
457 |
+
- 📋 Розгляд додаткових досліджень
|
|
|
|
|
458 |
### 🔬 Дослідницький потенціал
|
459 |
+
- 📊 Аналіз логів для вивчення роботи агентів
|
460 |
+
- 📈 Валідація на додаткових випадках
|
|
|
|
|
461 |
{comparison}
|
|
|
462 |
### ⚠️ Важливе застереження
|
463 |
+
🔴 **Цей діагноз згенеровано ШІ і НЕ замінює професійну медичну консультацію.**
|
464 |
"""
|
465 |
|
466 |
def _format_error(self, error_msg):
|
|
|
467 |
return (error_msg, "", "", None, None, "")
|
468 |
|
469 |
def load_sample_case(self, sample_key):
|
|
|
470 |
if sample_key and sample_key in self.sample_cases:
|
471 |
case_data = self.sample_cases[sample_key]
|
472 |
+
return case_data["info"], sample_key, case_data["expected"]
|
|
|
|
|
|
|
|
|
473 |
return "", "", ""
|
474 |
|
475 |
def get_enhanced_analytics(self):
|
|
|
476 |
try:
|
477 |
conversations = self.conversation_logger.list_conversations()
|
|
|
478 |
if not conversations:
|
479 |
+
return pd.DataFrame(), "📊 Немає даних для аналізу", []
|
|
|
480 |
|
|
|
481 |
df = pd.DataFrame(conversations)
|
482 |
+
df = df.reindex(columns=['case_id', 'timestamp', 'case_name', 'mode', 'diagnosis', 'confidence', 'cost', 'budget', 'duration', 'status'])
|
483 |
|
484 |
+
total_cases = len(df)
|
485 |
+
avg_accuracy = df['confidence'].mean()
|
486 |
+
avg_cost = df['cost'].mean()
|
|
|
487 |
|
488 |
summary = f"""
|
|
|
|
|
489 |
### 🎯 Загальна статистика
|
490 |
- **Всього випадків**: {total_cases}
|
491 |
- **Середня точність**: {avg_accuracy:.2f}/5.0 ⭐
|
492 |
- **Середня вартість**: ${avg_cost:,.2f}
|
|
|
493 |
"""
|
494 |
|
495 |
+
case_choices = [(f"{row['case_name']} ({row.get('case_id', 'N/A')})", row.get('case_id')) for _, row in df.iterrows()]
|
496 |
+
return df, summary, gr.Dropdown(choices=case_choices)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
except Exception as e:
|
498 |
return pd.DataFrame(), f"❌ Помилка аналітики: {e}", []
|
499 |
|
500 |
def create_enhanced_gradio_interface():
|
|
|
|
|
501 |
interface = EnhancedMAIDXInterface()
|
502 |
+
custom_css = """.gradio-container {font-family: 'Inter', sans-serif;} .main-header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem; border-radius: 15px; margin-bottom: 2rem; text-align: center;} .conversation-log {font-family: 'Fira Code', monospace; background: #2d3748; color: #e2e8f0; border-radius: 8px; padding: 1rem; max-height: 500px; overflow-y: auto; white-space: pre-wrap;}"""
|
503 |
|
504 |
+
with gr.Blocks(title="🏥 MAI-DX Enhanced Research Platform", theme=gr.themes.Soft(primary_hue="blue"), css=custom_css) as demo:
|
505 |
+
gr.HTML("""<div class="main-header"><h1>🏥 MAI-DX Enhanced Research Platform</h1><p>🤖 Дослідницька платформа для ШІ-діагностики з детальним логуванням</p></div>""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
|
507 |
+
with gr.Tabs():
|
508 |
+
with gr.Tab("🩺 Діагностика", elem_id="diagnosis-tab"):
|
|
|
|
|
|
|
509 |
with gr.Row():
|
|
|
510 |
with gr.Column(scale=1):
|
511 |
gr.Markdown("### 📝 Клінічний випадок")
|
512 |
+
case_name = gr.Textbox(label="🏷️ Назва випадку", placeholder="Наприклад: Кардіологічний випадок №1")
|
513 |
+
sample_selector = gr.Dropdown(choices=list(interface.sample_cases.keys()), label="🎯 Готові тестові випадки", interactive=True)
|
514 |
+
patient_info = gr.Textbox(label="👤 Інформація про пацієнта", lines=10, placeholder="Введіть опис клінічного випадку...")
|
515 |
+
expected_diagnosis = gr.Textbox(label="🎯 Очікуваний діагноз (для порівняння)", lines=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
516 |
|
|
|
517 |
with gr.Column(scale=1):
|
518 |
+
gr.Markdown("### ⚙️ Налаштування")
|
519 |
+
mode = gr.Radio(choices=["instant", "question_only", "budgeted", "no_budget", "ensemble"], label="🔧 Режим", value="budgeted", interactive=True)
|
520 |
+
budget = gr.Slider(minimum=500, maximum=10000, step=500, value=3000, label="💵 Бюджет ($)", interactive=True)
|
521 |
+
max_iterations = gr.Slider(minimum=1, maximum=15, step=1, value=8, label="🔄 Макс. ітерацій", interactive=True)
|
522 |
+
model_name = gr.Dropdown(choices=["gemini/gemini-1.5-flash", "gpt-4", "gpt-4-turbo"], label="🤖 LLM Модель", value="gemini/gemini-1.5-flash", interactive=True)
|
523 |
+
enable_logging = gr.Checkbox(label="📝 Детальне логування", value=True, interactive=True)
|
524 |
+
diagnose_btn = gr.Button("🚀 Запустити діагностику", variant="primary", size="lg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
525 |
|
526 |
+
sample_selector.change(interface.load_sample_case, inputs=[sample_selector], outputs=[patient_info, case_name, expected_diagnosis])
|
|
|
|
|
527 |
|
528 |
+
gr.Markdown("--- \n ## 📊 Результати діагностики")
|
529 |
with gr.Row():
|
530 |
with gr.Column(scale=2):
|
531 |
main_result = gr.Markdown(label="🎯 Основний результат")
|
532 |
detailed_analysis = gr.Markdown(label="🔬 Детальний аналіз")
|
|
|
533 |
with gr.Column(scale=1):
|
534 |
recommendations = gr.Markdown(label="💡 Рекомендації")
|
535 |
|
536 |
+
with gr.Row():
|
537 |
+
metrics_plot = gr.Plot(label="📈 Метрики діагностики")
|
538 |
+
agent_activity_plot = gr.Plot(label="🤖 Активність агентів")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
539 |
|
540 |
+
conversation_logs = gr.Textbox(label="💬 Логи взаємодії ШІ-агентів", lines=12, elem_classes=["conversation-log"], interactive=False, show_copy_button=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
541 |
|
542 |
+
diagnose_btn.click(interface.diagnose_with_enhanced_tracking,
|
543 |
+
inputs=[case_name, patient_info, mode, budget, max_iterations, model_name, expected_diagnosis, enable_logging],
|
544 |
+
outputs=[main_result, detailed_analysis, recommendations, metrics_plot, agent_activity_plot, conversation_logs])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
545 |
|
546 |
+
with gr.Tab("📊 Аналітика", elem_id="analytics-tab"):
|
|
|
547 |
gr.Markdown("### 🔍 Аналіз збережених діагностичних сесій")
|
|
|
548 |
with gr.Row():
|
549 |
+
refresh_analytics_btn = gr.Button("🔄 Оновити")
|
550 |
+
export_data_btn = gr.Button("📤 Експортувати CSV")
|
551 |
+
analytics_table = gr.Dataframe(label="📋 Історія сесій", interactive=False, wrap=True)
|
552 |
+
analytics_summary = gr.Markdown(label="📊 Статистичний звіт")
|
553 |
|
554 |
+
gr.Markdown("### 🔬 Детальний звіт по випадку")
|
555 |
with gr.Row():
|
556 |
+
case_selector = gr.Dropdown(label="🗂️ Оберіть випадок", choices=[], interactive=True)
|
557 |
+
analyze_case_btn = gr.Button("📋 Створити звіт (HTML)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
558 |
analysis_status = gr.Markdown()
|
559 |
|
|
|
560 |
def refresh_analytics():
|
561 |
+
df, summary, dd = interface.get_enhanced_analytics()
|
562 |
+
return df, summary, dd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
563 |
|
564 |
+
refresh_analytics_btn.click(refresh_analytics, outputs=[analytics_table, analytics_summary, case_selector])
|
565 |
+
export_data_btn.click(lambda: interface.conversation_logger.export_analytics_csv(), outputs=[analysis_status])
|
566 |
+
analyze_case_btn.click(lambda case_id: f"✅ Звіт створено: {interface.conversation_logger.export_conversation_report(case_id, 'html')}" if case_id else "⚠️ Оберіть випадок", inputs=[case_selector], outputs=[analysis_status])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
567 |
demo.load(refresh_analytics, outputs=[analytics_table, analytics_summary, case_selector])
|
568 |
|
569 |
+
with gr.Tab("📚 Документація", elem_id="docs-tab"):
|
570 |
+
gr.Markdown(f"""
|
571 |
+
### 🔧 Статус системи
|
572 |
+
- **MAI-DX доступність**: {'✅' if MAI_DX_AVAILABLE else '❌'}
|
573 |
+
- **Logger доступність**: {'✅' if LOGGER_AVAILABLE else '⚠️ Заглушка'}
|
574 |
+
- **Plotly візуалізації**: {'✅' if PLOTLY_AVAILABLE else '❌'}
|
575 |
+
- **Gradio версія**: {gr.__version__}
|
576 |
+
{'**Помилка MAI-DX**: ' + IMPORT_ERROR if not MAI_DX_AVAILABLE else ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
577 |
### 📖 Короткий гайд
|
578 |
+
1. Оберіть готовий випадок або введіть свій.
|
579 |
+
2. Налаштуйте режим, бюджет та модель.
|
580 |
+
3. Натисніть "Запустити діагностику".
|
581 |
+
4. Аналізуйте результати та логи взаємодії агентів.
|
582 |
+
5. Переходьте на вкладку "Аналітика" для перегляду історії.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
583 |
""")
|
584 |
|
585 |
return demo
|
|
|
596 |
demo.launch(
|
597 |
server_name="0.0.0.0",
|
598 |
server_port=7860,
|
599 |
+
share=True,
|
600 |
+
debug=False
|
|
|
|
|
|
|
601 |
)
|