DocUA commited on
Commit
143d94c
·
1 Parent(s): d963484

Видалено застарілі файли, включаючи модулі для логування, інтерфейси Gradio та приклади використання. Оновлено документацію для відображення нових змін у структурі проекту.

Browse files
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: indigo
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.36.2
8
- app_file: lmai_dx_interface_log.py
9
  pinned: false
10
  short_description: MAI-DX Enhanced Research Platform
11
  ---
 
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.36.2
8
+ app_file: run_mai_dx_fixed.py
9
  pinned: false
10
  short_description: MAI-DX Enhanced Research Platform
11
  ---
agent_conversation_logger.py DELETED
@@ -1,254 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Модуль для логування та управління діагностичними сесіями MAI-DX.
4
-
5
- Цей модуль забезпечує:
6
- - Створення та відстеження окремих діагностичних сесій.
7
- - Запис детальних повідомлень від кожного ШІ-агента.
8
- - Збереження повної інформації про сесію у форматі JSON.
9
- - Надання функцій для аналітики: перелік сесій, експорт у CSV та генерація звітів.
10
- """
11
- import os
12
- import json
13
- import csv
14
- from datetime import datetime
15
- from typing import List, Dict, Any, Optional
16
- from dataclasses import dataclass, asdict, field
17
- import threading
18
- import uuid
19
-
20
- @dataclass
21
- class DiagnosisSession:
22
- """
23
- Датаклас для зберігання повної інформації про одну діагностичну сесію.
24
- """
25
- case_id: str
26
- timestamp: str
27
- case_name: str
28
- patient_info: str
29
- mode: str
30
- budget: int
31
- diagnosis: str = "N/A"
32
- confidence: float = 0.0
33
- cost: float = 0.0
34
- iterations: int = 0
35
- duration: float = 0.0
36
- status: str = "In Progress"
37
- reasoning: str = "N/A"
38
- messages: List[Dict[str, Any]] = field(default_factory=list)
39
-
40
- class AgentConversationLogger:
41
- """
42
- Клас для управління логуванням діагностичних сесій.
43
- Зберігає кожну сесію в окремому JSON-файлі для легкого доступу та аналізу.
44
- """
45
-
46
- def __init__(self, log_dir: str = "mai_dx_logs"):
47
- """
48
- Ініціалізує логгер, створюючи директорію для логів, якщо її не існує.
49
-
50
- Args:
51
- log_dir (str): Директорія для збереження файлів логів.
52
- """
53
- self.log_dir = log_dir
54
- os.makedirs(self.log_dir, exist_ok=True)
55
- # Використовуємо локальне сховище потоку для ізоляції сесій
56
- self.thread_local = threading.local()
57
- print(f"✅ Справжній AgentConversationLogger ініціалізовано. Логи зберігаються в '{self.log_dir}'.")
58
-
59
- def start_conversation(self, case_name: str, patient_info: str, mode: str) -> str:
60
- """
61
- Розпочинає нову діагностичну сесію.
62
-
63
- Args:
64
- case_name (str): Назва клінічного випадку.
65
- patient_info (str): Вхідна інформація про пацієнта.
66
- mode (str): Режим діагностики.
67
-
68
- Returns:
69
- str: Унікальний ідентифікатор створеної сесії (case_id).
70
- """
71
- case_id = f"case_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}"
72
- session = DiagnosisSession(
73
- case_id=case_id,
74
- timestamp=datetime.now().isoformat(),
75
- case_name=case_name,
76
- patient_info=patient_info,
77
- mode=mode,
78
- budget=0 # Буде встановлено пізніше, якщо потрібно
79
- )
80
- self.thread_local.session = session
81
- return case_id
82
-
83
- def log_agent_message(self, agent_name: str, action: str, content: str, reasoning: str, confidence: float, cost: float, iteration: int):
84
- """
85
- Логує повідомлення від одного з ШІ-агентів.
86
- """
87
- if not hasattr(self.thread_local, 'session'):
88
- print("⚠️ Попередження: Спроба логування без активної сесії.")
89
- return
90
-
91
- message = {
92
- "timestamp": datetime.now().isoformat(),
93
- "agent": agent_name,
94
- "action": action,
95
- "content": content,
96
- "reasoning": reasoning,
97
- "confidence": confidence,
98
- "cost": cost,
99
- "iteration": iteration,
100
- }
101
- self.thread_local.session.messages.append(message)
102
-
103
- def end_conversation(self, final_diagnosis: str, confidence: float, cost: float, **kwargs) -> str:
104
- """
105
- Завершує сесію, оновлює фінальні дані та зберігає її у файл.
106
-
107
- Returns:
108
- str: Ідентифікатор збереженої сесії.
109
- """
110
- if not hasattr(self.thread_local, 'session'):
111
- print("⚠️ Попередження: Спроба завершити неіснуючу сесію.")
112
- return "no_active_session"
113
-
114
- session = self.thread_local.session
115
-
116
- # Оновлення фінальних даних сесії
117
- session.diagnosis = final_diagnosis
118
- session.confidence = confidence
119
- session.cost = cost
120
- session.status = "✅ Успішно" if confidence >= 3.0 else "⚠️ Потребує перегляду"
121
- session.reasoning = kwargs.get('reasoning', 'N/A')
122
- session.iterations = kwargs.get('iterations', len(session.messages))
123
-
124
- start_time = datetime.fromisoformat(session.timestamp)
125
- session.duration = (datetime.now() - start_time).total_seconds()
126
-
127
- # Збереження сесії у файл
128
- file_path = os.path.join(self.log_dir, f"{session.case_id}.json")
129
- with open(file_path, 'w', encoding='utf-8') as f:
130
- json.dump(asdict(session), f, ensure_ascii=False, indent=4)
131
-
132
- del self.thread_local.session # Очищення сесії для поточного потоку
133
- return session.case_id
134
-
135
- def list_conversations(self) -> List[Dict[str, Any]]:
136
- """
137
- Зчитує всі збережені сесії з директорії логів.
138
-
139
- Returns:
140
- List[Dict[str, Any]]: Список словників, де кожен словник - це одна сесія.
141
- """
142
- all_sessions = []
143
- for filename in sorted(os.listdir(self.log_dir), reverse=True):
144
- if filename.endswith(".json"):
145
- try:
146
- with open(os.path.join(self.log_dir, filename), 'r', encoding='utf-8') as f:
147
- data = json.load(f)
148
- # Видаляємо детальні повідомлення для короткого списку
149
- data.pop('messages', None)
150
- all_sessions.append(data)
151
- except (json.JSONDecodeError, IOError) as e:
152
- print(f"Помилка читання файлу логу {filename}: {e}")
153
- return all_sessions
154
-
155
- def export_analytics_csv(self) -> str:
156
- """
157
- Експортує зведену аналітику по всіх сесіях у CSV файл.
158
-
159
- Returns:
160
- str: Назва створеного CSV файлу.
161
- """
162
- sessions = self.list_conversations()
163
- if not sessions:
164
- return "Немає даних для експорту."
165
-
166
- filename = f"analytics_report_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
167
- file_path = os.path.join(self.log_dir, filename)
168
-
169
- # Визначаємо поля (колонки) на основі ключів першої сесії
170
- fieldnames = sessions[0].keys()
171
-
172
- with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
173
- writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
174
- writer.writeheader()
175
- writer.writerows(sessions)
176
-
177
- return file_path
178
-
179
- def export_conversation_report(self, case_id: str, format: str = 'html') -> str:
180
- """
181
- Створює детальний звіт для конкретного випадку.
182
-
183
- Args:
184
- case_id (str): Ідентифікатор сесії для звіту.
185
- format (str): Формат звіту ('html' або 'json').
186
-
187
- Returns:
188
- str: Назва створеного файлу звіту.
189
- """
190
- file_path = os.path.join(self.log_dir, f"{case_id}.json")
191
- try:
192
- with open(file_path, 'r', encoding='utf-8') as f:
193
- data = json.load(f)
194
- except FileNotFoundError:
195
- return f"Помилка: Файл для випадку {case_id} не знайдено."
196
-
197
- if format == 'json':
198
- return file_path
199
-
200
- # Генерація HTML-звіту
201
- report_filename = f"report_{case_id}.html"
202
- report_path = os.path.join(self.log_dir, report_filename)
203
-
204
- html = f"""
205
- <html>
206
- <head>
207
- <title>Звіт по випадку: {data['case_name']}</title>
208
- <style>
209
- body {{ font-family: sans-serif; line-height: 1.6; padding: 20px; }}
210
- h1, h2 {{ color: #333; }}
211
- table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
212
- th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
213
- th {{ background-color: #f2f2f2; }}
214
- .summary {{ background-color: #eef; padding: 15px; border-radius: 8px; }}
215
- </style>
216
- </head>
217
- <body>
218
- <h1>Клінічний звіт по випадку: {data['case_name']}</h1>
219
- <p><strong>ID:</strong> {data['case_id']}</p>
220
- <p><strong>Час:</strong> {data['timestamp']}</p>
221
-
222
- <div class="summary">
223
- <h2>Підсумки діагностики</h2>
224
- <p><strong>Фінальний діагноз:</strong> {data['diagnosis']}</p>
225
- <p><strong>Впевненість:</strong> {data['confidence']}/5.0</p>
226
- <p><strong>Вартість:</strong> ${data['cost']}</p>
227
- <p><strong>Тривалість:</strong> {data['duration']:.2f} сек.</p>
228
- </div>
229
-
230
- <h2>Детальний лог взаємодії агентів</h2>
231
- <table>
232
- <tr>
233
- <th>Час</th><th>Агент</th><th>Дія</th><th>Результат/Контент</th>
234
- </tr>
235
- """
236
- for msg in data.get('messages', []):
237
- html += f"""
238
- <tr>
239
- <td>{datetime.fromisoformat(msg['timestamp']).strftime('%H:%M:%S')}</td>
240
- <td>{msg['agent']}</td>
241
- <td>{msg['action']}</td>
242
- <td>{msg['content']}</td>
243
- </tr>
244
- """
245
- html += """
246
- </table>
247
- </body>
248
- </html>
249
- """
250
-
251
- with open(report_path, 'w', encoding='utf-8') as f:
252
- f.write(html)
253
-
254
- return report_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
enhanced_mai_dx_logger.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Покращений модуль для повного логування діагностичних сесій MAI-DX
4
+ з детальним захопленням розмов між агентами.
5
+ """
6
+ import os
7
+ import io
8
+ import sys
9
+ import json
10
+ import time
11
+ import uuid
12
+ import re
13
+ import logging
14
+ from datetime import datetime
15
+ from typing import List, Dict, Any, Optional, Callable
16
+ from dataclasses import dataclass, asdict, field
17
+ from contextlib import contextmanager, redirect_stdout, redirect_stderr
18
+ import threading
19
+ from queue import Queue
20
+
21
+ @dataclass
22
+ class AgentMessage:
23
+ """Повідомлення від одного агента"""
24
+ timestamp: str
25
+ agent_name: str
26
+ message_type: str # 'input', 'reasoning', 'output', 'decision'
27
+ content: str
28
+ metadata: Dict[str, Any] = field(default_factory=dict)
29
+
30
+ @dataclass
31
+ class AgentConversation:
32
+ """Повна розмова між агентами для одного раунду"""
33
+ round_number: int
34
+ start_time: str
35
+ end_time: Optional[str] = None
36
+ messages: List[AgentMessage] = field(default_factory=list)
37
+ decision: Optional[str] = None
38
+ tests_ordered: List[str] = field(default_factory=list)
39
+ questions_asked: List[str] = field(default_factory=list)
40
+ cost_incurred: float = 0.0
41
+
42
+ @dataclass
43
+ class DiagnosisSession:
44
+ """Повна діагностична сесія з усіма розмовами агентів"""
45
+ case_id: str
46
+ timestamp: str
47
+ case_name: str
48
+ patient_info: str
49
+ mode: str
50
+ budget: int = 0
51
+ diagnosis: str = "N/A"
52
+ confidence: float = 0.0
53
+ cost: float = 0.0
54
+ iterations: int = 0
55
+ duration: float = 0.0
56
+ status: str = "In Progress"
57
+ reasoning: str = "N/A"
58
+ conversations: List[AgentConversation] = field(default_factory=list)
59
+ raw_output: str = "" # Повний сирий вивід
60
+
61
+ class EnhancedOutputCapture:
62
+ """Клас для захоплення всіх видів виводу з різних джерел"""
63
+ def __init__(self):
64
+ self.captured_output = io.StringIO()
65
+ self.original_stdout = sys.stdout
66
+ self.original_stderr = sys.stderr
67
+ self.original_write = None
68
+ self.log_queue = Queue()
69
+ self.capture_thread = None
70
+ self.is_capturing = False
71
+ self.log_handler = None
72
+
73
+ def start_capture(self):
74
+ """Розпочати захоплення виводу"""
75
+ self.is_capturing = True
76
+ sys.stdout = self
77
+ sys.stderr = self
78
+
79
+ # Перехоплення логів
80
+ self._setup_logging_capture()
81
+
82
+ def stop_capture(self):
83
+ """Зупинити захоплення виводу"""
84
+ self.is_capturing = False
85
+ sys.stdout = self.original_stdout
86
+ sys.stderr = self.original_stderr
87
+
88
+ # Видаляємо log handler
89
+ if self.log_handler:
90
+ logging.root.removeHandler(self.log_handler)
91
+
92
+ captured_text = self.captured_output.getvalue()
93
+ # Очищаємо ANSI escape коди
94
+ return self._strip_ansi_codes(captured_text)
95
+
96
+ def write(self, text):
97
+ """Перехоплення всіх записів"""
98
+ if self.is_capturing:
99
+ self.captured_output.write(text)
100
+ self.original_stdout.write(text) # Дублюємо на консоль
101
+
102
+ def flush(self):
103
+ """Flush для сумісності"""
104
+ if hasattr(self.captured_output, 'flush'):
105
+ self.captured_output.flush()
106
+ self.original_stdout.flush()
107
+
108
+ def _setup_logging_capture(self):
109
+ """Налаштування перехоплення логів"""
110
+ class QueueHandler(logging.Handler):
111
+ def __init__(self, queue):
112
+ super().__init__()
113
+ self.queue = queue
114
+
115
+ def emit(self, record):
116
+ self.queue.put(self.format(record))
117
+
118
+ # Додаємо handler до root logger
119
+ self.log_handler = QueueHandler(self.log_queue)
120
+ logging.root.addHandler(self.log_handler)
121
+
122
+ @staticmethod
123
+ def _strip_ansi_codes(text):
124
+ """Видалити ANSI escape коди з тексту"""
125
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
126
+ return ansi_escape.sub('', text)
127
+
128
+ class MAIDxConversationLogger:
129
+ """Покращений логгер для MAI-DX з повним захопленням розмов"""
130
+
131
+ def __init__(self, log_dir: str = "mai_dx_logs"):
132
+ self.log_dir = log_dir
133
+ os.makedirs(self.log_dir, exist_ok=True)
134
+ self.sessions: Dict[str, DiagnosisSession] = {}
135
+ self.output_capture = EnhancedOutputCapture()
136
+
137
+ def start_session(self, case_name: str, patient_info: str, mode: str, budget: int) -> str:
138
+ """Розпочати нову діагностичну сесію"""
139
+ case_id = f"case_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}"
140
+ session = DiagnosisSession(
141
+ case_id=case_id,
142
+ timestamp=datetime.now().isoformat(),
143
+ case_name=case_name,
144
+ patient_info=patient_info,
145
+ mode=mode,
146
+ budget=budget
147
+ )
148
+ self.sessions[case_id] = session
149
+ return case_id
150
+
151
+ def capture_orchestrator_output(self, case_id: str, orchestrator_func: Callable, *args, **kwargs):
152
+ """Захопити весь вивід від orchestrator.run()"""
153
+ if case_id not in self.sessions:
154
+ raise ValueError(f"Сесія {case_id} не існує")
155
+
156
+ session = self.sessions[case_id]
157
+
158
+ # Розпочинаємо захоплення
159
+ self.output_capture.start_capture()
160
+
161
+ try:
162
+ # Виконуємо функцію orchestrator
163
+ result = orchestrator_func(*args, **kwargs)
164
+
165
+ # Зупиняємо захоплення
166
+ captured_text = self.output_capture.stop_capture()
167
+ session.raw_output = captured_text
168
+
169
+ # Парсимо захоплений текст
170
+ self._parse_captured_output(case_id, captured_text)
171
+
172
+ return result
173
+
174
+ except Exception as e:
175
+ # Завжди зупиняємо захоплення
176
+ self.output_capture.stop_capture()
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
+ for i, line in enumerate(lines):
191
+ # Пропускаємо порожні рядки та помилки Rich
192
+ if not line.strip() or "rich.errors.NotRenderableError" in line or "Exception in thread" in line:
193
+ continue
194
+
195
+ # Початок нового раунду
196
+ if "Starting Diagnostic Loop" in line:
197
+ if current_round and buffer:
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
+ round_number = int(round_match.group(1))
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
+ if in_agent_output and ("╰" in line or "└" in line):
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
+ if in_agent_output and current_agent:
240
+ # Очищаємо лінію від символів рамки
241
+ clean_line = re.sub(r'^[│|]\s*', '', line)
242
+ clean_line = re.sub(r'\s*[│|]\s*', '', clean_line)
243
+ if clean_line.strip():
244
+ buffer.append(clean_line.strip())
245
+ continue
246
+
247
+ # Логи від INFO рівня
248
+ if " | INFO " in line:
249
+ # Парсимо INFO логи
250
+ info_match = re.search(r'mai_dx\.main:(\w+):.*? - (.+)', line)
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+) -> (.+)', content)
268
+ if decision_match:
269
+ current_round.decision = f"{decision_match.group(1)}: {decision_match.group(2)}"
270
+
271
+ if "Current cost:" in content:
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
+ if "tests ordered:" in content.lower():
277
+ # Можна додати парсинг тестів
278
+ pass
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 = {
291
+ 'Dr. Hypothesis': ['Dr. Hypothesis', 'differential diagnosis'],
292
+ 'Dr. Test-Chooser': ['Dr. Test-Chooser', 'selecting optimal tests'],
293
+ 'Dr. Challenger': ['Dr. Challenger', 'challenging assumptions'],
294
+ 'Dr. Stewardship': ['Dr. Stewardship', 'cost-effectiveness'],
295
+ 'Dr. Checklist': ['Dr. Checklist', 'quality control'],
296
+ 'Consensus Coordinator': ['Consensus Coordinator', 'synthesizing panel'],
297
+ 'Judge': ['Judge', 'evaluation'],
298
+ 'Gatekeeper': ['Gatekeeper']
299
+ }
300
+
301
+ for agent, patterns in agent_patterns.items():
302
+ for pattern in patterns:
303
+ if pattern in log_content:
304
+ return agent
305
+ return None
306
+
307
+ def _extract_agent_name(self, line: str) -> Optional[str]:
308
+ """Витягти ім'я агента з рядка"""
309
+ agents = [
310
+ "Dr. Hypothesis", "Dr. Test-Chooser", "Dr. Challenger",
311
+ "Dr. Stewardship", "Dr. Checklist", "Consensus Coordinator",
312
+ "Gatekeeper", "Judge"
313
+ ]
314
+
315
+ for agent in agents:
316
+ if agent in line:
317
+ return agent
318
+ return None
319
+
320
+ def _extract_round_number(self, line: str) -> int:
321
+ """Витягти номер раунду"""
322
+ import re
323
+ match = re.search(r'(\d+)', line)
324
+ return int(match.group(1)) if match else 0
325
+
326
+ def _extract_tests(self, line: str) -> List[str]:
327
+ """Витягти список тестів"""
328
+ # Тут потрібна більш складна логіка залежно від формату
329
+ return []
330
+
331
+ def _extract_questions(self, line: str) -> List[str]:
332
+ """Витягти список питань"""
333
+ # Тут потрібна більш складна логіка залежно від формату
334
+ return []
335
+
336
+ def _extract_cost(self, line: str) -> Optional[float]:
337
+ """Витягти вартість"""
338
+ import re
339
+ match = re.search(r'\$?(\d+(?:\.\d+)?)', line)
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
+ if not message_type:
348
+ message_type = self._determine_message_type(agent_name, content)
349
+
350
+ message = AgentMessage(
351
+ timestamp=datetime.now().isoformat(),
352
+ agent_name=agent_name,
353
+ message_type=message_type,
354
+ content=content.strip(),
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()
363
+
364
+ if "analyzing" in content_lower or "considering" in content_lower:
365
+ return "reasoning"
366
+ elif "recommend" in content_lower or "suggest" in content_lower:
367
+ return "decision"
368
+ elif "?" in content:
369
+ return "input"
370
+ else:
371
+ return "output"
372
+
373
+ def _save_buffered_conversation(self, session: DiagnosisSession, conversation: AgentConversation, buffer: List[str]):
374
+ """Зберегти буферизовану розмову"""
375
+ if conversation and buffer:
376
+ conversation.end_time = datetime.now().isoformat()
377
+ # Можна додати додаткову обробку
378
+
379
+ def end_session(self, case_id: str, **kwargs) -> str:
380
+ """Завершити сесію та зберегти всі дані"""
381
+ if case_id not in self.sessions:
382
+ return "no_active_session"
383
+
384
+ session = self.sessions[case_id]
385
+
386
+ # Оновлюємо фінальні дані
387
+ session.diagnosis = kwargs.get('final_diagnosis', session.diagnosis)
388
+ session.confidence = kwargs.get('confidence', session.confidence)
389
+ session.cost = kwargs.get('cost', session.cost)
390
+ session.reasoning = kwargs.get('reasoning', session.reasoning)
391
+ session.iterations = len(session.conversations)
392
+ session.status = "✅ Успішно" if session.confidence >= 3.0 else "⚠️ Потребує перегляду"
393
+
394
+ start_time = datetime.fromisoformat(session.timestamp)
395
+ session.duration = (datetime.now() - start_time).total_seconds()
396
+
397
+ # Зберігаємо у файл
398
+ self._save_session_to_file(session)
399
+
400
+ del self.sessions[case_id]
401
+ return case_id
402
+
403
+ def _save_session_to_file(self, session: DiagnosisSession):
404
+ """Зберегти сесію у JSON файл з повною структурою"""
405
+ file_path = os.path.join(self.log_dir, f"{session.case_id}.json")
406
+
407
+ # Конвертуємо в словник з усіма вкладеними даними
408
+ session_dict = asdict(session)
409
+
410
+ with open(file_path, 'w', encoding='utf-8') as f:
411
+ json.dump(session_dict, f, ensure_ascii=False, indent=2)
412
+
413
+ # Додатково зберігаємо сирий вивід
414
+ raw_output_path = os.path.join(self.log_dir, f"{session.case_id}_raw.txt")
415
+ with open(raw_output_path, 'w', encoding='utf-8') as f:
416
+ f.write(session.raw_output)
417
+
418
+ def get_session_conversations(self, case_id: str) -> List[AgentConversation]:
419
+ """Отримати всі розмови для сесії"""
420
+ file_path = os.path.join(self.log_dir, f"{case_id}.json")
421
+
422
+ if not os.path.exists(file_path):
423
+ # Спробувати з активних сесій
424
+ if case_id in self.sessions:
425
+ return self.sessions[case_id].conversations
426
+ return []
427
+
428
+ with open(file_path, 'r', encoding='utf-8') as f:
429
+ data = json.load(f)
430
+
431
+ # Реконструюємо об'єкти з JSON
432
+ conversations = []
433
+ for conv_data in data.get('conversations', []):
434
+ messages = [AgentMessage(**msg) for msg in conv_data.get('messages', [])]
435
+ conv = AgentConversation(
436
+ round_number=conv_data['round_number'],
437
+ start_time=conv_data['start_time'],
438
+ end_time=conv_data.get('end_time'),
439
+ messages=messages,
440
+ decision=conv_data.get('decision'),
441
+ tests_ordered=conv_data.get('tests_ordered', []),
442
+ questions_asked=conv_data.get('questions_asked', []),
443
+ cost_incurred=conv_data.get('cost_incurred', 0.0)
444
+ )
445
+ conversations.append(conv)
446
+
447
+ return conversations
448
+
449
+ def export_conversation_html(self, case_id: str) -> str:
450
+ """Експортувати розмови у читабельний HTML формат"""
451
+ conversations = self.get_session_conversations(case_id)
452
+
453
+ html_content = f"""
454
+ <!DOCTYPE html>
455
+ <html>
456
+ <head>
457
+ <meta charset="UTF-8">
458
+ <title>MAI-DX Conversation Log - {case_id}</title>
459
+ <style>
460
+ body {{ font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }}
461
+ .round {{ background: white; padding: 20px; margin: 20px 0; border-radius: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }}
462
+ .agent-message {{ margin: 10px 0; padding: 10px; border-left: 4px solid #007bff; background: #f8f9fa; }}
463
+ .agent-name {{ font-weight: bold; color: #007bff; }}
464
+ .message-type {{ font-size: 0.9em; color: #666; }}
465
+ .decision {{ background: #d4edda; border-color: #28a745; }}
466
+ .tests {{ background: #fff3cd; border-color: #ffc107; }}
467
+ .cost {{ color: #dc3545; font-weight: bold; }}
468
+ .raw-output {{ background: #2d3748; color: #e2e8f0; padding: 15px; border-radius: 5px; font-family: 'Courier New', monospace; white-space: pre-wrap; }}
469
+ </style>
470
+ </head>
471
+ <body>
472
+ <h1>MAI-DX Diagnostic Session: {case_id}</h1>
473
+ """
474
+
475
+ for conv in conversations:
476
+ html_content += f"""
477
+ <div class="round">
478
+ <h2>Round {conv.round_number}</h2>
479
+ <p><strong>Time:</strong> {conv.start_time} - {conv.end_time or 'In Progress'}</p>
480
+ """
481
+
482
+ for msg in conv.messages:
483
+ css_class = "agent-message"
484
+ if msg.message_type == "decision":
485
+ css_class += " decision"
486
+
487
+ html_content += f"""
488
+ <div class="{css_class}">
489
+ <div class="agent-name">{msg.agent_name}</div>
490
+ <div class="message-type">[{msg.message_type}]</div>
491
+ <div class="content">{msg.content}</div>
492
+ </div>
493
+ """
494
+
495
+ if conv.decision:
496
+ html_content += f'<div class="decision"><strong>Decision:</strong> {conv.decision}</div>'
497
+
498
+ if conv.tests_ordered:
499
+ html_content += f'<div class="tests"><strong>Tests:</strong> {", ".join(conv.tests_ordered)}</div>'
500
+
501
+ if conv.cost_incurred > 0:
502
+ html_content += f'<div class="cost">Cost: ${conv.cost_incurred:.2f}</div>'
503
+
504
+ html_content += "</div>"
505
+
506
+ html_content += """
507
+ </body>
508
+ </html>
509
+ """
510
+
511
+ # Зберігаємо HTML файл
512
+ html_path = os.path.join(self.log_dir, f"{case_id}_conversation.html")
513
+ with open(html_path, 'w', encoding='utf-8') as f:
514
+ f.write(html_content)
515
+
516
+ return html_path
lmai_dx_interface_log.py DELETED
@@ -1,601 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Виправлений MAI-DX Gradio Interface з сумісністю для нових версій Gradio
4
- """
5
- import os
6
- import sys
7
- import json
8
- import time
9
- import pandas as pd
10
- import gradio as gr
11
- from datetime import datetime, timedelta
12
- from typing import Dict, List, Tuple, Optional
13
- from dataclasses import dataclass, asdict
14
- import warnings
15
- import threading
16
- import queue
17
- import io
18
- import re # Імпортуємо re для парсингу
19
- from contextlib import redirect_stdout, redirect_stderr
20
-
21
- # Налаштування середовища
22
- os.environ.update({
23
- "SWARMS_VERBOSITY": "ERROR",
24
- "RICH_TRACEBACK": "0",
25
- "SWARMS_SHOW_PANEL": "false",
26
- "SWARMS_AUTO_PRINT": "false"
27
- })
28
- warnings.filterwarnings("ignore")
29
-
30
- from dotenv import load_dotenv
31
- load_dotenv()
32
-
33
- # Патч Rich formatter
34
- def patch_rich_formatter():
35
- try:
36
- import swarms.utils.formatter
37
- def dummy_print_panel(*args, **kwargs):
38
- pass
39
- if hasattr(swarms.utils.formatter, 'Formatter'):
40
- swarms.utils.formatter.Formatter._print_panel = dummy_print_panel
41
- return True
42
- except:
43
- return False
44
-
45
- patch_rich_formatter()
46
-
47
- # Імпорт MAI-DX
48
- try:
49
- from mai_dx import MaiDxOrchestrator
50
- MAI_DX_AVAILABLE = True
51
- except ImportError as e:
52
- MAI_DX_AVAILABLE = False
53
- IMPORT_ERROR = str(e)
54
-
55
- # Перевірка доступності Plotly
56
- try:
57
- import plotly.graph_objects as go
58
- import plotly.express as px
59
- PLOTLY_AVAILABLE = True
60
- except ImportError:
61
- PLOTLY_AVAILABLE = False
62
- print("⚠️ Plotly не встановлено, візуалізації будуть недоступні")
63
-
64
- # Імпорт логгера (створюємо заглушку якщо немає)
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
78
- mode: str
79
- budget: int
80
- diagnosis: str
81
- confidence: float
82
- cost: float
83
- iterations: int
84
- duration: float
85
- status: str
86
- reasoning: str
87
-
88
- class AgentConversationLogger:
89
- def __init__(self, *args, **kwargs):
90
- self.conversations = []
91
- def start_conversation(self, *args):
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):
102
- return "analytics.csv"
103
-
104
- class RealTimeMetrics:
105
- """Метрики в реальному часі"""
106
-
107
- def __init__(self):
108
- self.reset()
109
-
110
- def reset(self):
111
- self.start_time = time.time()
112
- self.agents_activity = {
113
- 'Dr. Hypothesis': 0,
114
- 'Dr. Test-Chooser': 0,
115
- 'Dr. Challenger': 0,
116
- 'Dr. Stewardship': 0,
117
- 'Dr. Checklist': 0,
118
- 'Consensus Coordinator': 0,
119
- 'Gatekeeper': 0,
120
- 'Judge': 0
121
- }
122
- self.cost_progression = []
123
- self.confidence_progression = []
124
- self.decision_timeline = []
125
-
126
- def update_agent_activity(self, agent_name: str):
127
- if agent_name in self.agents_activity:
128
- self.agents_activity[agent_name] += 1
129
-
130
- def add_cost_point(self, cost: float):
131
- self.cost_progression.append({
132
- 'time': time.time() - self.start_time,
133
- 'cost': cost
134
- })
135
-
136
- def add_confidence_point(self, confidence: float):
137
- self.confidence_progression.append({
138
- 'time': time.time() - self.start_time,
139
- 'confidence': confidence
140
- })
141
-
142
- class EnhancedMAIDXInterface:
143
- """Покращений інтерфейс з сумісністю для Gradio"""
144
-
145
- def __init__(self):
146
- self.sessions_history = []
147
- self.conversation_logger = AgentConversationLogger("mai_dx_logs")
148
- self.current_metrics = RealTimeMetrics()
149
-
150
- # Розширені тестові кейси (з виправленими очікуваними діагнозами)
151
- self.sample_cases = {
152
- "🫀 Кардіологічний (Гострий MI)": {
153
- "info": """Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі
154
- Скарги: Гострий роздираючий біль у грудях 3 години, іррадіація в ліву руку
155
- Огляд: Блідий, пітливий, АТ 160/90, ЧСС 95
156
- ЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)
157
- Тропонін I: 8.5 нг/мл (норма <0.04)
158
- Анамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС""",
159
- "expected": "Acute inferior wall myocardial infarction (STEMI)"
160
- },
161
-
162
- "🧠 Неврологічний (Гострий інсульт)": {
163
- "info": """Пацієнтка: 67-річна жінка, раптові неврологічні симптоми
164
- Презентація: Раптова слабкість правої сторони 2 години тому
165
- Огляд: Свідома, дезорієнтована у часі, правостороннє опущення обличчя
166
- Неврологія: Правостороння геміплегія, афазія, девіація очей вліво
167
- КТ голови: Гострого крововиливу немає, рання ішемія у лівій МСА
168
- NIHSS: 15 балів""",
169
- "expected": "Acute ischemic stroke in the left middle cerebral artery territory"
170
- },
171
-
172
- "🦠 Інфекційний (Сепсис)": {
173
- "info": """Пацієнт: 45-річний чоловік з прогресуючою лихоманкою
174
- Скарги: Висока температура 39.5°C, озноб, загальна слабкість 3 дні
175
- Огляд: Гарячий, тахікардія 120/хв, гіпотензія 85/50
176
- Лабораторно: Лейкоцити 18000, С-реактивний білок 180, прокальцитонін 5.2
177
- Посів крові: Pending, lactate 4.2 ммоль/л
178
- Анамнез: Нещодавна стоматологічна процедура""",
179
- "expected": "Sepsis with a possible odontogenic source"
180
- }
181
- }
182
-
183
- def diagnose_with_enhanced_tracking(
184
- self,
185
- case_name: str,
186
- patient_info: str,
187
- mode: str,
188
- budget: int,
189
- max_iterations: int,
190
- model_name: str,
191
- expected_diagnosis: str = "",
192
- enable_logging: bool = True,
193
- progress=gr.Progress()
194
- ) -> Tuple[str, str, str, Optional[object], Optional[object], str]:
195
- """Діагностика з покращеним відстеженням - ВИПРАВЛЕНА ВЕРСІЯ"""
196
-
197
- if not MAI_DX_AVAILABLE:
198
- return self._format_error(f"❌ MAI-DX недоступний: {IMPORT_ERROR}")
199
-
200
- if not patient_info.strip():
201
- return self._format_error("❌ Введіть інформацію про пацієнта")
202
-
203
- self.current_metrics.reset()
204
- conversation_log = ""
205
- case_id = None
206
-
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')}",
213
- patient_info,
214
- mode
215
- )
216
- conversation_log += f"📝 Розпочато логування: {case_id}\n\n"
217
-
218
- progress(0.2, desc="🤖 Створення AI-панелі лікарів...")
219
-
220
- orchestrator = MaiDxOrchestrator(
221
- model_name=model_name,
222
- max_iterations=max_iterations,
223
- initial_budget=budget,
224
- mode=mode if mode != "ensemble" else "budgeted"
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,
285
- mode=mode,
286
- budget=budget,
287
- diagnosis=result.final_diagnosis,
288
- confidence=result.accuracy_score,
289
- cost=result.total_cost,
290
- iterations=getattr(result, 'iterations', max_iterations),
291
- duration=duration,
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),
305
- self._generate_enhanced_recommendations(result, expected_diagnosis),
306
- metrics_plot,
307
- agent_plot,
308
- conversation_log
309
- )
310
-
311
- except Exception as e:
312
- error_msg = f"❌ Помилка діагностики: {str(e)}"
313
- if case_id:
314
- error_msg += f"\n🗂️ Case ID: {case_id}"
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
586
-
587
- if __name__ == "__main__":
588
- print("🚀 Запуск виправленого MAI-DX Enhanced Research Platform...")
589
- print(f"🤖 MAI-DX доступність: {'✅' if MAI_DX_AVAILABLE else '❌'}")
590
- print(f"📊 Plotly візуалізації: {'✅' if PLOTLY_AVAILABLE else '❌'}")
591
- print(f"📝 Logger: {'✅' if LOGGER_AVAILABLE else '⚠️ заглушка'}")
592
- print("🔍 Логи зберігаються у: mai_dx_logs/")
593
-
594
- demo = create_enhanced_gradio_interface()
595
-
596
- demo.launch(
597
- server_name="0.0.0.0",
598
- server_port=7860,
599
- share=True,
600
- debug=False
601
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mai_dx_interface.py DELETED
@@ -1,855 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- MAI-DX Gradio Research Interface
4
- Зручний веб-інтерфейс для дослідження медичної діагностики з ШІ
5
- """
6
- import os
7
- import sys
8
- import json
9
- import time
10
- import pandas as pd
11
- import gradio as gr
12
- from datetime import datetime
13
- from typing import Dict, List, Tuple, Optional
14
- from dataclasses import dataclass, asdict
15
- import warnings
16
-
17
- # Налаштування для запобігання помилок
18
- os.environ["SWARMS_VERBOSITY"] = "ERROR"
19
- os.environ["RICH_TRACEBACK"] = "0"
20
- os.environ["SWARMS_SHOW_PANEL"] = "false"
21
- os.environ["SWARMS_AUTO_PRINT"] = "false"
22
- warnings.filterwarnings("ignore")
23
-
24
- # Завантаження змінних середовища
25
- from dotenv import load_dotenv
26
- load_dotenv()
27
-
28
- # Патч Rich formatter
29
- def patch_rich_formatter():
30
- try:
31
- import swarms.utils.formatter
32
- def dummy_print_panel(*args, **kwargs):
33
- pass
34
- if hasattr(swarms.utils.formatter, 'Formatter'):
35
- swarms.utils.formatter.Formatter._print_panel = dummy_print_panel
36
- return True
37
- except:
38
- return False
39
-
40
- patch_rich_formatter()
41
-
42
- # Імпорт MAI-DX
43
- try:
44
- from mai_dx import MaiDxOrchestrator
45
- MAI_DX_AVAILABLE = True
46
- except ImportError as e:
47
- MAI_DX_AVAILABLE = False
48
- IMPORT_ERROR = str(e)
49
-
50
- @dataclass
51
- class DiagnosisSession:
52
- """Структура для зберігання сесії діагностики"""
53
- timestamp: str
54
- case_name: str
55
- patient_info: str
56
- mode: str
57
- budget: int
58
- diagnosis: str
59
- confidence: float
60
- cost: float
61
- iterations: int
62
- duration: float
63
- status: str
64
- reasoning: str = ""
65
-
66
- class MAIDXGradioInterface:
67
- """Gradio інтерфейс для MAI-DX"""
68
-
69
- def __init__(self):
70
- self.sessions_history = []
71
- self.current_session = None
72
-
73
- # Перевірка доступності MAI-DX
74
- if not MAI_DX_AVAILABLE:
75
- print(f"❌ MAI-DX не доступний: {IMPORT_ERROR}")
76
- print("📋 Інструкції установки:")
77
- print(" pip install mai-dx python-dotenv")
78
- print(" Створіть .env файл з API ключами")
79
-
80
- # Предвизначені тестові кейси
81
- self.sample_cases = {
82
- "Кардіологічний (Інфаркт)": """
83
- Пацієнт: 58-річний чоловік, менеджер
84
- Скарги: Гострий біль у грудях протягом 3 годин
85
-
86
- Анамнез:
87
- - Біль почався під час підйому по сходах
88
- - Сильний, здавлюючий, іррадіює в ліву руку та щелепу
89
- - Супроводжується потовиділенням, нудотою, задишкою
90
- - Не зменшується у спокої
91
-
92
- Фактори ризику:
93
- - Цукровий діабет 2 типу
94
- - Артеріальна гіпертензія
95
- - Куріння 2 пачки на день протягом 30 років
96
- - Сімейний анамнез: батько помер від ІМ у 52 роки
97
-
98
- Огляд:
99
- - Дискомфорт, потовиділення
100
- - АТ 180/110, ЧСС 105, ЧД 24
101
- - Серце: S4 галоп, шумів немає
102
- - Легені: ясні з обох сторін
103
-
104
- ЕКГ: ST-підйоми у відведеннях II, III, aVF
105
- Тропонін I: 8.5 нг/мл (норма <0.04)
106
- """,
107
-
108
- "Неврологічний (Інсульт)": """
109
- Пацієнтка: 67-річна жінка з раптовими неврологічними симптомами
110
-
111
- Презентація:
112
- - Раптова слабкість правої сторони 2 години тому
113
- - Утруднення мови (невиразна мова)
114
- - Опущення правої сторони обличчя
115
- - Без втрати свідомості
116
-
117
- Анамнез:
118
- - Фібриляція передсердь (не приймає антикоагулянти)
119
- - Артеріальна гіпертензія
120
- - Попередня ТІА 6 місяців тому
121
-
122
- Огляд:
123
- - У свідомості, але плутається
124
- - Правостороннє опущення обличчя
125
- - Дрейф правої руки
126
- - Дизартрія
127
- - NIHSS балів: 8
128
-
129
- КТ голови: Гострого крововиливу немає
130
- Час від початку: 2 години 15 хвилин
131
- """,
132
-
133
- "Педіатричний (Отит)": """
134
- Пацієнт: 3-річний хлопчик, привели батьки через лихоманку
135
-
136
- Анамнез:
137
- - Лихоманка до 39.5°C протягом 2 днів
138
- - Зменшення апетиту та активності
139
- - Тягне за праве вухо
140
- - Плаче більше звичайного, особли��о лежачи
141
- - Без кашлю, нежиті або блювання
142
-
143
- Анамнез життя:
144
- - Доношена вагітність, нормальний розвиток
145
- - Щеплення за календарем
146
- - Алергії невідомі
147
-
148
- Огляд:
149
- - Температура: 39.2°C, ЧСС: 130, ЧД: 28
150
- - Загалом дратівливий, але заспокоюється
151
- - ЛОР: права барабанна перетинка еритематозна і випнута
152
- - Шия: м'яка, лімфаденопатії немає
153
- - Груди: ясні з обох сторін
154
- - Живіт: м'який, безболісний
155
- """,
156
-
157
- "Ендокринологічний (Гіпертиреоз)": """
158
- Пацієнтка: 45-річна жінка з прогресуючою втомою
159
-
160
- Скарги: "Постійно втомлююся і худну, хоча їм"
161
-
162
- Анамнез захворювання:
163
- - 6-місячна історія прогресуючої втоми
164
- - Ненавмисне схуднення на 7 кг
165
- - Непереносимість спеки та надмірне потовиділення
166
- - Серцебиття та тривожність
167
- - Порушення сну
168
- - Тремор рук
169
-
170
- Огляд систем:
171
- - Часті випорожнення (3-4 рази на день)
172
- - Випадіння волосся
173
- - Порушення менструального циклу
174
-
175
- Огляд:
176
- - АТ 150/85, ЧСС 110, ЧД 16, Т 37.1°C
177
- - Худорлява, тривожна жінка
178
- - Очі: легкий екзофтальм, симптом запізнення повік
179
- - Щитовидна залоза: дифузно збільшена
180
- - Серце: тахікардія, шумів немає
181
- - Тремор витягнутих рук
182
- - Шкіра: тепла та волога
183
- """
184
- }
185
-
186
- def check_api_keys(self) -> Tuple[str, str]:
187
- """Перевірка наявності API ключів"""
188
- required_keys = ["OPENAI_API_KEY", "GEMINI_API_KEY", "ANTHROPIC_API_KEY"]
189
- missing_keys = []
190
- available_keys = []
191
-
192
- for key in required_keys:
193
- if os.getenv(key):
194
- available_keys.append(key)
195
- else:
196
- missing_keys.append(key)
197
-
198
- if available_keys:
199
- status = f"✅ Доступні ключі: {', '.join(available_keys)}"
200
- color = "green"
201
- else:
202
- status = f"❌ Відсутні всі API ключі: {', '.join(missing_keys)}"
203
- color = "red"
204
-
205
- if missing_keys:
206
- status += f"\n⚠️ Відсутні: {', '.join(missing_keys)}"
207
-
208
- return status, color
209
-
210
- def diagnose_case(
211
- self,
212
- case_name: str,
213
- patient_info: str,
214
- mode: str,
215
- budget: int,
216
- max_iterations: int,
217
- model_name: str,
218
- expected_diagnosis: str = "",
219
- progress=gr.Progress()
220
- ) -> Tuple[str, str, str, pd.DataFrame]:
221
- """Основна функція діагностики"""
222
-
223
- if not MAI_DX_AVAILABLE:
224
- return (
225
- f"❌ MAI-DX не доступний: {IMPORT_ERROR}",
226
- "",
227
- "",
228
- pd.DataFrame()
229
- )
230
-
231
- if not patient_info.strip():
232
- return (
233
- "❌ Будь ласка, введіть інформацію про пацієнта",
234
- "",
235
- "",
236
- pd.DataFrame()
237
- )
238
-
239
- # Початок діагностики
240
- progress(0.1, desc="Ініціалізація MAI-DX...")
241
-
242
- start_time = time.time()
243
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
244
-
245
- try:
246
- # Створення оркестратора
247
- progress(0.2, desc="Створення діагностичного оркестратора...")
248
-
249
- orchestrator = MaiDxOrchestrator(
250
- model_name=model_name,
251
- max_iterations=max_iterations,
252
- initial_budget=budget,
253
- mode=mode if mode != "ensemble" else "budgeted" # Ensemble використовує базовий режим
254
- )
255
-
256
- progress(0.3, desc="Початок аналізу медичного випадку...")
257
-
258
- # Запуск діагностики
259
- if mode == "ensemble":
260
- # Для ensemble режиму використовуємо run_ensemble якщо доступний
261
- progress(0.4, desc="Запуск ensemble діагностики (кілька прогонів)...")
262
- try:
263
- result = orchestrator.run_ensemble(
264
- initial_case_info=patient_info,
265
- full_case_details=patient_info,
266
- ground_truth_diagnosis=expected_diagnosis or "Unknown",
267
- num_runs=2 # Зменшено для швидкості в інтерфейсі
268
- )
269
- except AttributeError:
270
- # Якщо run_ensemble недоступний, використовуємо звичайний run
271
- progress(0.4, desc="Ensemble недоступний, використовуємо стандартний режим...")
272
- result = orchestrator.run(
273
- initial_case_info=patient_info,
274
- full_case_details=patient_info,
275
- ground_truth_diagnosis=expected_diagnosis or "Unknown"
276
- )
277
- else:
278
- result = orchestrator.run(
279
- initial_case_info=patient_info,
280
- full_case_details=patient_info,
281
- ground_truth_diagnosis=expected_diagnosis or "Unknown"
282
- )
283
-
284
- progress(0.8, desc="Обробка результатів...")
285
-
286
- duration = time.time() - start_time
287
-
288
- # Створення сесії
289
- session = DiagnosisSession(
290
- timestamp=timestamp,
291
- case_name=case_name or f"Case_{len(self.sessions_history) + 1}",
292
- patient_info=patient_info[:200] + "..." if len(patient_info) > 200 else patient_info,
293
- mode=mode,
294
- budget=budget,
295
- diagnosis=result.final_diagnosis,
296
- confidence=result.accuracy_score,
297
- cost=result.total_cost,
298
- iterations=result.iterations,
299
- duration=duration,
300
- status="Успішно" if result.accuracy_score >= 2.0 else "Потребує перегляду",
301
- reasoning=getattr(result, 'accuracy_reasoning', 'Недоступно')[:300] + "..."
302
- )
303
-
304
- self.sessions_history.append(session)
305
- self.current_session = session
306
-
307
- progress(1.0, desc="Готово!")
308
-
309
- # Форматування результатів
310
- main_result = self._format_main_result(session, result)
311
- detailed_analysis = self._format_detailed_analysis(session, result)
312
- recommendations = self._generate_recommendations(result)
313
- history_df = self._get_history_dataframe()
314
-
315
- return main_result, detailed_analysis, recommendations, history_df
316
-
317
- except Exception as e:
318
- error_msg = f"❌ Помилка діагностики: {str(e)}"
319
- return error_msg, "", "", pd.DataFrame()
320
-
321
- def _format_main_result(self, session: DiagnosisSession, result) -> str:
322
- """Форматування основного результату"""
323
-
324
- # Визначення рівня довіри
325
- if session.confidence >= 4.0:
326
- confidence_level = "🎉 ВИСОКИЙ"
327
- confidence_color = "green"
328
- elif session.confidence >= 3.0:
329
- confidence_level = "👍 СЕРЕДНІЙ"
330
- confidence_color = "orange"
331
- elif session.confidence >= 2.0:
332
- confidence_level = "⚠️ НИЗЬКИЙ"
333
- confidence_color = "orange"
334
- else:
335
- confidence_level = "❌ ДУЖЕ НИЗЬКИЙ"
336
- confidence_color = "red"
337
-
338
- main_result = f"""
339
- # 🏥 Результати MAI-DX Діагностики
340
-
341
- ## 📋 Основна інформація
342
- - **Випадок**: {session.case_name}
343
- - **Час**: {session.timestamp}
344
- - **Режим**: {session.mode}
345
-
346
- ## 🎯 Діагноз
347
- **{session.diagnosis}**
348
-
349
- ## 📊 Оцінка якості
350
- - **Рівень довіри**: {confidence_level}
351
- - **Бал точності**: {session.confidence}/5.0
352
- - **Статус**: {session.status}
353
-
354
- ## 💰 Ресурси
355
- - **Загальна вартість**: ${session.cost:,.2f}
356
- - **Бюджет**: ${session.budget:,}
357
- - **Ітерації**: {session.iterations}
358
- - **Тривалість**: {session.duration:.1f} сек
359
- """
360
-
361
- return main_result
362
-
363
- def _format_detailed_analysis(self, session: DiagnosisSession, result) -> str:
364
- """Детальний аналіз результатів"""
365
-
366
- analysis = f"""
367
- # 🔬 Детальний Аналіз
368
-
369
- ## 💭 Медичне обґрунтування
370
- {session.reasoning}
371
-
372
- ## 📈 Показники ефективності
373
- - **Швидкість діагностики**: {session.duration:.1f} секунд
374
- - **Економічна ефективність**: {((session.budget - session.cost) / session.budget * 100):.1f}% бюджету залишилось
375
- - **Ітеративна ефективність**: {session.iterations} циклів аналізу
376
-
377
- ## 🔄 Про��ес діагностики
378
- 1. **Початковий аналіз** пацієнтських даних
379
- 2. **Диференційна діагностика** з {session.iterations} ітераціями
380
- 3. **Консенсус панелі** 8 ШІ-лікарів
381
- 4. **Фінальна оцінка** та верифікація
382
-
383
- ## 📊 Порівняння з попередніми випадками
384
- """
385
-
386
- if len(self.sessions_history) > 1:
387
- avg_confidence = sum(s.confidence for s in self.sessions_history[:-1]) / len(self.sessions_history[:-1])
388
- avg_cost = sum(s.cost for s in self.sessions_history[:-1]) / len(self.sessions_history[:-1])
389
-
390
- analysis += f"""
391
- - **Середня точність** попередніх випадків: {avg_confidence:.1f}/5.0
392
- - **Середня вартість** попередніх випадків: ${avg_cost:,.2f}
393
- - **Поточний випадок**: {"Вище середнього" if session.confidence > avg_confidence else "Нижче середнього"} за точністю
394
- """
395
- else:
396
- analysis += "\n- Це ваш перший діагностичний випадок"
397
-
398
- return analysis
399
-
400
- def _generate_recommendations(self, result) -> str:
401
- """Генерація рекомендацій"""
402
-
403
- recommendations = """
404
- # 💡 Рекомендації
405
-
406
- ## 🏥 Клінічні рекомендації
407
- """
408
-
409
- diagnosis = result.final_diagnosis.lower()
410
-
411
- # Базові рекомендації залежно від діагнозу
412
- if any(word in diagnosis for word in ["emergency", "acute", "urgent", "infarction", "stroke"]):
413
- recommendations += """
414
- - 🚨 **НЕВІДКЛАДНА МЕДИЧНА ДОПОМОГА**
415
- - Негайна госпіталізація
416
- - Моніторинг життєво важливих функцій
417
- - Консультація спеціаліста протягом години
418
- """
419
- elif any(word in diagnosis for word in ["infection", "bacterial", "viral"]):
420
- recommendations += """
421
- - 💊 Розгляньте антибактеріальну терапію
422
- - 🌡️ Моніторинг температури тіла
423
- - 🔬 Додаткові лабораторні дослідження
424
- - 📅 Контрольний огляд через 48-72 години
425
- """
426
- elif any(word in diagnosis for word in ["chronic", "management", "control"]):
427
- recommendations += """
428
- - 📋 Розробка довготривалого плану лікування
429
- - 🗓️ Регулярні контрольні огляди
430
- - 👨‍⚕️ Консультація профільних спеціалістів
431
- - 📚 Навчання пацієнта самоконтролю
432
- """
433
- else:
434
- recommendations += """
435
- - 🔍 Подальше спостереження та моніторинг
436
- - 📋 Розгляньте додаткові діагностичні тести
437
- - 👨‍⚕️ Консультація з лікарем для верифікації
438
- - 📝 Документування симптомів та їх динаміки
439
- """
440
-
441
- recommendations += f"""
442
-
443
- ## 🔬 Дослідницькі рекомендації
444
- - **Валідація ШІ-діагнозу**: Порівняйте з експертною оцінкою
445
- - **Документування**: Збережіть результат для дослідження
446
- - **Аналіз точності**: Оцініть відповідність реальному діагнозу
447
- - **Поліпшення**: Використайте результат для налаштування параметрів
448
-
449
- ## ⚠️ Важливі застереження
450
- - Цей діагноз згенеровано ШІ для дослідницьких цілей
451
- - НЕ замінює професійну медичну консультацію
452
- - Завжди консультуйтеся з кваліфікованими медичними працівниками
453
- - Використовуйте лише як допоміжний інструмент для навчання
454
-
455
- ## 📈 Поліпшення точності
456
- - Додайте більше клінічних деталей до опису випадку
457
- - Використайте режим "no_budget" для складних випадків
458
- - Розгляньте ensemble діагностику для критичних випадків
459
- """
460
-
461
- return recommendations
462
-
463
- def _get_history_dataframe(self) -> pd.DataFrame:
464
- """Отримання історії у вигляді DataFrame"""
465
-
466
- if not self.sessions_history:
467
- return pd.DataFrame()
468
-
469
- data = []
470
- for session in self.sessions_history:
471
- data.append({
472
- "Час": session.timestamp,
473
- "Випадок": session.case_name,
474
- "Режим": session.mode,
475
- "Діагноз": session.diagnosis[:50] + "..." if len(session.diagnosis) > 50 else session.diagnosis,
476
- "Точність": f"{session.confidence:.1f}/5.0",
477
- "Вартість": f"${session.cost:.2f}",
478
- "Ітерації": session.iterations,
479
- "Тривалість": f"{session.duration:.1f}с",
480
- "Статус": session.status
481
- })
482
-
483
- return pd.DataFrame(data)
484
-
485
- def export_results(self) -> str:
486
- """Експорт результатів дослідження"""
487
-
488
- if not self.sessions_history:
489
- return "Немає даних для експорту"
490
-
491
- # Підготовка даних для експорту
492
- export_data = {
493
- "metadata": {
494
- "export_date": datetime.now().isoformat(),
495
- "total_sessions": len(self.sessions_history),
496
- "software_version": "MAI-DX Gradio Interface v1.0"
497
- },
498
- "sessions": [asdict(session) for session in self.sessions_history],
499
- "statistics": self._calculate_statistics()
500
- }
501
-
502
- # Збереження у файл
503
- filename = f"mai_dx_research_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
504
-
505
- try:
506
- with open(filename, 'w', encoding='utf-8') as f:
507
- json.dump(export_data, f, indent=2, ensure_ascii=False)
508
-
509
- return f"✅ Дані експортовано у файл: {filename}"
510
- except Exception as e:
511
- return f"❌ Помилка експорту: {e}"
512
-
513
- def _calculate_statistics(self) -> Dict:
514
- """Розрахунок статистики досліджень"""
515
-
516
- if not self.sessions_history:
517
- return {}
518
-
519
- sessions = self.sessions_history
520
-
521
- stats = {
522
- "total_cases": len(sessions),
523
- "average_confidence": sum(s.confidence for s in sessions) / len(sessions),
524
- "average_cost": sum(s.cost for s in sessions) / len(sessions),
525
- "average_duration": sum(s.duration for s in sessions) / len(sessions),
526
- "mode_distribution": {},
527
- "confidence_distribution": {
528
- "high": len([s for s in sessions if s.confidence >= 4.0]),
529
- "medium": len([s for s in sessions if 2.0 <= s.confidence < 4.0]),
530
- "low": len([s for s in sessions if s.confidence < 2.0])
531
- },
532
- "success_rate": len([s for s in sessions if s.confidence >= 2.0]) / len(sessions)
533
- }
534
-
535
- # Розподіл по режимах
536
- for session in sessions:
537
- mode = session.mode
538
- stats["mode_distribution"][mode] = stats["mode_distribution"].get(mode, 0) + 1
539
-
540
- return stats
541
-
542
- def create_gradio_interface():
543
- """Створення Gradio інтерфейсу"""
544
-
545
- interface = MAIDXGradioInterface()
546
-
547
- with gr.Blocks(
548
- title="MAI-DX Research Interface",
549
- theme=gr.themes.Soft(),
550
- css="""
551
- .main-header { text-align: center; color: #2c3e50; }
552
- .status-good { color: green; font-weight: bold; }
553
- .status-bad { color: red; font-weight: bold; }
554
- .case-input { font-family: monospace; }
555
- """
556
- ) as demo:
557
-
558
- # Заголовок
559
- gr.Markdown("""
560
- # 🏥 MAI-DX Research Interface
561
- ## Дослідницький інтерфейс для медичної діагностики з ШІ
562
-
563
- Цей інтерфейс дозволяє проводити дослідження діагностичних можливостей MAI-DX системи з 8 ШІ-агентами лікарів.
564
- """, elem_classes=["main-header"])
565
-
566
- # Статус системи
567
- with gr.Row():
568
- status_display = gr.Markdown()
569
-
570
- # Оновлення статусу при завантаженні
571
- def update_status():
572
- status, color = interface.check_api_keys()
573
- return f"**Статус системи**: {status}"
574
-
575
- demo.load(update_status, outputs=[status_display])
576
-
577
- # Основний інтерфейс
578
- with gr.Tabs():
579
-
580
- # Вкладка діагностики
581
- with gr.Tab("🩺 Діагностика"):
582
- with gr.Row():
583
- with gr.Column(scale=1):
584
- gr.Markdown("### 📝 Введення медичного випадку")
585
-
586
- case_name = gr.Textbox(
587
- label="Назва випадку",
588
- placeholder="Наприклад: Кардіологічний випадок №1",
589
- value=""
590
- )
591
-
592
- # Dropdown з прикладами
593
- sample_selector = gr.Dropdown(
594
- choices=list(interface.sample_cases.keys()),
595
- label="Приклади випадків",
596
- value=None
597
- )
598
-
599
- patient_info = gr.Textbox(
600
- label="Інформація про пацієнта",
601
- lines=15,
602
- placeholder="Введіть детальний опис медичного випадку...",
603
- elem_classes=["case-input"]
604
- )
605
-
606
- # Завантаження прикладу
607
- def load_sample(sample_name):
608
- if sample_name:
609
- return interface.sample_cases[sample_name], sample_name
610
- return "", ""
611
-
612
- sample_selector.change(
613
- load_sample,
614
- inputs=[sample_selector],
615
- outputs=[patient_info, case_name]
616
- )
617
-
618
- expected_diagnosis = gr.Textbox(
619
- label="Очікуваний діагноз (опціонально)",
620
- placeholder="Для оцінки точності...",
621
- value=""
622
- )
623
-
624
- with gr.Column(scale=1):
625
- gr.Markdown("### ⚙️ Налаштування діагностики")
626
-
627
- mode = gr.Radio(
628
- choices=[
629
- ("instant", "Миттєвий (найшвидший)"),
630
- ("question_only", "Тільки питання (швидко)"),
631
- ("budgeted", "З бюджетом (збалансовано)"),
632
- ("no_budget", "Без обмежень (повний аналіз)"),
633
- ("ensemble", "Консенсус (найточніший)")
634
- ],
635
- label="Режим діагностики",
636
- value="budgeted"
637
- )
638
-
639
- budget = gr.Slider(
640
- minimum=500,
641
- maximum=10000,
642
- step=500,
643
- value=3000,
644
- label="Бюджет ($)"
645
- )
646
-
647
- max_iterations = gr.Slider(
648
- minimum=1,
649
- maximum=10,
650
- step=1,
651
- value=5,
652
- label="Максимум ітерацій"
653
- )
654
-
655
- model_name = gr.Dropdown(
656
- choices=[
657
- "gemini/gemini-2.5-flash",
658
- "gpt-4",
659
- "gpt-3.5-turbo",
660
- "claude-3-5-sonnet",
661
- "grok-beta",
662
- "deepseek-chat",
663
- "llama-3.1-405b"
664
- ],
665
- label="LLM Модель",
666
- value="gemini/gemini-2.5-flash"
667
- )
668
-
669
- diagnose_btn = gr.Button(
670
- "🚀 Запустити діагностику",
671
- variant="primary",
672
- size="lg"
673
- )
674
-
675
- # Результати
676
- gr.Markdown("### 📊 Результати діагностики")
677
-
678
- with gr.Row():
679
- with gr.Column():
680
- main_result = gr.Markdown(label="Основний результат")
681
-
682
- with gr.Row():
683
- with gr.Column():
684
- detailed_analysis = gr.Markdown(label="Детальний аналіз")
685
-
686
- with gr.Column():
687
- recommendations = gr.Markdown(label="Рекомендації")
688
-
689
- # Запуск діагностики
690
- diagnose_btn.click(
691
- interface.diagnose_case,
692
- inputs=[
693
- case_name, patient_info, mode, budget,
694
- max_iterations, model_name, expected_diagnosis
695
- ],
696
- outputs=[main_result, detailed_analysis, recommendations, gr.State()]
697
- )
698
-
699
- # Вкладка історії
700
- with gr.Tab("📈 Історія та Статистика"):
701
- gr.Markdown("### 📋 Історія діагностичних сесій")
702
-
703
- with gr.Row():
704
- refresh_btn = gr.Button("🔄 Оновити", variant="secondary")
705
- export_btn = gr.Button("💾 Експортувати результати", variant="primary")
706
-
707
- history_table = gr.Dataframe(
708
- label="Історія діагнозів",
709
- interactive=False
710
- )
711
-
712
- export_status = gr.Markdown()
713
-
714
- # Функції для оновлення
715
- def refresh_history():
716
- return interface._get_history_dataframe()
717
-
718
- def export_data():
719
- return interface.export_results()
720
-
721
- refresh_btn.click(refresh_history, outputs=[history_table])
722
- export_btn.click(export_data, outputs=[export_status])
723
-
724
- # Статистика
725
- with gr.Row():
726
- with gr.Column():
727
- gr.Markdown("### 📊 Статистика досліджень")
728
- stats_display = gr.Markdown()
729
-
730
- def update_stats():
731
- if not interface.sessions_history:
732
- return "Поки що немає даних для статистики"
733
-
734
- stats = interface._calculate_statistics()
735
-
736
- return f"""
737
- **Загальна статистика:**
738
- - Всього випадків: {stats['total_cases']}
739
- - Середня точність: {stats['average_confidence']:.2f}/5.0
740
- - Середня вартість: ${stats['average_cost']:.2f}
741
- - Середня тривалість: {stats['average_duration']:.1f}с
742
- - Рівень успішності: {stats['success_rate']:.1%}
743
-
744
- **Розподіл по режимах:**
745
- {chr(10).join([f"- {mode}: {count}" for mode, count in stats['mode_distribution'].items()])}
746
-
747
- **Розподіл по точності:**
748
- - Висока (≥4.0): {stats['confidence_distribution']['high']}
749
- - Середня (2.0-3.9): {stats['confidence_distribution']['medium']}
750
- - Низька (<2.0): {stats['confidence_distribution']['low']}
751
- """
752
-
753
- refresh_stats_btn = gr.Button("🔄 Оновити статистику")
754
- refresh_stats_btn.click(update_stats, outputs=[stats_display])
755
-
756
- # Вкладка налаштувань
757
- with gr.Tab("⚙️ Налаштування"):
758
- gr.Markdown("### 🔧 Конфігурація системи")
759
-
760
- with gr.Row():
761
- with gr.Column():
762
- gr.Markdown("""
763
- #### 📋 Інструкції з установки
764
-
765
- **Для pip (ваша конфігурація):**
766
- ```bash
767
- # 1. Встановлення залежностей
768
- pip install mai-dx python-dotenv gradio pandas
769
-
770
- # 2. Створення .env файлу
771
- echo "OPENAI_API_KEY=your-key-here" > .env
772
- echo "GEMINI_API_KEY=your-key-here" >> .env
773
- echo "ANTHROPIC_API_KEY=your-key-here" >> .env
774
-
775
- # 3. Запуск інтерфейсу
776
- python mai_dx_gradio_interface.py
777
- ```
778
-
779
- #### 🔑 Необхідні API ключі:
780
- - **OpenAI**: для GPT моделей
781
- - **Google AI**: для Gemini моделей
782
- - **Anthropic**: для Claude моделей
783
-
784
- Отримайте ключі на відповідних платформах та додайте їх у .env файл.
785
- """)
786
-
787
- with gr.Column():
788
- gr.Markdown("""
789
- #### 🛠️ Troubleshooting
790
-
791
- **Часті проблеми:**
792
-
793
- 1. **MAI-DX не імпортується**
794
- - Встановіть: `pip install mai-dx`
795
- - Перевірте віртуальне середовище
796
-
797
- 2. **API ключі не працюють**
798
- - Перевірте .env файл
799
- - Переконайтеся що файл у тій же папці
800
-
801
- 3. **Rich console помилки**
802
- - Це нормально, можна ігнорувати
803
- - Система працює попри помилки
804
-
805
- 4. **Повільна робота**
806
- - Використовуйте ��видші моделі
807
- - Зменшіть кількість ітерацій
808
- - Оберіть режим "question_only"
809
-
810
- #### 📞 Підтримка:
811
- - GitHub: [MAI-DX Repository](https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator)
812
- - Документація: включена у репозиторій
813
- """)
814
-
815
- # Тестування підключення
816
- with gr.Row():
817
- test_connection_btn = gr.Button("🔍 Тест підключення", variant="secondary")
818
- connection_status = gr.Markdown()
819
-
820
- def test_system():
821
- if not MAI_DX_AVAILABLE:
822
- return f"❌ MAI-DX недоступний: {IMPORT_ERROR}"
823
-
824
- api_status, _ = interface.check_api_keys()
825
-
826
- return f"""
827
- **Результати тестування:**
828
-
829
- 📦 **MAI-DX статус**: ✅ Доступний
830
- 🔑 **API ключі**: {api_status}
831
- 🐍 **Python**: {sys.version.split()[0]}
832
- 📍 **Робоча директорія**: {os.getcwd()}
833
-
834
- **Готовність до роботи**: {"✅ Готово" if MAI_DX_AVAILABLE else "❌ Потребує налаштування"}
835
- """
836
-
837
- test_connection_btn.click(test_system, outputs=[connection_status])
838
-
839
- return demo
840
-
841
- if __name__ == "__main__":
842
- # Створення та запуск інтерфейсу
843
- demo = create_gradio_interface()
844
-
845
- print("🚀 Запуск MAI-DX Research Interface...")
846
- print("📱 Інтерфейс буде доступний у браузері")
847
- print("🔧 Переконайтеся що .env файл з API ключами у поточній директорії")
848
-
849
- demo.launch(
850
- server_name="0.0.0.0", # Доступ з мережі
851
- server_port=7860, # Порт
852
- share=False, # Встановіть True для публічного доступу
853
- debug=True, # Режим налагодження
854
- show_error=True # Показувати помилки
855
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mai_dx_logs/case_20250710_173828_5d4912.json DELETED
@@ -1,27 +0,0 @@
1
- {
2
- "case_id": "case_20250710_173828_5d4912",
3
- "timestamp": "2025-07-10T17:38:28.722692",
4
- "case_name": "🫀 Кардіологічний (Гострий MI)",
5
- "patient_info": "Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі\nСкарги: Гострий роздираючий біль у грудях 3 години, іррадіація в ліву руку\nОгляд: Блідий, пітливий, АТ 160/90, ЧСС 95\nЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)\nТропонін I: 8.5 нг/мл (норма <0.04)\nАнамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС",
6
- "mode": "⚡ Миттєвий (найшвидший, базовий аналіз)",
7
- "budget": 0,
8
- "diagnosis": "ST-elevation Myocardial Infarction (STEMI)",
9
- "confidence": 1.0,
10
- "cost": 300,
11
- "iterations": 1,
12
- "duration": 97.490005,
13
- "status": "⚠️ Потребує перегляду",
14
- "reasoning": "N/A",
15
- "messages": [
16
- {
17
- "timestamp": "2025-07-10T17:40:06.212655",
18
- "agent": "Judge",
19
- "action": "final_evaluation",
20
- "content": "ST-elevation Myocardial Infarction (STEMI)",
21
- "reasoning": "[detailed reasoning for the score]",
22
- "confidence": 0.2,
23
- "cost": 300,
24
- "iteration": 8
25
- }
26
- ]
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mai_dx_logs/case_20250710_174912_2b0a2d.json DELETED
@@ -1,27 +0,0 @@
1
- {
2
- "case_id": "case_20250710_174912_2b0a2d",
3
- "timestamp": "2025-07-10T17:49:12.547474",
4
- "case_name": "🫀 Кардіологічний (Гострий MI)",
5
- "patient_info": "Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі\nСкарги: Гострий роздираючий біль у грудях 3 години, іррадіація в ліву руку\nОгляд: Блідий, пітливий, АТ 160/90, ЧСС 95\nЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)\nТропонін I: 8.5 нг/мл (норма <0.04)\nАнамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС",
6
- "mode": "budgeted",
7
- "budget": 0,
8
- "diagnosis": "ST-Elevation Myocardial Infarction (STEMI)",
9
- "confidence": 5.0,
10
- "cost": 300,
11
- "iterations": 1,
12
- "duration": 95.609545,
13
- "status": "✅ Успішно",
14
- "reasoning": "N/A",
15
- "messages": [
16
- {
17
- "timestamp": "2025-07-10T17:50:48.156978",
18
- "agent": "Judge",
19
- "action": "final_evaluation",
20
- "content": "ST-Elevation Myocardial Infarction (STEMI)",
21
- "reasoning": "[detailed reasoning for the score]",
22
- "confidence": 1.0,
23
- "cost": 300,
24
- "iteration": 8
25
- }
26
- ]
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mai_dx_wrapper.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Обгортка для MAI-DX що виправляє проблеми з Rich форматуванням
4
+ """
5
+ import os
6
+ import sys
7
+ import warnings
8
+
9
+ # Налаштування середовища
10
+ os.environ.update({
11
+ "SWARMS_VERBOSITY": "ERROR",
12
+ "RICH_TRACEBACK": "0",
13
+ "SWARMS_SHOW_PANEL": "true",
14
+ "SWARMS_AUTO_PRINT": "true",
15
+ "PYTHONWARNINGS": "ignore"
16
+ })
17
+
18
+ def patch_rich_and_swarms():
19
+ """Патчимо Rich та Swarms для уникнення помилок виводу"""
20
+ try:
21
+ # Патчимо Rich Console
22
+ import rich.console
23
+
24
+ original_print = rich.console.Console.print
25
+
26
+ def safe_print(self, *args, **kwargs):
27
+ """Безпечний print що ігнорує None значення"""
28
+ try:
29
+ # Фільтруємо None значення
30
+ filtered_args = [arg for arg in args if arg is not None]
31
+ if filtered_args:
32
+ return original_print(self, *filtered_args, **kwargs)
33
+ except Exception:
34
+ # Ігноруємо всі помилки виводу
35
+ pass
36
+
37
+ rich.console.Console.print = safe_print
38
+
39
+ # Патчимо Swarms formatter
40
+ try:
41
+ import swarms.utils.formatter as formatter
42
+
43
+ # # Заміняємо _print_panel на заглушку
44
+ # def dummy_print_panel(*args, **kwargs):
45
+ # pass
46
+
47
+ # if hasattr(formatter, 'Formatter'):
48
+ # formatter.Formatter._print_panel = dummy_print_panel
49
+
50
+ # Патчимо print_panel_token_usage
51
+ def safe_print_panel_token_usage(self, *args, **kwargs):
52
+ try:
53
+ # Просто виводимо базову інформацію без форматування
54
+ if args and hasattr(args[0], '__dict__'):
55
+ data = args[0].__dict__ if hasattr(args[0], '__dict__') else str(args[0])
56
+ print(f"[Token Usage] {data}")
57
+ except:
58
+ pass
59
+
60
+ if hasattr(formatter, 'print_panel_token_usage'):
61
+ formatter.print_panel_token_usage = safe_print_panel_token_usage
62
+
63
+ except ImportError:
64
+ pass
65
+
66
+ # Патчимо threading для уникнення помилок в потоках
67
+ import threading
68
+ original_thread_init = threading.Thread.__init__
69
+
70
+ def safe_thread_init(self, *args, **kwargs):
71
+ """Обгортаємо target функцію в try/except"""
72
+ original_target = kwargs.get('target')
73
+
74
+ if original_target:
75
+ def safe_target(*args, **kwargs):
76
+ try:
77
+ return original_target(*args, **kwargs)
78
+ except Exception:
79
+ # Ігноруємо помилки в потоках
80
+ pass
81
+
82
+ kwargs['target'] = safe_target
83
+
84
+ return original_thread_init(self, *args, **kwargs)
85
+
86
+ threading.Thread.__init__ = safe_thread_init
87
+
88
+ except Exception as e:
89
+ print(f"Warning: Could not patch Rich/Swarms: {e}")
90
+
91
+ # Застосовуємо патчі перед імпортом MAI-DX
92
+ patch_rich_and_swarms()
93
+
94
+ # Тепер можна безпечно імпортувати MAI-DX
95
+ try:
96
+ from mai_dx import MaiDxOrchestrator
97
+ MAI_DX_AVAILABLE = True
98
+ except ImportError as e:
99
+ MAI_DX_AVAILABLE = False
100
+ print(f"MAI-DX not available: {e}")
101
+
102
+ class SafeMaiDxOrchestrator:
103
+ """Безпечна обгортка для MaiDxOrchestrator"""
104
+
105
+ def __init__(self, *args, **kwargs):
106
+ if not MAI_DX_AVAILABLE:
107
+ raise ImportError("MAI-DX is not available")
108
+
109
+ # Додаткові патчі безпосередньо перед створенням
110
+ patch_rich_and_swarms()
111
+
112
+ # Створюємо оригінальний orchestrator
113
+ self.orchestrator = MaiDxOrchestrator(*args, **kwargs)
114
+
115
+ def run(self, *args, **kwargs):
116
+ """Виконати діагностику з додатковим захистом"""
117
+ # Переконуємося що патчі активні
118
+ patch_rich_and_swarms()
119
+
120
+ # Тимчасово змінюємо stderr щоб приховати помилки Rich
121
+ import io
122
+ original_stderr = sys.stderr
123
+ sys.stderr = io.StringIO()
124
+
125
+ try:
126
+ # Виконуємо оригінальний run
127
+ result = self.orchestrator.run(*args, **kwargs)
128
+ return result
129
+ finally:
130
+ # Відновлю��мо stderr
131
+ sys.stderr = original_stderr
132
+
133
+ def __getattr__(self, name):
134
+ """Проксі для всіх інших методів"""
135
+ return getattr(self.orchestrator, name)
136
+
137
+ # Експортуємо класи
138
+ __all__ = ['SafeMaiDxOrchestrator', 'MAI_DX_AVAILABLE', 'patch_rich_and_swarms']
production_mai_dx.py DELETED
@@ -1,254 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Production-ready MAI-DX Orchestrator
4
- Оптимізовано для реального використання
5
- """
6
- import os
7
- import sys
8
- import warnings
9
- from dotenv import load_dotenv
10
-
11
- # Завантажуємо змінні середовища
12
- load_dotenv()
13
-
14
- # Виробничі налаштування для стабільності
15
- os.environ["SWARMS_VERBOSITY"] = "SILENT"
16
- os.environ["RICH_TRACEBACK"] = "0"
17
- os.environ["SWARMS_SHOW_PANEL"] = "false"
18
- os.environ["SWARMS_AUTO_PRINT"] = "false"
19
- os.environ["LOGURU_LEVEL"] = "WARNING" # Менше спаму в логах
20
- warnings.filterwarnings("ignore")
21
-
22
- # Rich formatter патч (довелося ефективність!)
23
- def patch_rich_formatter():
24
- try:
25
- import swarms.utils.formatter
26
- def dummy_print_panel(*args, **kwargs):
27
- pass
28
- if hasattr(swarms.utils.formatter, 'Formatter'):
29
- swarms.utils.formatter.Formatter._print_panel = dummy_print_panel
30
- return True
31
- except:
32
- return False
33
-
34
- patch_rich_formatter()
35
-
36
- from mai_dx import MaiDxOrchestrator
37
- from loguru import logger
38
-
39
- class ProductionMAIDX:
40
- """Production-wrapper для MAI-DX з поліпшеннями"""
41
-
42
- def __init__(self, model="gemini/gemini-2.5-flash"):
43
- self.model = model
44
- self.available_modes = ["question_only", "budgeted", "no_budget"]
45
-
46
- def create_optimized_orchestrator(self, mode="question_only", budget=2000, max_iterations=5):
47
- """Створює оптимізований оркестратор для production"""
48
-
49
- # Рекомендовані налаштування для кожного режиму
50
- configs = {
51
- "question_only": {"budget": 1000, "iterations": 3},
52
- "budgeted": {"budget": 3000, "iterations": 5},
53
- "no_budget": {"budget": 10000, "iterations": 7}
54
- }
55
-
56
- config = configs.get(mode, {"budget": budget, "iterations": max_iterations})
57
-
58
- try:
59
- orchestrator = MaiDxOrchestrator(
60
- model_name=self.model,
61
- max_iterations=config["iterations"],
62
- initial_budget=config["budget"],
63
- mode=mode
64
- )
65
-
66
- print(f"✅ MAI-DX готовий: {mode} режим, бюджет ${config['budget']}")
67
- return orchestrator
68
-
69
- except Exception as e:
70
- print(f"❌ Помилка створення: {e}")
71
- return None
72
-
73
- def diagnose_case(self, case_description, expected_diagnosis=None, mode="question_only"):
74
- """Основний метод для діагностики"""
75
-
76
- print(f"\n🩺 Запуск MAI-DX діагностики...")
77
- print(f"📋 Режим: {mode}")
78
-
79
- # Створюємо оркестратор
80
- orchestrator = self.create_optimized_orchestrator(mode)
81
- if not orchestrator:
82
- return None
83
-
84
- try:
85
- # Якщо немає expected_diagnosis, використовуємо загальний
86
- ground_truth = expected_diagnosis or "Unknown diagnosis"
87
-
88
- print("🔄 Аналіз випадку... (це може зайняти 2-5 хвилин)")
89
-
90
- # Запускаємо діагностику
91
- result = orchestrator.run(
92
- initial_case_info=case_description,
93
- full_case_details=case_description,
94
- ground_truth_diagnosis=ground_truth
95
- )
96
-
97
- return self._format_result(result, mode)
98
-
99
- except Exception as e:
100
- print(f"❌ Помилка діагностики: {e}")
101
- return None
102
-
103
- def _format_result(self, result, mode):
104
- """Форматуємо результат для зручного відображення"""
105
-
106
- # Оцінюємо якість результату
107
- if result.accuracy_score >= 4.0:
108
- quality = "🎉 ВІДМІННО"
109
- elif result.accuracy_score >= 3.0:
110
- quality = "👍 ДОБРЕ"
111
- elif result.accuracy_score >= 2.0:
112
- quality = "⚠️ ЗАДОВІЛЬНО"
113
- elif "not reached" in result.final_diagnosis.lower():
114
- quality = "🔄 ПОТРІБНО БІЛЬШЕ ЧАСУ"
115
- else:
116
- quality = "🔍 ПОТРЕБУЄ ПЕРЕВІРКИ"
117
-
118
- formatted = {
119
- "diagnosis": result.final_diagnosis,
120
- "confidence": result.accuracy_score,
121
- "quality": quality,
122
- "cost": result.total_cost,
123
- "iterations": result.iterations,
124
- "mode": mode,
125
- "reasoning": getattr(result, 'accuracy_reasoning', 'Недоступно')[:200] + "..."
126
- }
127
-
128
- return formatted
129
-
130
- def batch_diagnose(self, cases, mode="question_only"):
131
- """Діагностика кількох випадків"""
132
- results = []
133
-
134
- for i, case in enumerate(cases, 1):
135
- print(f"\n📋 Випадок {i}/{len(cases)}")
136
-
137
- case_text = case.get("description", "")
138
- expected = case.get("expected", None)
139
-
140
- result = self.diagnose_case(case_text, expected, mode)
141
- if result:
142
- results.append({"case_id": i, **result})
143
-
144
- return results
145
-
146
- def demo_production_usage():
147
- """Демонстрація production використання"""
148
-
149
- print("=" * 70)
150
- print("🏥 MAI-DX PRODUCTION DEMO")
151
- print("=" * 70)
152
-
153
- # Створюємо production instance
154
- mai_dx = ProductionMAIDX()
155
-
156
- # Тестові випадки
157
- test_cases = [
158
- {
159
- "description": "35-year-old man with sudden severe chest pain radiating to left arm, sweating, nausea. Pain started 2 hours ago during exercise.",
160
- "expected": "Myocardial infarction"
161
- },
162
- {
163
- "description": "22-year-old woman with severe headache, neck stiffness, fever 39°C, photophobia. Symptoms started this morning.",
164
- "expected": "Meningitis"
165
- },
166
- {
167
- "description": "45-year-old smoker with persistent cough for 3 months, weight loss 10kg, bloody sputum occasionally.",
168
- "expected": "Lung cancer"
169
- }
170
- ]
171
-
172
- print(f"📋 Тестування {len(test_cases)} випадків...")
173
-
174
- # Запускаємо діагностику
175
- results = mai_dx.batch_diagnose(test_cases, mode="question_only")
176
-
177
- # Виводимо результати
178
- print(f"\n{'='*70}")
179
- print("📊 РЕЗУЛЬТАТИ ТЕСТУВАННЯ")
180
- print("="*70)
181
-
182
- for result in results:
183
- print(f"\n🔍 Випадок {result['case_id']}:")
184
- print(f" Діагноз: {result['diagnosis']}")
185
- print(f" Якість: {result['quality']}")
186
- print(f" Оцінка: {result['confidence']}/5.0")
187
- print(f" Вартість: ${result['cost']}")
188
- print(f" Ітерації: {result['iterations']}")
189
-
190
- # Статистика
191
- successful = sum(1 for r in results if r['confidence'] >= 2.0)
192
- print(f"\n📈 Статистика: {successful}/{len(results)} успішних діагнозів")
193
-
194
- if successful >= len(results) * 0.7:
195
- print("🎉 MAI-DX готовий для production!")
196
- else:
197
- print("⚠️ Потрібне додаткове налаштування")
198
-
199
- def simple_diagnostic_session():
200
- """Простий інтерактивний сеанс діагностики"""
201
-
202
- print("\n🩺 Простий сеанс діагностики")
203
- print("-" * 40)
204
-
205
- # Простий випадок для демонстрації
206
- case = """
207
- 29-year-old woman with sore throat and peritonsillar swelling and bleeding.
208
- Symptoms did not abate with antimicrobial therapy.
209
-
210
- History: Onset 7 weeks ago, worsening right-sided pain.
211
- Physical Exam: Right peritonsillar mass, displaces uvula.
212
- Biopsy: Round-cell neoplasm, positive for desmin and MyoD1.
213
- """
214
-
215
- expected = "Embryonal rhabdomyosarcoma of the pharynx"
216
-
217
- mai_dx = ProductionMAIDX()
218
- result = mai_dx.diagnose_case(case, expected, "question_only")
219
-
220
- if result:
221
- print(f"\n🎯 Діагноз: {result['diagnosis']}")
222
- print(f"⭐ Якість: {result['quality']}")
223
- print(f"💰 Вартість: ${result['cost']}")
224
-
225
- # Аналіз близькості діагнозу
226
- if "rhabdomyosarcoma" in result['diagnosis'].lower():
227
- print("✅ Правильний тип пухлини ідентифіковано!")
228
-
229
- return True
230
- else:
231
- print("❌ Діагностика не вдалася")
232
- return False
233
-
234
- if __name__ == "__main__":
235
- try:
236
- # Запускаємо простий тест
237
- simple_diagnostic_session()
238
-
239
- # Опціонально - повний demo
240
- print("\n" + "="*50)
241
- print("Запустити повний production demo? (y/n): ", end="")
242
-
243
- # Для автоматичного запуску в тестах - пропускаємо input
244
- try:
245
- choice = input().lower()
246
- if choice == 'y':
247
- demo_production_usage()
248
- except:
249
- print("Пропускаємо інтерактивний demo")
250
-
251
- except KeyboardInterrupt:
252
- print("\n⏹️ Зупинено користувачем")
253
- except Exception as e:
254
- print(f"\n💥 Помилка: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
run_mai_dx_fixed.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Головний файл запуску MAI-DX з виправленнями та повним логуванням
4
+ Використовуйте цей файл замість app.py
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import warnings
10
+
11
+ # Встановлюємо змінні середовища ДО всіх імпортів
12
+ os.environ.update({
13
+ "SWARMS_VERBOSITY": "ERROR",
14
+ "RICH_TRACEBACK": "0",
15
+ "SWARMS_SHOW_PANEL": "true",
16
+ "SWARMS_AUTO_PRINT": "true",
17
+ "PYTHONWARNINGS": "ignore",
18
+ "GRADIO_ANALYTICS_ENABLED": "false"
19
+ })
20
+
21
+ # Ігноруємо всі попередження
22
+ warnings.filterwarnings("ignore")
23
+
24
+ # Перевіряємо що всі необхідні файли присутні
25
+ required_files = [
26
+ "enhanced_mai_dx_logger.py",
27
+ "mai_dx_wrapper.py",
28
+ "updated_mai_dx_interface.py"
29
+ ]
30
+
31
+ for file in required_files:
32
+ if not os.path.exists(file):
33
+ print(f"❌ Помилка: Файл {file} не знайдено!")
34
+ print("Переконайтеся що всі файли з артефактів збережені в робочій директорії.")
35
+ sys.exit(1)
36
+
37
+ # Імпортуємо інтерфейс
38
+ try:
39
+ from updated_mai_dx_interface import create_updated_gradio_interface
40
+ except ImportError as e:
41
+ print(f"❌ Помилка імпорту інтерфейсу: {e}")
42
+ print("Перевірте що всі залежності встановлені:")
43
+ print("pip install gradio pandas plotly python-dotenv")
44
+ sys.exit(1)
45
+
46
+ def main():
47
+ """Головна функція запуску"""
48
+ print("🚀 Запуск MAI-DX Enhanced Platform with Full Agent Conversation Logging...")
49
+ print("📁 Логи будуть збережені в директорії: mai_dx_logs/")
50
+ print("=" * 60)
51
+
52
+ # Створюємо директорію для логів якщо її немає
53
+ os.makedirs("mai_dx_logs", exist_ok=True)
54
+
55
+ try:
56
+ # Створюємо та запускаємо інтерфейс
57
+ demo = create_updated_gradio_interface()
58
+
59
+ # Запускаємо з налаштуваннями
60
+ demo.launch(
61
+ server_name="0.0.0.0",
62
+ server_port=7860,
63
+ share=False,
64
+ debug=False,
65
+ show_error=True,
66
+ quiet=False
67
+ )
68
+
69
+ except KeyboardInterrupt:
70
+ print("\n⏹️ Зупинка сервера...")
71
+ except Exception as e:
72
+ print(f"\n❌ Критична помилка: {e}")
73
+ import traceback
74
+ traceback.print_exc()
75
+
76
+ if __name__ == "__main__":
77
+ main()
example.py → test/example.py RENAMED
File without changes
example_corrected.py → test/example_corrected.py RENAMED
File without changes
simple_test.py → test/simple_test.py RENAMED
File without changes
updated_mai_dx_interface.py ADDED
@@ -0,0 +1,730 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Оновлений MAI-DX Gradio Interface з повним логуванням розмов агентів
4
+ """
5
+ import os
6
+ import sys
7
+ import json
8
+ import time
9
+ import pandas as pd
10
+ import gradio as gr
11
+ from datetime import datetime
12
+ from typing import Dict, List, Tuple, Optional
13
+ from dataclasses import dataclass, asdict
14
+ import warnings
15
+
16
+ # Налаштування середовища
17
+ os.environ.update({
18
+ "SWARMS_VERBOSITY": "ERROR",
19
+ "RICH_TRACEBACK": "0",
20
+ "SWARMS_SHOW_PANEL": "true",
21
+ "SWARMS_AUTO_PRINT": "true"
22
+ })
23
+ warnings.filterwarnings("ignore")
24
+
25
+ from dotenv import load_dotenv
26
+ load_dotenv()
27
+
28
+ # Імпорт MAI-DX через безпечну обгортку
29
+ try:
30
+ from mai_dx_wrapper import SafeMaiDxOrchestrator as MaiDxOrchestrator, MAI_DX_AVAILABLE
31
+ except ImportError:
32
+ try:
33
+ from mai_dx import MaiDxOrchestrator
34
+ MAI_DX_AVAILABLE = True
35
+ except ImportError as e:
36
+ MAI_DX_AVAILABLE = False
37
+ IMPORT_ERROR = str(e)
38
+
39
+ # Імпорт покращеного логгера
40
+ from enhanced_mai_dx_logger import MAIDxConversationLogger, DiagnosisSession
41
+
42
+ # Перевірка доступності Plotly
43
+ try:
44
+ import plotly.graph_objects as go
45
+ import plotly.express as px
46
+ PLOTLY_AVAILABLE = True
47
+ except ImportError:
48
+ PLOTLY_AVAILABLE = False
49
+ print("⚠️ Plotly не встановлено, візуалізації будуть недоступні")
50
+
51
+ class RealTimeMetrics:
52
+ """Метрики в реальному часі"""
53
+ def __init__(self):
54
+ self.reset()
55
+
56
+ def reset(self):
57
+ self.start_time = time.time()
58
+ self.agents_activity = {
59
+ 'Dr. Hypothesis': 0, 'Dr. Test-Chooser': 0, 'Dr. Challenger': 0,
60
+ 'Dr. Stewardship': 0, 'Dr. Checklist': 0, 'Consensus Coordinator': 0,
61
+ 'Gatekeeper': 0, 'Judge': 0
62
+ }
63
+ self.cost_progression = []
64
+ self.conversation_rounds = []
65
+
66
+ def update_agent_activity(self, agent_name: str):
67
+ if agent_name in self.agents_activity:
68
+ self.agents_activity[agent_name] += 1
69
+
70
+ def add_cost_point(self, cost: float):
71
+ self.cost_progression.append({'time': time.time() - self.start_time, 'cost': cost})
72
+
73
+ def add_conversation_round(self, round_data: dict):
74
+ self.conversation_rounds.append(round_data)
75
+
76
+ class UpdatedMAIDXInterface:
77
+ """Оновлений інтерфейс з повним логуванням розмов"""
78
+
79
+ def __init__(self):
80
+ self.sessions_history = []
81
+ self.conversation_logger = MAIDxConversationLogger("mai_dx_logs")
82
+ self.current_metrics = RealTimeMetrics()
83
+
84
+ self.sample_cases = {
85
+ "🫀 Кардіологічний (Гострий MI)": {
86
+ "info": "Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі\nСкарги: Гострий роздираючий біль у грудях 3 години, іррадіація в ліву руку\nОгляд: Блідий, пітливий, АТ 160/90, ЧСС 95\nЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)\nТропонін I: 8.5 нг/мл (норма <0.04)\nАнамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС",
87
+ "expected": "Acute inferior wall myocardial infarction (STEMI)"
88
+ },
89
+ "🧠 Неврологічний (Гострий інсульт)": {
90
+ "info": "Пацієнтка: 67-річна жінка, раптові неврологічні симптоми\nПрезентація: Раптова слабкість правої сторони 2 години тому\nОгляд: Свідома, дезорієнтована у часі, правостороннє опущення обличчя\nНеврологія: Правостороння геміплегія, афазія, девіація очей вліво\nКТ голови: Гострого крововиливу немає, рання ішемія у лівій МСА\nNIHSS: 15 балів",
91
+ "expected": "Acute ischemic stroke in the left middle cerebral artery territory"
92
+ },
93
+ "🦠 Інфекційний (Сепсис)": {
94
+ "info": "Пацієнт: 45-річний чоловік з прогресуючою лихоманкою\nСкарги: Висока температура 39.5°C, озноб, загальна слабкість 3 дні\nОгляд: Гарячий, тахікардія 120/хв, гіпотензія 85/50\nЛабораторно: Лейкоцити 18000, С-реактивний білок 180, прокальцитонін 5.2\nПосів крові: Pending, lactate 4.2 ммоль/л\nАнамнез: Нещодавня стоматологічна процедура",
95
+ "expected": "Sepsis with a possible odontogenic source"
96
+ }
97
+ }
98
+
99
+ def diagnose_with_full_logging(
100
+ self, case_name: str, patient_info: str, mode: str, budget: int, max_iterations: int,
101
+ model_name: str, expected_diagnosis: str = "", enable_logging: bool = True,
102
+ progress=gr.Progress()
103
+ ) -> Tuple[str, str, str, Optional[object], Optional[object], str, str]:
104
+
105
+ if not MAI_DX_AVAILABLE:
106
+ return self._format_error(f"❌ MAI-DX недоступний: {IMPORT_ERROR}")
107
+ if not patient_info.strip():
108
+ return self._format_error("❌ Введіть інформацію про пацієнта")
109
+
110
+ self.current_metrics.reset()
111
+ conversation_log = ""
112
+ structured_conversations = ""
113
+ case_id = None
114
+
115
+ # Тимчасово відключаємо Rich форматування
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,
128
+ patient_info=patient_info,
129
+ mode=mode,
130
+ budget=budget
131
+ )
132
+ conversation_log += f"📝 Розпочато логування: {case_id}\n\n"
133
+
134
+ progress(0.2, desc="🤖 Створення AI-панелі...")
135
+
136
+ # Створюємо orchestrator
137
+ orchestrator = MaiDxOrchestrator(
138
+ model_name=model_name,
139
+ max_iterations=max_iterations,
140
+ initial_budget=budget,
141
+ mode=mode
142
+ )
143
+
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,
151
+ orchestrator_func=orchestrator.run,
152
+ initial_case_info=patient_info,
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(
184
+ initial_case_info=patient_info,
185
+ full_case_details=patient_info,
186
+ ground_truth_diagnosis=expected_diagnosis or "Unknown"
187
+ )
188
+
189
+ duration = time.time() - start_time
190
+ progress(0.9, desc="📊 Обробка результатів...")
191
+
192
+ # Завершуємо сесію
193
+ if enable_logging and case_id:
194
+ saved_case_id = self.conversation_logger.end_session(
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
+ session = DiagnosisSession(
212
+ case_id=case_id or "no_logging",
213
+ timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
214
+ case_name=case_name,
215
+ patient_info=patient_info,
216
+ mode=mode,
217
+ budget=budget,
218
+ diagnosis=result.final_diagnosis,
219
+ confidence=result.accuracy_score,
220
+ cost=result.total_cost,
221
+ iterations=getattr(result, 'iterations', 0),
222
+ duration=duration,
223
+ status="✅ Успішно" if result.accuracy_score >= 3.0 else "⚠️ Потребує перегляду",
224
+ reasoning=getattr(result, 'accuracy_reasoning', 'N/A')
225
+ )
226
+ self.sessions_history.append(session)
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),
237
+ self._generate_enhanced_recommendations(result, expected_diagnosis),
238
+ metrics_plot,
239
+ agent_plot,
240
+ conversation_log,
241
+ structured_conversations
242
+ )
243
+
244
+ except Exception as e:
245
+ error_msg = f"❌ Помилка діагностики: {str(e)}"
246
+ if case_id:
247
+ error_msg += f"\n🗂️ Case ID: {case_id}"
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, case_id: str) -> str:
256
+ """Форматує структуровані розмови у читабельний вигляд"""
257
+ conversations = self.conversation_logger.get_session_conversations(case_id)
258
+
259
+ if not conversations:
260
+ return "📭 Розмови не знайдено"
261
+
262
+ output = "## 💬 Структуровані розмови агентів\n\n"
263
+
264
+ for conv in conversations:
265
+ output += f"### 🔄 Раунд {conv.round_number}\n"
266
+ output += f"**Час:** {conv.start_time} - {conv.end_time or 'В процесі'}\n\n"
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}]\n"
277
+ output += f"```\n{msg.content[:500]}{'...' if len(msg.content) > 500 else ''}\n```\n\n"
278
+
279
+ if conv.decision:
280
+ output += f"**🎯 Рішення:** {conv.decision}\n"
281
+
282
+ if conv.tests_ordered:
283
+ output += f"**🧪 Замовлені тести:** {', '.join(conv.tests_ordered)}\n"
284
+
285
+ if conv.questions_asked:
286
+ output += f"**❓ Поставлені питання:** {', '.join(conv.questions_asked[:3])}...\n"
287
+
288
+ if conv.cost_incurred > 0:
289
+ output += f"**💰 Вартість раунду:** ${conv.cost_incurred:.2f}\n"
290
+
291
+ output += "\n---\n\n"
292
+
293
+ return output
294
+
295
+ def _create_enhanced_metrics_visualization(self):
296
+ """Створює покращену візуалізацію метрик"""
297
+ if not PLOTLY_AVAILABLE:
298
+ return None
299
+
300
+ if not self.current_metrics.conversation_rounds:
301
+ return None
302
+
303
+ try:
304
+ # Створюємо subplot з двома графіками
305
+ from plotly.subplots import make_subplots
306
+
307
+ fig = make_subplots(
308
+ rows=2, cols=1,
309
+ subplot_titles=('Прогресія вартості', 'Активність по раундах'),
310
+ vertical_spacing=0.15
311
+ )
312
+
313
+ # Графік вартості
314
+ if self.current_metrics.cost_progression:
315
+ fig.add_trace(
316
+ go.Scatter(
317
+ x=[p['time'] for p in self.current_metrics.cost_progression],
318
+ y=[p['cost'] for p in self.current_metrics.cost_progression],
319
+ mode='lines+markers',
320
+ name='Вартість',
321
+ line=dict(color='#3498db', width=3)
322
+ ),
323
+ row=1, col=1
324
+ )
325
+
326
+ # Графік активності по раундах
327
+ rounds_data = self.current_metrics.conversation_rounds
328
+ if rounds_data:
329
+ fig.add_trace(
330
+ go.Bar(
331
+ x=[f"Раунд {r['round']}" for r in rounds_data],
332
+ y=[r['messages'] for r in rounds_data],
333
+ name='Кількість повідомлень',
334
+ marker_color='#2ecc71'
335
+ ),
336
+ row=2, col=1
337
+ )
338
+
339
+ fig.update_layout(
340
+ height=600,
341
+ showlegend=True,
342
+ template='plotly_white'
343
+ )
344
+
345
+ return fig
346
+
347
+ except Exception as e:
348
+ print(f"Помилка візуалізації: {e}")
349
+ return None
350
+
351
+ def _create_conversation_flow_chart(self):
352
+ """Створює діаграму потоку розмов між агентами"""
353
+ if not PLOTLY_AVAILABLE:
354
+ return None
355
+
356
+ activity_data = {k: v for k, v in self.current_metrics.agents_activity.items() if v > 0}
357
+
358
+ if not activity_data:
359
+ return None
360
+
361
+ try:
362
+ # Кольорова схема для різних агентів
363
+ colors = {
364
+ 'Dr. Hypothesis': '#3498db',
365
+ 'Dr. Test-Chooser': '#e74c3c',
366
+ 'Dr. Challenger': '#f39c12',
367
+ 'Dr. Stewardship': '#27ae60',
368
+ 'Dr. Checklist': '#9b59b6',
369
+ 'Consensus Coordinator': '#34495e',
370
+ 'Gatekeeper': '#16a085',
371
+ 'Judge': '#c0392b'
372
+ }
373
+
374
+ agent_colors = [colors.get(agent, '#95a5a6') for agent in activity_data.keys()]
375
+
376
+ fig = go.Figure(go.Bar(
377
+ x=list(activity_data.keys()),
378
+ y=list(activity_data.values()),
379
+ text=list(activity_data.values()),
380
+ textposition='auto',
381
+ marker_color=agent_colors
382
+ ))
383
+
384
+ fig.update_layout(
385
+ title='🤖 Активність агентів у діагностичному процесі',
386
+ xaxis_title='Агенти',
387
+ yaxis_title='Кількість взаємодій',
388
+ template='plotly_white',
389
+ height=400,
390
+ margin=dict(t=60, b=100)
391
+ )
392
+
393
+ fig.update_xaxes(tickangle=-45)
394
+
395
+ return fig
396
+
397
+ except Exception as e:
398
+ print(f"Помилка графіку агентів: {e}")
399
+ return None
400
+
401
+ def _format_main_result(self, session):
402
+ efficiency = ((session.budget - session.cost) / session.budget * 100) if session.budget > 0 else 0
403
+ confidence_emoji = "🎯" if session.confidence >= 4.0 else "👍" if session.confidence >= 3.0 else "⚠️"
404
+
405
+ return f"""
406
+ ## 🏥 Результати MAI-DX Діагностики
407
+
408
+ ### 📋 Основна інформація
409
+ - **🗂️ Випадок**: {session.case_name}
410
+ - **🆔 ID сесії**: {session.case_id}
411
+ - **⏰ Час**: {session.timestamp}
412
+ - **🔧 Режим**: {session.mode}
413
+
414
+ ### {confidence_emoji} Діагностичний висновок
415
+ **{session.diagnosis}**
416
+
417
+ ### 📊 Показники якості
418
+ - **Точність**: {session.confidence:.1f}/5.0 ⭐
419
+ - **Статус**: {session.status}
420
+ - **Ітерації**: {session.iterations} циклів
421
+ - **Кількість агентів**: {len([a for a in self.current_metrics.agents_activity.values() if a > 0])}
422
+
423
+ ### 💰 Економічні показники
424
+ - **Витрачено**: ${session.cost:,.2f}
425
+ - **Бюджет**: ${session.budget:,}
426
+ - **Ефективність**: {efficiency:.1f}% бюджету збережено
427
+ - **Швидкість**: {session.duration:.1f} секунд
428
+ """
429
+
430
+ def _format_detailed_analysis(self, session):
431
+ return f"""
432
+ ## 🔬 Детальний клінічний аналіз
433
+
434
+ ### 💭 Мед��чне обґрунтування
435
+ {session.reasoning}
436
+
437
+ ### 📂 Файли логів
438
+ - JSON: `mai_dx_logs/{session.case_id}.json`
439
+ - Raw text: `mai_dx_logs/{session.case_id}_raw.txt`
440
+ - HTML звіт: `mai_dx_logs/{session.case_id}_conversation.html`
441
+ """
442
+
443
+ def _generate_enhanced_recommendations(self, result, expected_diagnosis):
444
+ comparison = ""
445
+ if expected_diagnosis:
446
+ is_match = expected_diagnosis.lower() in result.final_diagnosis.lower()
447
+ comparison = f"""
448
+ ### 🎯 Порівняння з очікуваним
449
+ **Очікувався**: {expected_diagnosis}
450
+ **Отримано**: {result.final_diagnosis}
451
+ **Збіг**: {'✅ Так' if is_match else '❌ Ні'}
452
+ """
453
+
454
+ return f"""
455
+ ## 💡 Клінічні рекомендації
456
+
457
+ ### 🏥 Негайні дії
458
+ - 🔍 Верифікувати діагноз з лікарем-спеціалістом
459
+ - 📋 Розглянути необхідність додаткових досліджень
460
+ - 📊 Переглянути детальний лог розмов агентів
461
+
462
+ {comparison}
463
+
464
+ ### ⚠️ Важливе застереження
465
+ 🔴 **Цей діагноз згенеровано ШІ і НЕ замінює професійну медичну консультацію.**
466
+ """
467
+
468
+ def _format_error(self, error_msg):
469
+ return (error_msg, "", "", None, None, "", "")
470
+
471
+ def load_sample_case(self, sample_key):
472
+ case_data = self.sample_cases.get(sample_key, {})
473
+ return case_data.get("info", ""), sample_key, case_data.get("expected", "")
474
+
475
+ def get_session_details(self, case_id: str):
476
+ """Отримати детальну інформацію про сесію"""
477
+ conversations = self.conversation_logger.get_session_conversations(case_id)
478
+
479
+ if not conversations:
480
+ return "Сесія не знайдена"
481
+
482
+ details = f"## Детальний аналіз сесії {case_id}\n\n"
483
+ details += f"**Кількість раундів**: {len(conversations)}\n"
484
+
485
+ total_messages = sum(len(conv.messages) for conv in conversations)
486
+ details += f"**Всього повідомлень**: {total_messages}\n\n"
487
+
488
+ # Статистика по агентах
489
+ agent_stats = {}
490
+ for conv in conversations:
491
+ for msg in conv.messages:
492
+ agent_stats[msg.agent_name] = agent_stats.get(msg.agent_name, 0) + 1
493
+
494
+ details += "### Статистика по агентах:\n"
495
+ for agent, count in sorted(agent_stats.items(), key=lambda x: x[1], reverse=True):
496
+ details += f"- **{agent}**: {count} повідомлень\n"
497
+
498
+ return details
499
+
500
+ def create_updated_gradio_interface():
501
+ interface = UpdatedMAIDXInterface()
502
+
503
+ custom_css = """
504
+ .gradio-container {
505
+ font-family: 'Inter', sans-serif;
506
+ }
507
+ .main-header {
508
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
509
+ color: white;
510
+ padding: 2rem;
511
+ border-radius: 15px;
512
+ margin-bottom: 2rem;
513
+ text-align: center;
514
+ }
515
+ .conversation-log {
516
+ font-family: 'Fira Code', monospace;
517
+ background: #2d3748;
518
+ color: #e2e8f0;
519
+ border-radius: 8px;
520
+ padding: 1rem;
521
+ max-height: 600px;
522
+ overflow-y: auto;
523
+ white-space: pre-wrap;
524
+ }
525
+ .structured-conv {
526
+ background: #f7fafc;
527
+ border: 1px solid #e2e8f0;
528
+ border-radius: 8px;
529
+ padding: 1rem;
530
+ max-height: 800px;
531
+ overflow-y: auto;
532
+ }
533
+ """
534
+
535
+ with gr.Blocks(
536
+ title="🏥 MAI-DX Enhanced Platform with Full Logging",
537
+ theme=gr.themes.Soft(primary_hue="blue"),
538
+ css=custom_css
539
+ ) as demo:
540
+
541
+ gr.HTML("""
542
+ <div class='main-header'>
543
+ <h1>🏥 MAI-DX Enhanced Research Platform</h1>
544
+ <p>🤖 Платформа для ШІ-діагностики з повним логуванням розмов агентів</p>
545
+ </div>
546
+ """)
547
+
548
+ with gr.Tabs():
549
+ with gr.Tab("🩺 Діагностика", elem_id="diagnosis-tab"):
550
+ with gr.Row():
551
+ with gr.Column(scale=1):
552
+ gr.Markdown("### 📝 Клінічний випадок")
553
+ case_name = gr.Textbox(
554
+ label="🏷️ Назва випадку",
555
+ placeholder="Наприклад: Кардіологічний випадок №1"
556
+ )
557
+
558
+ sample_selector = gr.Dropdown(
559
+ choices=list(interface.sample_cases.keys()),
560
+ label="🎯 Готові тестові випадки",
561
+ interactive=True
562
+ )
563
+
564
+ patient_info = gr.Textbox(
565
+ label="👤 Інформація про пацієнта",
566
+ lines=12,
567
+ placeholder="Введіть опис клінічного випадку..."
568
+ )
569
+
570
+ expected_diagnosis = gr.Textbox(
571
+ label="🎯 Очікуваний діагноз (англ.)",
572
+ lines=2
573
+ )
574
+
575
+ with gr.Column(scale=1):
576
+ gr.Markdown("### ⚙️ Налаштування")
577
+
578
+ mode = gr.Radio(
579
+ choices=["instant", "question_only", "budgeted", "no_budget", "ensemble"],
580
+ label="🔧 Режим",
581
+ value="budgeted",
582
+ interactive=True
583
+ )
584
+
585
+ budget = gr.Slider(
586
+ minimum=500,
587
+ maximum=10000,
588
+ step=500,
589
+ value=3000,
590
+ label="💵 Бюджет ($)",
591
+ interactive=True
592
+ )
593
+
594
+ max_iterations = gr.Slider(
595
+ minimum=1,
596
+ maximum=15,
597
+ step=1,
598
+ value=8,
599
+ label="🔄 Макс. ітерацій",
600
+ interactive=True
601
+ )
602
+
603
+ model_name = gr.Dropdown(
604
+ choices=[
605
+ "gemini/gemini-2.5-flash",
606
+ "gpt-4",
607
+ "gpt-4-turbo",
608
+ "claude-3-5-sonnet",
609
+ "gpt-4o"
610
+ ],
611
+ label="🤖 LLM Модель",
612
+ value="gemini/gemini-2.5-flash",
613
+ interactive=True
614
+ )
615
+
616
+ enable_logging = gr.Checkbox(
617
+ label="📝 Повне логування розмов",
618
+ value=True,
619
+ interactive=True
620
+ )
621
+
622
+ diagnose_btn = gr.Button(
623
+ "🚀 Запустити діагностику",
624
+ variant="primary",
625
+ size="lg"
626
+ )
627
+
628
+ # Зв'язуємо вибір прикладу
629
+ sample_selector.change(
630
+ interface.load_sample_case,
631
+ inputs=sample_selector,
632
+ outputs=[patient_info, case_name, expected_diagnosis]
633
+ )
634
+
635
+ gr.Markdown("---\n## 📊 Результати діагностики")
636
+
637
+ with gr.Row():
638
+ with gr.Column(scale=2):
639
+ main_result = gr.Markdown()
640
+ detailed_analysis = gr.Markdown()
641
+
642
+ with gr.Column(scale=1):
643
+ recommendations = gr.Markdown()
644
+
645
+ # Візуалізації
646
+ if PLOTLY_AVAILABLE:
647
+ with gr.Row():
648
+ metrics_plot = gr.Plot(label="📈 Метрики процесу")
649
+ agent_activity_plot = gr.Plot(label="🤖 Активність агентів")
650
+ else:
651
+ metrics_plot = gr.Plot(visible=False)
652
+ agent_activity_plot = gr.Plot(visible=False)
653
+
654
+ # Логи
655
+ with gr.Row():
656
+ with gr.Column():
657
+ gr.Markdown("### 💬 Сирий вивід системи")
658
+ conversation_logs = gr.Textbox(
659
+ label="Raw logs",
660
+ lines=15,
661
+ elem_classes=["conversation-log"],
662
+ interactive=False,
663
+ show_copy_button=True
664
+ )
665
+
666
+ with gr.Column():
667
+ gr.Markdown("### 🗣️ Структуровані розмови агентів")
668
+ structured_conversations = gr.Markdown(
669
+ elem_classes=["structured-conv"]
670
+ )
671
+
672
+ # Підключаємо функцію діагностики
673
+ diagnose_btn.click(
674
+ interface.diagnose_with_full_logging,
675
+ inputs=[
676
+ case_name, patient_info, mode, budget,
677
+ max_iterations, model_name, expected_diagnosis,
678
+ enable_logging
679
+ ],
680
+ outputs=[
681
+ main_result, detailed_analysis, recommendations,
682
+ metrics_plot, agent_activity_plot,
683
+ conversation_logs, structured_conversations
684
+ ]
685
+ )
686
+
687
+ with gr.Tab("📊 Аналіз логів", elem_id="logs-tab"):
688
+ gr.Markdown("### 🔍 Аналіз збережених діагностичних сесій")
689
+
690
+ with gr.Row():
691
+ case_id_input = gr.Textbox(
692
+ label="🆔 ID сесії",
693
+ placeholder="Введіть ID сесії для аналізу"
694
+ )
695
+
696
+ analyze_btn = gr.Button("📋 Аналізувати")
697
+
698
+ session_details = gr.Markdown()
699
+
700
+ # Функція аналізу
701
+ analyze_btn.click(
702
+ interface.get_session_details,
703
+ inputs=[case_id_input],
704
+ outputs=[session_details]
705
+ )
706
+
707
+ gr.Markdown("### 📁 Експорт даних")
708
+
709
+ with gr.Row():
710
+ export_format = gr.Radio(
711
+ choices=["JSON", "HTML", "CSV"],
712
+ label="Формат експорту",
713
+ value="HTML"
714
+ )
715
+
716
+ export_btn = gr.Button("📤 Експортувати")
717
+
718
+ export_status = gr.Markdown()
719
+
720
+ return demo
721
+
722
+ if __name__ == "__main__":
723
+ print("🚀 Запуск MAI-DX Enhanced Platform with Full Agent Conversation Logging...")
724
+ demo = create_updated_gradio_interface()
725
+ demo.launch(
726
+ server_name="0.0.0.0",
727
+ server_port=7860,
728
+ share=True,
729
+ debug=False
730
+ )
utils/analyze_logs.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Скрипт для аналізу збережених логів MAI-DX
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import pandas as pd
9
+ from datetime import datetime
10
+ from collections import defaultdict
11
+ import argparse
12
+
13
+ def load_all_sessions(log_dir="mai_dx_logs"):
14
+ """Завантажити всі сесії з директорії логів"""
15
+ sessions = []
16
+
17
+ if not os.path.exists(log_dir):
18
+ print(f"❌ Директорія {log_dir} не існує")
19
+ return sessions
20
+
21
+ for filename in os.listdir(log_dir):
22
+ if filename.endswith('.json') and not filename.endswith('_analytics.json'):
23
+ filepath = os.path.join(log_dir, filename)
24
+ try:
25
+ with open(filepath, 'r', encoding='utf-8') as f:
26
+ session = json.load(f)
27
+ sessions.append(session)
28
+ except Exception as e:
29
+ print(f"⚠️ Помилка читання {filename}: {e}")
30
+
31
+ return sessions
32
+
33
+ def analyze_session(session):
34
+ """Детальний аналіз однієї сесії"""
35
+ print(f"\n{'='*60}")
36
+ print(f"📋 Аналіз сесії: {session['case_id']}")
37
+ print(f"{'='*60}")
38
+
39
+ print(f"\n📊 Основна інформація:")
40
+ print(f" - Назва випадку: {session['case_name']}")
41
+ print(f" - Час: {session['timestamp']}")
42
+ print(f" - Режим: {session['mode']}")
43
+ print(f" - Статус: {session['status']}")
44
+
45
+ print(f"\n💊 Діагноз:")
46
+ print(f" - Фінальний діагноз: {session['diagnosis']}")
47
+ print(f" - Впевненість: {session['confidence']}/5.0")
48
+ print(f" - Обґрунтування: {session['reasoning'][:100]}...")
49
+
50
+ print(f"\n💰 Економіка:")
51
+ print(f" - Витрачено: ${session['cost']}")
52
+ print(f" - Бюджет: ${session['budget']}")
53
+ print(f" - Ефективність: {((session['budget'] - session['cost']) / session['budget'] * 100):.1f}%")
54
+
55
+ print(f"\n⏱️ Продуктивність:")
56
+ print(f" - Тривалість: {session['duration']:.1f} секунд")
57
+ print(f" - Ітерацій: {session['iterations']}")
58
+
59
+ # Аналіз розмов
60
+ conversations = session.get('conversations', [])
61
+ if conversations:
62
+ print(f"\n💬 Аналіз розмов:")
63
+ print(f" - Всього раундів: {len(conversations)}")
64
+
65
+ # Підрахунок активності агентів
66
+ agent_activity = defaultdict(int)
67
+ message_types = defaultdict(int)
68
+ total_messages = 0
69
+
70
+ for conv in conversations:
71
+ for msg in conv.get('messages', []):
72
+ agent_activity[msg['agent_name']] += 1
73
+ message_types[msg['message_type']] += 1
74
+ total_messages += 1
75
+
76
+ print(f" - Всього повідомлень: {total_messages}")
77
+
78
+ print(f"\n 🤖 Активність агентів:")
79
+ for agent, count in sorted(agent_activity.items(), key=lambda x: x[1], reverse=True):
80
+ print(f" - {agent}: {count} повідомлень ({count/total_messages*100:.1f}%)")
81
+
82
+ print(f"\n 📝 Типи повідомлень:")
83
+ for msg_type, count in sorted(message_types.items(), key=lambda x: x[1], reverse=True):
84
+ print(f" - {msg_type}: {count} ({count/total_messages*100:.1f}%)")
85
+
86
+ # Деталі раундів
87
+ print(f"\n 🔄 Деталі раундів:")
88
+ for i, conv in enumerate(conversations):
89
+ msgs = len(conv.get('messages', []))
90
+ cost = conv.get('cost_incurred', 0)
91
+ decision = conv.get('decision', 'N/A')
92
+ print(f" Раунд {i+1}: {msgs} повідомлень, ${cost:.2f}, Рішення: {decision}")
93
+
94
+ def generate_summary_report(sessions, output_file="mai_dx_summary_report.html"):
95
+ """Генерація зведеного HTML звіту по всіх сесіях"""
96
+ if not sessions:
97
+ print("❌ Немає сесій для аналізу")
98
+ return
99
+
100
+ html_content = """
101
+ <!DOCTYPE html>
102
+ <html>
103
+ <head>
104
+ <meta charset="UTF-8">
105
+ <title>MAI-DX Summary Report</title>
106
+ <style>
107
+ body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
108
+ .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
109
+ h1 { color: #333; text-align: center; }
110
+ .summary { background: #e8f4f8; padding: 20px; border-radius: 8px; margin: 20px 0; }
111
+ .metric { display: inline-block; margin: 10px 20px; }
112
+ .metric-value { font-size: 24px; font-weight: bold; color: #007bff; }
113
+ .metric-label { color: #666; }
114
+ table { width: 100%; border-collapse: collapse; margin: 20px 0; }
115
+ th { background: #007bff; color: white; padding: 12px; text-align: left; }
116
+ td { padding: 10px; border-bottom: 1px solid #ddd; }
117
+ tr:hover { background: #f5f5f5; }
118
+ .success { color: #28a745; }
119
+ .warning { color: #ffc107; }
120
+ .danger { color: #dc3545; }
121
+ </style>
122
+ </head>
123
+ <body>
124
+ <div class="container">
125
+ <h1>🏥 MAI-DX Summary Report</h1>
126
+ """
127
+
128
+ # Загальна статистика
129
+ total_sessions = len(sessions)
130
+ avg_accuracy = sum(s['confidence'] for s in sessions) / total_sessions
131
+ avg_cost = sum(s['cost'] for s in sessions) / total_sessions
132
+ avg_duration = sum(s['duration'] for s in sessions) / total_sessions
133
+ success_rate = sum(1 for s in sessions if s['confidence'] >= 3.0) / total_sessions * 100
134
+
135
+ html_content += f"""
136
+ <div class="summary">
137
+ <h2>📊 Загальна статистика</h2>
138
+ <div class="metric">
139
+ <div class="metric-value">{total_sessions}</div>
140
+ <div class="metric-label">Всього сесій</div>
141
+ </div>
142
+ <div class="metric">
143
+ <div class="metric-value">{avg_accuracy:.2f}/5.0</div>
144
+ <div class="metric-label">Середня точність</div>
145
+ </div>
146
+ <div class="metric">
147
+ <div class="metric-value">${avg_cost:.2f}</div>
148
+ <div class="metric-label">Середня вартість</div>
149
+ </div>
150
+ <div class="metric">
151
+ <div class="metric-value">{avg_duration:.1f}с</div>
152
+ <div class="metric-label">Середній час</div>
153
+ </div>
154
+ <div class="metric">
155
+ <div class="metric-value">{success_rate:.1f}%</div>
156
+ <div class="metric-label">Успішність</div>
157
+ </div>
158
+ </div>
159
+
160
+ <h2>📋 Детальна таблиця сесій</h2>
161
+ <table>
162
+ <thead>
163
+ <tr>
164
+ <th>ID сесії</th>
165
+ <th>Час</th>
166
+ <th>Випадок</th>
167
+ <th>Діагноз</th>
168
+ <th>Точність</th>
169
+ <th>Вартість</th>
170
+ <th>Час</th>
171
+ <th>Статус</th>
172
+ </tr>
173
+ </thead>
174
+ <tbody>
175
+ """
176
+
177
+ # Сортуємо сесії за часом
178
+ sorted_sessions = sorted(sessions, key=lambda x: x['timestamp'], reverse=True)
179
+
180
+ for session in sorted_sessions:
181
+ status_class = 'success' if session['confidence'] >= 3.0 else 'warning'
182
+ html_content += f"""
183
+ <tr>
184
+ <td>{session['case_id']}</td>
185
+ <td>{session['timestamp'][:19]}</td>
186
+ <td>{session['case_name']}</td>
187
+ <td>{session['diagnosis']}</td>
188
+ <td class="{status_class}">{session['confidence']:.1f}/5.0</td>
189
+ <td>${session['cost']:.2f}</td>
190
+ <td>{session['duration']:.1f}с</td>
191
+ <td>{session['status']}</td>
192
+ </tr>
193
+ """
194
+
195
+ html_content += """
196
+ </tbody>
197
+ </table>
198
+
199
+ <p style="text-align: center; color: #666; margin-top: 30px;">
200
+ Згенеровано: """ + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """
201
+ </p>
202
+ </div>
203
+ </body>
204
+ </html>
205
+ """
206
+
207
+ with open(output_file, 'w', encoding='utf-8') as f:
208
+ f.write(html_content)
209
+
210
+ print(f"\n✅ Зведений звіт збережено: {output_file}")
211
+
212
+ def export_to_csv(sessions, output_file="mai_dx_sessions.csv"):
213
+ """Експорт сесій в CSV"""
214
+ if not sessions:
215
+ print("❌ Немає сесій для експорту")
216
+ return
217
+
218
+ # Створюємо DataFrame
219
+ data = []
220
+ for session in sessions:
221
+ # Підраховуємо агентів та повідомлення
222
+ total_messages = 0
223
+ unique_agents = set()
224
+
225
+ for conv in session.get('conversations', []):
226
+ for msg in conv.get('messages', []):
227
+ total_messages += 1
228
+ unique_agents.add(msg['agent_name'])
229
+
230
+ data.append({
231
+ 'case_id': session['case_id'],
232
+ 'timestamp': session['timestamp'],
233
+ 'case_name': session['case_name'],
234
+ 'mode': session['mode'],
235
+ 'diagnosis': session['diagnosis'],
236
+ 'confidence': session['confidence'],
237
+ 'cost': session['cost'],
238
+ 'budget': session['budget'],
239
+ 'duration': session['duration'],
240
+ 'iterations': session['iterations'],
241
+ 'status': session['status'],
242
+ 'total_messages': total_messages,
243
+ 'unique_agents': len(unique_agents)
244
+ })
245
+
246
+ df = pd.DataFrame(data)
247
+ df.to_csv(output_file, index=False, encoding='utf-8')
248
+
249
+ print(f"✅ Дані експортовано в CSV: {output_file}")
250
+ print(f" Всього записів: {len(df)}")
251
+
252
+ def main():
253
+ parser = argparse.ArgumentParser(description='Аналіз логів MAI-DX')
254
+ parser.add_argument('--dir', default='mai_dx_logs', help='Директорія з логами')
255
+ parser.add_argument('--case', help='ID конкретної сесії для детального аналізу')
256
+ parser.add_argument('--export-csv', action='store_true', help='Експортувати в CSV')
257
+ parser.add_argument('--summary', action='store_true', help='Створити зведений HTML звіт')
258
+
259
+ args = parser.parse_args()
260
+
261
+ print("🔬 Аналіз логів MAI-DX")
262
+ print("=" * 60)
263
+
264
+ # Завантажуємо сесії
265
+ sessions = load_all_sessions(args.dir)
266
+ print(f"📁 Знайдено {len(sessions)} сесій в {args.dir}")
267
+
268
+ if args.case:
269
+ # Аналіз конкретної сесії
270
+ session = next((s for s in sessions if s['case_id'] == args.case), None)
271
+ if session:
272
+ analyze_session(session)
273
+ else:
274
+ print(f"❌ Сесія {args.case} не знайдена")
275
+ elif args.export_csv:
276
+ # Експорт в CSV
277
+ export_to_csv(sessions)
278
+ elif args.summary:
279
+ # Створення зведеного звіту
280
+ generate_summary_report(sessions)
281
+ else:
282
+ # Загальна статистика
283
+ if sessions:
284
+ print(f"\n📊 Загальна статистика:")
285
+ print(f" - Середня точність: {sum(s['confidence'] for s in sessions) / len(sessions):.2f}/5.0")
286
+ print(f" - Середня вартість: ${sum(s['cost'] for s in sessions) / len(sessions):.2f}")
287
+ print(f" - Середній час: {sum(s['duration'] for s in sessions) / len(sessions):.1f} секунд")
288
+
289
+ print(f"\n💡 Для детального аналізу використовуйте:")
290
+ print(f" python {__file__} --case <case_id>")
291
+ print(f" python {__file__} --export-csv")
292
+ print(f" python {__file__} --summary")
293
+
294
+ if __name__ == "__main__":
295
+ main()
utils/test_logging.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Тестовий скрипт для перевірки що логування працює коректно
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+ import json
10
+ from enhanced_mai_dx_logger import MAIDxConversationLogger
11
+
12
+ def test_basic_logging():
13
+ """Тест базового логування"""
14
+ print("🧪 Тест 1: Базове логування")
15
+
16
+ logger = MAIDxConversationLogger("test_logs")
17
+
18
+ # Створюємо тестову сесію
19
+ case_id = logger.start_session(
20
+ case_name="Тестовий випадок",
21
+ patient_info="Тестова інформація",
22
+ mode="test",
23
+ budget=1000
24
+ )
25
+
26
+ print(f"✅ Створено сесію: {case_id}")
27
+
28
+ # Симулюємо захоплення виводу
29
+ def mock_orchestrator_run(*args, **kwargs):
30
+ print("Starting Diagnostic Loop 1/3")
31
+ print("2025-07-11 10:00:00 | INFO | mai_dx.main:_run_panel_deliberation:1304 - 🧠 Dr. Hypothesis analyzing differential diagnosis...")
32
+ print("╭─── Agent Name Dr. Hypothesis [Max Loops: 1] ───╮")
33
+ print("│ Analyzing patient symptoms... │")
34
+ print("│ High probability of sepsis │")
35
+ print("╰────────────────────────────────────────────────╯")
36
+ print("2025-07-11 10:00:05 | INFO | mai_dx.main:run:1689 - ⚕️ Panel decision: DIAGNOSE -> Sepsis")
37
+
38
+ # Симулюємо результат
39
+ class MockResult:
40
+ final_diagnosis = "Sepsis"
41
+ accuracy_score = 4.0
42
+ total_cost = 500.0
43
+
44
+ return MockResult()
45
+
46
+ # Виконуємо з захопленням
47
+ result = logger.capture_orchestrator_output(
48
+ case_id=case_id,
49
+ orchestrator_func=mock_orchestrator_run
50
+ )
51
+
52
+ print(f"✅ Діагноз: {result.final_diagnosis}")
53
+
54
+ # Завершуємо сесію
55
+ saved_id = logger.end_session(
56
+ case_id=case_id,
57
+ final_diagnosis=result.final_diagnosis,
58
+ confidence=result.accuracy_score,
59
+ cost=result.total_cost,
60
+ reasoning="Test reasoning"
61
+ )
62
+
63
+ print(f"✅ Збережено: {saved_id}")
64
+
65
+ # Перевіряємо що файли створені
66
+ json_file = f"test_logs/{case_id}.json"
67
+ if os.path.exists(json_file):
68
+ with open(json_file, 'r') as f:
69
+ data = json.load(f)
70
+ print(f"✅ JSON файл містить {len(data['conversations'])} розмов")
71
+
72
+ if data['conversations']:
73
+ conv = data['conversations'][0]
74
+ print(f" - Раунд {conv['round_number']}: {len(conv['messages'])} повідомлень")
75
+ else:
76
+ print(f"❌ JSON файл не створено!")
77
+
78
+ print("-" * 60)
79
+
80
+ def test_conversation_parsing():
81
+ """Тест парсингу розмов"""
82
+ print("🧪 Тест 2: Парсинг складних розмов")
83
+
84
+ logger = MAIDxConversationLogger("test_logs")
85
+
86
+ case_id = logger.start_session(
87
+ case_name="Складний випадок",
88
+ patient_info="Множинні симптоми",
89
+ mode="budgeted",
90
+ budget=5000
91
+ )
92
+
93
+ # Симулюємо складніший вивід
94
+ def complex_orchestrator_run(*args, **kwargs):
95
+ output = """
96
+ 2025-07-11 10:00:00 | INFO | mai_dx.main:run:1679 - --- Starting Diagnostic Loop 1/3 ---
97
+ 2025-07-11 10:00:00 | INFO | mai_dx.main:run:1682 - Current cost: $300 | Remaining budget: $4,700
98
+ 2025-07-11 10:00:01 | INFO | mai_dx.main:_run_panel_deliberation:1304 - 🧠 Dr. Hypothesis analyzing differential diagnosis...
99
+ ╭─────────────────────────────── Agent Name Dr. Hypothesis [Max Loops: 1 ] ───────────────────────────────╮
100
+ │ Structured Output - Attempting Function Call Execution [10:00:02] │
101
+ │ │
102
+ │ Top differential diagnoses: │
103
+ │ 1. Acute Myocardial Infarction (85%) │
104
+ │ 2. Pulmonary Embolism (10%) │
105
+ │ 3. Aortic Dissection (5%) │
106
+ ╰──────────────────────────────────────────────────────────────��──────────────────────────────────────────╯
107
+
108
+ 2025-07-11 10:00:05 | INFO | mai_dx.main:_run_panel_deliberation:1320 - 🔬 Dr. Test-Chooser selecting optimal tests...
109
+ ╭────────────────────────────── Agent Name Dr. Test-Chooser [Max Loops: 1 ] ──────────────────────────────╮
110
+ │ Recommending tests: │
111
+ │ - ECG (immediate) │
112
+ │ - Troponin levels │
113
+ │ - Chest X-ray │
114
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
115
+
116
+ 2025-07-11 10:00:10 | INFO | mai_dx.main:run:1689 - ⚕️ Panel decision: TEST -> ECG, Troponin, CXR
117
+ 2025-07-11 10:00:10 | INFO | mai_dx.main:run:1692 - 💭 Medical reasoning: High suspicion for cardiac event
118
+ """
119
+ print(output)
120
+
121
+ class MockResult:
122
+ final_diagnosis = "Acute Myocardial Infarction"
123
+ accuracy_score = 4.5
124
+ total_cost = 850.0
125
+
126
+ return MockResult()
127
+
128
+ result = logger.capture_orchestrator_output(
129
+ case_id=case_id,
130
+ orchestrator_func=complex_orchestrator_run
131
+ )
132
+
133
+ # Генеруємо HTML
134
+ html_path = logger.export_conversation_html(case_id)
135
+ print(f"✅ HTML звіт: {html_path}")
136
+
137
+ # Завершуємо
138
+ logger.end_session(
139
+ case_id=case_id,
140
+ final_diagnosis=result.final_diagnosis,
141
+ confidence=result.accuracy_score,
142
+ cost=result.total_cost
143
+ )
144
+
145
+ print("-" * 60)
146
+
147
+ def test_error_handling():
148
+ """Тест обробки помилок"""
149
+ print("🧪 Тест 3: Обробка помилок")
150
+
151
+ logger = MAIDxConversationLogger("test_logs")
152
+
153
+ # Спроба завершити неіснуючу сесію
154
+ result = logger.end_session("non_existent_id")
155
+ print(f"✅ Обробка неіснуючої сесії: {result}")
156
+
157
+ # Спроба отримати розмови неіснуючої сесії
158
+ conversations = logger.get_session_conversations("non_existent_id")
159
+ print(f"✅ Розмови неіснуючої сесії: {len(conversations)} елементів")
160
+
161
+ print("-" * 60)
162
+
163
+ def main():
164
+ """Запуск всіх тестів"""
165
+ print("=" * 60)
166
+ print("🔬 Тестування системи логування MAI-DX")
167
+ print("=" * 60)
168
+
169
+ # Створюємо тестову директорію
170
+ os.makedirs("test_logs", exist_ok=True)
171
+
172
+ try:
173
+ test_basic_logging()
174
+ test_conversation_parsing()
175
+ test_error_handling()
176
+
177
+ print("\n✅ Всі тести пройдено успішно!")
178
+ print("📁 Перевірте директорію 'test_logs' для результатів")
179
+
180
+ except Exception as e:
181
+ print(f"\n❌ Помилка під час тестування: {e}")
182
+ import traceback
183
+ traceback.print_exc()
184
+
185
+ if __name__ == "__main__":
186
+ main()
utils/view_conversation.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Скрипт для швидкого перегляду розмов MAI-DX сесії в терміналі
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import sys
9
+ import argparse
10
+ from datetime import datetime
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+ from rich.syntax import Syntax
15
+ from rich.markdown import Markdown
16
+
17
+ # Спробуємо імпортувати Rich, якщо немає - використаємо простий вивід
18
+ try:
19
+ from rich.console import Console
20
+ from rich.panel import Panel
21
+ from rich.table import Table
22
+ from rich.syntax import Syntax
23
+ from rich.markdown import Markdown
24
+ RICH_AVAILABLE = True
25
+ console = Console()
26
+ except ImportError:
27
+ RICH_AVAILABLE = False
28
+ print("⚠️ Rich не встановлено. Для кращого відображення: pip install rich")
29
+
30
+ def load_session(case_id, log_dir="mai_dx_logs"):
31
+ """Завантажити конкретну сесію"""
32
+ json_path = os.path.join(log_dir, f"{case_id}.json")
33
+
34
+ if not os.path.exists(json_path):
35
+ # Спробуємо знайти файл з частковим збігом
36
+ for filename in os.listdir(log_dir):
37
+ if case_id in filename and filename.endswith('.json'):
38
+ json_path = os.path.join(log_dir, filename)
39
+ break
40
+ else:
41
+ return None
42
+
43
+ try:
44
+ with open(json_path, 'r', encoding='utf-8') as f:
45
+ return json.load(f)
46
+ except Exception as e:
47
+ print(f"❌ Помилка читання файлу: {e}")
48
+ return None
49
+
50
+ def display_session_rich(session):
51
+ """Відображення сесії з Rich форматуванням"""
52
+ # Заголовок
53
+ console.print(Panel.fit(
54
+ f"[bold blue]MAI-DX Diagnostic Session[/bold blue]\n"
55
+ f"[dim]{session['case_id']}[/dim]",
56
+ border_style="blue"
57
+ ))
58
+
59
+ # Основна інформація
60
+ info_table = Table(show_header=False, box=None, padding=(0, 2))
61
+ info_table.add_column(style="bold cyan")
62
+ info_table.add_column()
63
+
64
+ info_table.add_row("📋 Випадок:", session['case_name'])
65
+ info_table.add_row("🕐 Час:", session['timestamp'])
66
+ info_table.add_row("🔧 Режим:", session['mode'])
67
+ info_table.add_row("💊 Діагноз:", f"[bold yellow]{session['diagnosis']}[/bold yellow]")
68
+ info_table.add_row("📊 Точність:", f"{session['confidence']}/5.0 {'✅' if session['confidence'] >= 3.0 else '⚠️'}")
69
+ info_table.add_row("💰 Вартість:", f"${session['cost']} (бюджет: ${session['budget']})")
70
+ info_table.add_row("⏱️ Тривалість:", f"{session['duration']:.1f} секунд")
71
+
72
+ console.print(info_table)
73
+ console.print()
74
+
75
+ # Розмови
76
+ conversations = session.get('conversations', [])
77
+ if not conversations:
78
+ console.print("[yellow]⚠️ Розмови не знайдено[/yellow]")
79
+ return
80
+
81
+ console.print(f"[bold]💬 Розмови ({len(conversations)} раундів):[/bold]\n")
82
+
83
+ for i, conv in enumerate(conversations):
84
+ # Заголовок раунду
85
+ console.print(Panel(
86
+ f"[bold]Раунд {conv['round_number']}[/bold]\n"
87
+ f"Час: {conv.get('start_time', 'N/A')} - {conv.get('end_time', 'N/A')}\n"
88
+ f"Повідомлень: {len(conv.get('messages', []))}"
89
+ + (f"\nРішення: [yellow]{conv['decision']}[/yellow]" if conv.get('decision') else "")
90
+ + (f"\nВартість: ${conv['cost_incurred']}" if conv.get('cost_incurred') else ""),
91
+ title=f"[cyan]Раунд {i+1}[/cyan]",
92
+ border_style="cyan"
93
+ ))
94
+
95
+ # Повідомлення
96
+ for msg in conv.get('messages', []):
97
+ agent_color = {
98
+ 'Dr. Hypothesis': 'blue',
99
+ 'Dr. Test-Chooser': 'green',
100
+ 'Dr. Challenger': 'red',
101
+ 'Dr. Stewardship': 'yellow',
102
+ 'Dr. Checklist': 'magenta',
103
+ 'Consensus Coordinator': 'cyan',
104
+ 'Judge': 'white',
105
+ 'Gatekeeper': 'bright_black'
106
+ }.get(msg['agent_name'], 'white')
107
+
108
+ msg_type_icon = {
109
+ 'reasoning': '🤔',
110
+ 'decision': '💡',
111
+ 'input': '❓',
112
+ 'output': '📊',
113
+ 'log': '📝'
114
+ }.get(msg['message_type'], '💬')
115
+
116
+ console.print(
117
+ f"\n[bold {agent_color}]{msg_type_icon} {msg['agent_name']}[/bold {agent_color}] "
118
+ f"[dim]({msg['message_type']})[/dim]"
119
+ )
120
+
121
+ # Обмежуємо довжину контенту для кращого відображення
122
+ content = msg['content']
123
+ if len(content) > 500:
124
+ content = content[:500] + "..."
125
+
126
+ console.print(Panel(
127
+ content,
128
+ border_style=agent_color,
129
+ padding=(0, 1)
130
+ ))
131
+
132
+ def display_session_simple(session):
133
+ """Відображення сесії без Rich"""
134
+ print("="*60)
135
+ print(f"MAI-DX Diagnostic Session: {session['case_id']}")
136
+ print("="*60)
137
+
138
+ print(f"\n📋 Випадок: {session['case_name']}")
139
+ print(f"🕐 Час: {session['timestamp']}")
140
+ print(f"💊 Діагноз: {session['diagnosis']}")
141
+ print(f"📊 Точність: {session['confidence']}/5.0")
142
+ print(f"💰 Вартість: ${session['cost']} (бюджет: ${session['budget']})")
143
+
144
+ conversations = session.get('conversations', [])
145
+ if not conversations:
146
+ print("\n⚠️ Розмови не знайдено")
147
+ return
148
+
149
+ print(f"\n💬 Розмови ({len(conversations)} раундів):")
150
+
151
+ for i, conv in enumerate(conversations):
152
+ print(f"\n{'='*40}")
153
+ print(f"Раунд {conv['round_number']}")
154
+ print(f"{'='*40}")
155
+
156
+ if conv.get('decision'):
157
+ print(f"Рішення: {conv['decision']}")
158
+
159
+ for msg in conv.get('messages', []):
160
+ print(f"\n[{msg['message_type']}] {msg['agent_name']}:")
161
+ content = msg['content']
162
+ if len(content) > 300:
163
+ content = content[:300] + "..."
164
+ print(content)
165
+ print("-"*40)
166
+
167
+ def export_markdown(session, output_file=None):
168
+ """Експорт розмов в Markdown"""
169
+ if not output_file:
170
+ output_file = f"{session['case_id']}_conversation.md"
171
+
172
+ md_content = f"""# MAI-DX Diagnostic Session: {session['case_id']}
173
+
174
+ ## 📋 Інформація про сесію
175
+
176
+ - **Випадок**: {session['case_name']}
177
+ - **Час**: {session['timestamp']}
178
+ - **Режим**: {session['mode']}
179
+ - **Діагноз**: **{session['diagnosis']}**
180
+ - **Точність**: {session['confidence']}/5.0
181
+ - **Вартість**: ${session['cost']} (бюджет: ${session['budget']})
182
+ - **Тривалість**: {session['duration']:.1f} секунд
183
+
184
+ ## 💬 Розмови
185
+
186
+ """
187
+
188
+ for i, conv in enumerate(session.get('conversations', [])):
189
+ md_content += f"\n### Раунд {conv['round_number']}\n\n"
190
+
191
+ if conv.get('decision'):
192
+ md_content += f"**Рішення**: {conv['decision']}\n\n"
193
+
194
+ for msg in conv.get('messages', []):
195
+ md_content += f"\n#### {msg['agent_name']} [{msg['message_type']}]\n\n"
196
+ md_content += f"{msg['content']}\n\n"
197
+ md_content += "---\n"
198
+
199
+ with open(output_file, 'w', encoding='utf-8') as f:
200
+ f.write(md_content)
201
+
202
+ print(f"\n✅ Експортовано в Markdown: {output_file}")
203
+
204
+ def main():
205
+ parser = argparse.ArgumentParser(description='Перегляд розмов MAI-DX сесії')
206
+ parser.add_argument('case_id', help='ID сесії або його частина')
207
+ parser.add_argument('--dir', default='mai_dx_logs', help='Директорія з логами')
208
+ parser.add_argument('--simple', action='store_true', help='Простий вивід без Rich')
209
+ parser.add_argument('--export-md', action='store_true', help='Експортувати в Markdown')
210
+ parser.add_argument('--raw', action='store_true', help='Показати сирий текст')
211
+
212
+ args = parser.parse_args()
213
+
214
+ # Завантажуємо сесію
215
+ session = load_session(args.case_id, args.dir)
216
+
217
+ if not session:
218
+ print(f"❌ Сесія '{args.case_id}' не знайдена в {args.dir}")
219
+
220
+ # Показуємо доступні сесії
221
+ print("\n📁 Доступні сесії:")
222
+ for filename in sorted(os.listdir(args.dir)):
223
+ if filename.endswith('.json') and not filename.endswith('_analytics.json'):
224
+ print(f" - {filename[:-5]}")
225
+ sys.exit(1)
226
+
227
+ # Показуємо сирий текст якщо потрібно
228
+ if args.raw:
229
+ raw_file = os.path.join(args.dir, f"{session['case_id']}_raw.txt")
230
+ if os.path.exists(raw_file):
231
+ with open(raw_file, 'r', encoding='utf-8') as f:
232
+ print(f.read())
233
+ else:
234
+ print("❌ Файл з сирим текстом не знайдено")
235
+ return
236
+
237
+ # Експортуємо в Markdown якщо потрібно
238
+ if args.export_md:
239
+ export_markdown(session)
240
+ return
241
+
242
+ # Відображаємо сесію
243
+ if args.simple or not RICH_AVAILABLE:
244
+ display_session_simple(session)
245
+ else:
246
+ display_session_rich(session)
247
+
248
+ if __name__ == "__main__":
249
+ main()