DocUA commited on
Commit
cb898e1
·
1 Parent(s): 78e1ca4

Оновлено модуль логування MAI-DX з виправленим парсером для надійнішого захоплення розмов агентів. Внесено зміни в обробку виводу, покращено форматування результатів та аналізу, а також додано нові функції для відображення активності агентів. Виправлено помилки в інтерфейсі та оновлено документацію.

Browse files
enhanced_mai_dx_logger.py CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env python3
2
  """
3
  Покращений модуль для повного логування діагностичних сесій MAI-DX
4
- з детальним захопленням розмов між агентами.
5
  """
6
  import os
7
  import io
@@ -64,7 +64,8 @@ class EnhancedOutputCapture:
64
 
65
  def write(self, text):
66
  self.captured_output.write(text)
67
- self.original_stdout.write(text)
 
68
 
69
  def flush(self):
70
  self.original_stdout.flush()
@@ -95,6 +96,7 @@ class MAIDxConversationLogger:
95
 
96
  def finalize_and_save_session(self, session: DiagnosisSession, result: Any, raw_output: str, duration: float) -> DiagnosisSession:
97
  session.raw_output = raw_output
 
98
  self._parse_captured_output(session, raw_output)
99
 
100
  session.diagnosis = getattr(result, 'final_diagnosis', 'N/A')
@@ -110,6 +112,10 @@ class MAIDxConversationLogger:
110
  return session
111
 
112
  def _parse_captured_output(self, session: DiagnosisSession, captured_text: str):
 
 
 
 
113
  lines = captured_text.split('\n')
114
  current_round: Optional[AgentConversation] = None
115
  current_agent: Optional[str] = None
@@ -119,67 +125,54 @@ class MAIDxConversationLogger:
119
 
120
  for line in lines:
121
  stripped_line = line.strip()
122
- if not stripped_line: continue
123
-
124
- if "Starting Diagnostic Loop" in line:
125
- if current_round: current_round.end_time = datetime.now().isoformat()
126
- round_match = re.search(r'Starting Diagnostic Loop (\d+)/\d+', line)
127
- round_number = int(round_match.group(1)) if round_match else round_number + 1
128
- current_round = AgentConversation(round_number=round_number, start_time=datetime.now().isoformat())
129
- session.conversations.append(current_round)
130
- continue
131
 
 
132
  if "Agent Name" in line and ("╭" in line or "┌" in line):
133
- agent_match = re.search(r'Agent Name (.*?) \[', line)
 
 
 
 
 
 
 
 
134
  if agent_match:
135
  current_agent = agent_match.group(1).strip()
136
  in_agent_output = True
137
  buffer = []
138
  continue
139
 
 
140
  if in_agent_output and ("╰" in line or "└" in line):
141
  if buffer and current_agent and current_round:
142
  self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
143
  in_agent_output, current_agent, buffer = False, None, []
144
  continue
145
 
 
146
  if in_agent_output:
 
147
  clean_line = re.sub(r'^[│|]\s*|\s*[│|]\s*$', '', line)
148
- if clean_line: buffer.append(clean_line)
149
  continue
150
-
151
- if " | INFO " in line and current_round:
152
- info_match = re.search(r'mai_dx\.main:([^:]+):.*? - (.*)', line)
153
- if info_match:
154
- content = info_match.group(2).strip()
155
- if "Panel decision:" in content:
156
- decision_match = re.search(r'Panel decision: (\w+) -> (.*)', content)
157
- if decision_match: current_round.decision = f"{decision_match.group(1)}: {decision_match.group(2)}"
158
- elif "Current cost:" in content:
159
- cost_match = re.search(r'Current cost: \$(\d+(?:\.\d+)?)', content)
160
- if cost_match: current_round.cost_incurred = float(cost_match.group(1))
161
 
162
- if current_round: current_round.end_time = datetime.now().isoformat()
 
163
 
164
  def _add_agent_message(self, conversation: AgentConversation, agent_name: str, content: str):
165
  message_type = "output"
 
166
  formatted_content = content.strip()
167
 
168
- if "Structured Output - Attempting Function Call Execution" in content:
 
169
  message_type = "function_call"
170
- func_name_match = re.search(r"name:\s*(\w+)", content)
171
- func_name = func_name_match.group(1) if func_name_match else "unknown"
172
- args_match = re.search(r"arguments:\s*(\{.*\})", content, re.DOTALL)
173
- formatted_content = f"🤖 **Дія:** `{func_name}`\n\n"
174
- if args_match:
175
- try:
176
- args_data = json.loads(args_match.group(1))
177
- formatted_content += f"```json\n{json.dumps(args_data, indent=2, ensure_ascii=False)}\n```"
178
- except json.JSONDecodeError:
179
- formatted_content += "Помилка парсингу JSON."
180
- else:
181
- formatted_content += "Аргументи не знайдено."
182
-
183
  message = AgentMessage(
184
  datetime.now().isoformat(), agent_name, message_type, formatted_content, {'raw_content': content}
185
  )
 
1
  #!/usr/bin/env python3
2
  """
3
  Покращений модуль для повного логування діагностичних сесій MAI-DX
4
+ з детальним захопленням розмов між агентами. (ВЕРСІЯ З ВИПРАВЛЕНИМ ПАРСЕРОМ)
5
  """
6
  import os
7
  import io
 
64
 
65
  def write(self, text):
66
  self.captured_output.write(text)
67
+ # Уникаємо подвійного виводу в консоль, якщо Gradio робить це сам
68
+ # self.original_stdout.write(text)
69
 
70
  def flush(self):
71
  self.original_stdout.flush()
 
96
 
97
  def finalize_and_save_session(self, session: DiagnosisSession, result: Any, raw_output: str, duration: float) -> DiagnosisSession:
98
  session.raw_output = raw_output
99
+ # Головний виклик, що парсить лог
100
  self._parse_captured_output(session, raw_output)
101
 
102
  session.diagnosis = getattr(result, 'final_diagnosis', 'N/A')
 
112
  return session
113
 
114
  def _parse_captured_output(self, session: DiagnosisSession, captured_text: str):
115
+ """
116
+ FIXED: Повністю перероблений, більш надійний парсер, який не залежить
117
+ від рядка "Starting Diagnostic Loop".
118
+ """
119
  lines = captured_text.split('\n')
120
  current_round: Optional[AgentConversation] = None
121
  current_agent: Optional[str] = None
 
125
 
126
  for line in lines:
127
  stripped_line = line.strip()
 
 
 
 
 
 
 
 
 
128
 
129
+ # Визначаємо початок блоку агента
130
  if "Agent Name" in line and ("╭" in line or "┌" in line):
131
+ # Якщо це перший агент у раунді, створюємо новий раунд
132
+ if not current_round or in_agent_output: # in_agent_output check for nested blocks
133
+ round_number += 1
134
+ if current_round:
135
+ current_round.end_time = datetime.now().isoformat()
136
+ current_round = AgentConversation(round_number=round_number, start_time=datetime.now().isoformat())
137
+ session.conversations.append(current_round)
138
+
139
+ agent_match = re.search(r'Agent Name (.*?) (?:\[|\s)', line)
140
  if agent_match:
141
  current_agent = agent_match.group(1).strip()
142
  in_agent_output = True
143
  buffer = []
144
  continue
145
 
146
+ # Визначаємо кінець блоку агента
147
  if in_agent_output and ("╰" in line or "└" in line):
148
  if buffer and current_agent and current_round:
149
  self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
150
  in_agent_output, current_agent, buffer = False, None, []
151
  continue
152
 
153
+ # Збираємо вміст блоку
154
  if in_agent_output:
155
+ # Очищуємо рядки від символів рамок
156
  clean_line = re.sub(r'^[│|]\s*|\s*[│|]\s*$', '', line)
157
+ buffer.append(clean_line)
158
  continue
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ if current_round:
161
+ current_round.end_time = datetime.now().isoformat()
162
 
163
  def _add_agent_message(self, conversation: AgentConversation, agent_name: str, content: str):
164
  message_type = "output"
165
+ # Видаляємо зайві порожні рядки на початку та в кінці
166
  formatted_content = content.strip()
167
 
168
+ # Покращуємо визначення типу повідомлення
169
+ if "Structured Output - Attempting Function Call Execution" in formatted_content:
170
  message_type = "function_call"
171
+ elif "Score:" in formatted_content and "Justification:" in formatted_content:
172
+ message_type = "judgement"
173
+ elif "No tests have been proposed" in formatted_content:
174
+ message_type = "status_update"
175
+
 
 
 
 
 
 
 
 
176
  message = AgentMessage(
177
  datetime.now().isoformat(), agent_name, message_type, formatted_content, {'raw_content': content}
178
  )
updated_mai_dx_interface.py CHANGED
@@ -31,13 +31,6 @@ except ImportError as e:
31
  MAI_DX_AVAILABLE = False
32
  IMPORT_ERROR = str(e)
33
 
34
- try:
35
- import plotly.graph_objects as go
36
- import plotly.express as px
37
- PLOTLY_AVAILABLE = True
38
- except ImportError:
39
- PLOTLY_AVAILABLE = False
40
-
41
  class UpdatedMAIDXInterface:
42
  def __init__(self):
43
  self.conversation_logger = MAIDxConversationLogger("mai_dx_logs")
@@ -59,8 +52,8 @@ class UpdatedMAIDXInterface:
59
 
60
  def diagnose_with_full_logging(
61
  self, case_name: str, patient_info: str, mode: str, budget: int, max_iterations: int,
62
- model_name: str, expected_diagnosis: str = "", enable_logging: bool = True,
63
- progress=gr.Progress()
64
  ):
65
  if not MAI_DX_AVAILABLE: return self._format_error(f"❌ MAI-DX недоступний: {IMPORT_ERROR}")
66
  if not patient_info.strip(): return self._format_error("❌ Введіть інформацію про пацієнта")
@@ -74,7 +67,6 @@ class UpdatedMAIDXInterface:
74
  progress(0.3, desc="🔍 Запуск діагностики...")
75
  start_time = time.time()
76
 
77
- raw_output = ""
78
  with EnhancedOutputCapture() as capture:
79
  result = orchestrator.run(patient_info, patient_info, expected_diagnosis or "Unknown")
80
  raw_output = capture.get_value()
@@ -90,11 +82,10 @@ class UpdatedMAIDXInterface:
90
  main_result_str = self._format_main_result(session)
91
  detailed_analysis_str = self._format_detailed_analysis(session)
92
  recommendations_str = self._generate_enhanced_recommendations(result, expected_diagnosis)
93
- agent_plot_fig = self._create_agent_activity_chart(session.conversations)
94
- raw_log_str = f"📝 Сесія: {session.case_id}\n\n" + raw_output
95
  structured_conv_str = self._format_structured_conversations(session.conversations)
96
 
97
- return main_result_str, detailed_analysis_str, recommendations_str, agent_plot_fig, None, raw_log_str, structured_conv_str
98
 
99
  except Exception as e:
100
  import traceback
@@ -102,39 +93,39 @@ class UpdatedMAIDXInterface:
102
  return self._format_error(error_msg)
103
 
104
  def _format_structured_conversations(self, conversations: List[AgentConversation]) -> str:
105
- if not conversations: return "📭 Розмови не знайдено"
106
- output = "## 💬 Структуровані розмови агентів\n\n"
107
  for conv in conversations:
108
- output += f"### 🔄 Раунд {conv.round_number}\n"
109
  for msg in conv.messages:
110
- output += f"**{msg.agent_name}** `[{msg.message_type}]`:\n"
111
- output += f"```\n{msg.content}\n```\n"
112
  return output
113
 
114
- def _create_agent_activity_chart(self, conversations: List[AgentConversation]) -> Optional[go.Figure]:
115
- if not PLOTLY_AVAILABLE or not conversations: return None
 
 
116
  agent_activity = {}
117
  for conv in conversations:
118
  for msg in conv.messages:
119
  agent_activity[msg.agent_name] = agent_activity.get(msg.agent_name, 0) + 1
120
- if not agent_activity: return None
 
 
121
 
122
- try:
123
- colors = {'Dr. Hypothesis': '#3498db', 'Dr. Test-Chooser': '#e74c3c', 'Dr. Challenger': '#f39c12', 'Dr. Stewardship': '#27ae60', 'Dr. Checklist': '#9b59b6', 'Consensus Coordinator': '#34495e', 'Gatekeeper': '#16a085', 'Judge': '#c0392b'}
124
- agent_colors = [colors.get(agent, '#95a5a6') for agent in agent_activity.keys()]
125
- fig = go.Figure(go.Bar(x=list(agent_activity.keys()), y=list(agent_activity.values()), name='Взаємодії', marker_color=agent_colors))
126
- fig.update_layout(title='🤖 Активність агентів', height=400, showlegend=False, template='plotly_white')
127
- fig.update_xaxes(tickangle=-45)
128
- return fig
129
- except Exception as e:
130
- print(f"Помилка візуалізації: {e}")
131
- return None
132
 
133
  def _format_main_result(self, session: DiagnosisSession) -> str:
134
  efficiency = ((session.budget - session.cost) / session.budget * 100) if session.budget > 0 else 0
135
  confidence_emoji = "🎯" if session.confidence >= 4.0 else "👍" if session.confidence >= 3.0 else "⚠️"
136
 
137
- # ПОВЕРНУТО ПОВНИЙ ФОРМАТ РЕЗУЛЬТАТІВ
138
  return f"""
139
  ## 🏥 Результати MAI-DX Діагностики
140
  ### 📋 Основна інформація
@@ -185,16 +176,13 @@ class UpdatedMAIDXInterface:
185
  🔴 **Цей діагноз згенеровано ШІ і НЕ замінює професійну медичну консультацію.**
186
  """
187
 
188
- def _format_error(self, msg: str) -> Tuple[str, str, str, None, None, str, str]:
189
- return (msg, "", "", None, None, msg, "")
190
 
191
  def load_sample_case(self, key: str) -> Tuple[str, str, str]:
192
  case = self.sample_cases.get(key, {})
193
  return case.get("info", ""), key, case.get("expected", "")
194
 
195
- def get_session_details(self, case_id: str):
196
- return f"Аналіз сесії {case_id} ще не реалізовано."
197
-
198
  def create_updated_gradio_interface():
199
  interface = UpdatedMAIDXInterface()
200
  with gr.Blocks(title="🏥 MAI-DX Enhanced Platform", theme=gr.themes.Soft(primary_hue="blue")) as demo:
@@ -210,13 +198,10 @@ def create_updated_gradio_interface():
210
  expected_diagnosis = gr.Textbox(label="🎯 Очікуваний діагноз (англ.)", lines=2)
211
  with gr.Column(scale=1):
212
  gr.Markdown("### ⚙️ Налаштування")
213
- # ПОВЕРНУТО ПОВНИЙ СПИСОК РЕЖИМІВ
214
  mode = gr.Radio(["instant", "question_only", "budgeted", "no_budget", "ensemble"], label="🔧 Режим", value="budgeted")
215
  budget = gr.Slider(500, 10000, 3000, step=500, label="💵 Бюджет ($)")
216
  max_iterations = gr.Slider(1, 15, 8, step=1, label="🔄 Макс. ітерацій")
217
- # ПОВЕРНУТО ПОВНИЙ СПИСОК МОДЕЛЕЙ
218
  model_name = gr.Dropdown(["gemini/gemini-2.5-flash", "gpt-4o", "claude-3-5-sonnet", "gpt-4", "gpt-4-turbo"], label="🤖 LLM Модель", value="gemini/gemini-2.5-flash")
219
- enable_logging = gr.Checkbox(label="📝 Повне логування розмов", value=True)
220
  diagnose_btn = gr.Button("🚀 Запустити діагностику", variant="primary", size="lg")
221
 
222
  sample_selector.change(interface.load_sample_case, inputs=sample_selector, outputs=[patient_info, case_name, expected_diagnosis])
@@ -229,27 +214,26 @@ def create_updated_gradio_interface():
229
  with gr.Column(scale=1):
230
  recommendations = gr.Markdown()
231
 
232
- # Повертаємо два слоти для графіків, як було в оригіналі
233
- with gr.Row():
234
- metrics_plot = gr.Plot(label="📈 Метрики процесу")
235
- agent_activity_plot = gr.Plot(label="🤖 Активність агентів")
236
-
237
  with gr.Row():
238
- raw_logs = gr.Textbox(label="💬 Сирий вивід", lines=15, interactive=False, show_copy_button=True)
239
- structured_conversations = gr.Markdown(label="🗣️ Структуровані розмови")
 
 
240
 
241
  diagnose_btn.click(
242
  interface.diagnose_with_full_logging,
243
- inputs=[case_name, patient_info, mode, budget, max_iterations, model_name, expected_diagnosis, enable_logging],
244
- outputs=[main_result, detailed_analysis, recommendations, agent_activity_plot, metrics_plot, raw_logs, structured_conversations]
 
 
245
  )
246
 
247
  with gr.Tab("📊 Аналіз логів"):
248
  gr.Markdown("### 🔍 Аналіз збережених діагностичних сесій")
249
- # ... (код для аналізу логів) ...
250
 
251
  return demo
252
 
253
  if __name__ == "__main__":
254
  demo = create_updated_gradio_interface()
255
- demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
 
31
  MAI_DX_AVAILABLE = False
32
  IMPORT_ERROR = str(e)
33
 
 
 
 
 
 
 
 
34
  class UpdatedMAIDXInterface:
35
  def __init__(self):
36
  self.conversation_logger = MAIDxConversationLogger("mai_dx_logs")
 
52
 
53
  def diagnose_with_full_logging(
54
  self, case_name: str, patient_info: str, mode: str, budget: int, max_iterations: int,
55
+ model_name: str, expected_diagnosis: str = "",
56
+ progress=gr.Progress(track_tqdm=True) # Додаємо track_tqdm для кращої інтеграції
57
  ):
58
  if not MAI_DX_AVAILABLE: return self._format_error(f"❌ MAI-DX недоступний: {IMPORT_ERROR}")
59
  if not patient_info.strip(): return self._format_error("❌ Введіть інформацію про пацієнта")
 
67
  progress(0.3, desc="🔍 Запуск діагностики...")
68
  start_time = time.time()
69
 
 
70
  with EnhancedOutputCapture() as capture:
71
  result = orchestrator.run(patient_info, patient_info, expected_diagnosis or "Unknown")
72
  raw_output = capture.get_value()
 
82
  main_result_str = self._format_main_result(session)
83
  detailed_analysis_str = self._format_detailed_analysis(session)
84
  recommendations_str = self._generate_enhanced_recommendations(result, expected_diagnosis)
85
+ agent_report_str = self._create_agent_activity_report(session.conversations)
 
86
  structured_conv_str = self._format_structured_conversations(session.conversations)
87
 
88
+ return main_result_str, detailed_analysis_str, recommendations_str, agent_report_str, structured_conv_str
89
 
90
  except Exception as e:
91
  import traceback
 
93
  return self._format_error(error_msg)
94
 
95
  def _format_structured_conversations(self, conversations: List[AgentConversation]) -> str:
96
+ if not conversations: return "### 🗣️ Розмови агентів\n\n*Розмови не знайдено в логах. Можливо, діагноз було поставлено миттєво.*"
97
+ output = "### 🗣️ Розмови агентів\n\n"
98
  for conv in conversations:
99
+ output += f"#### 🔄 Раунд {conv.round_number}\n"
100
  for msg in conv.messages:
101
+ output += f"<details><summary><strong>{msg.agent_name}</strong> <code>[{msg.message_type}]</code></summary>\n\n"
102
+ output += f"```\n{msg.content.strip()}\n```\n\n</details>\n"
103
  return output
104
 
105
+ def _create_agent_activity_report(self, conversations: List[AgentConversation]) -> str:
106
+ if not conversations:
107
+ return "### 🤖 Активність агентів\n\n*Дані відсутні.*"
108
+
109
  agent_activity = {}
110
  for conv in conversations:
111
  for msg in conv.messages:
112
  agent_activity[msg.agent_name] = agent_activity.get(msg.agent_name, 0) + 1
113
+
114
+ if not agent_activity:
115
+ return "### 🤖 Активність агентів\n\n*Повідомлення від агентів не знайдено.*"
116
 
117
+ report_md = "### 🤖 Активність агентів\n\n"
118
+ sorted_activity = sorted(agent_activity.items(), key=lambda item: item[1], reverse=True)
119
+
120
+ for agent, count in sorted_activity:
121
+ report_md += f"- **{agent}**: {count} повідомлень\n"
122
+
123
+ return report_md
 
 
 
124
 
125
  def _format_main_result(self, session: DiagnosisSession) -> str:
126
  efficiency = ((session.budget - session.cost) / session.budget * 100) if session.budget > 0 else 0
127
  confidence_emoji = "🎯" if session.confidence >= 4.0 else "👍" if session.confidence >= 3.0 else "⚠️"
128
 
 
129
  return f"""
130
  ## 🏥 Результати MAI-DX Діагностики
131
  ### 📋 Основна інформація
 
176
  🔴 **Цей діагноз згенеровано ШІ і НЕ замінює професійну медичну консультацію.**
177
  """
178
 
179
+ def _format_error(self, msg: str) -> Tuple[str, str, str, str, str]:
180
+ return (msg, "", "", "", "")
181
 
182
  def load_sample_case(self, key: str) -> Tuple[str, str, str]:
183
  case = self.sample_cases.get(key, {})
184
  return case.get("info", ""), key, case.get("expected", "")
185
 
 
 
 
186
  def create_updated_gradio_interface():
187
  interface = UpdatedMAIDXInterface()
188
  with gr.Blocks(title="🏥 MAI-DX Enhanced Platform", theme=gr.themes.Soft(primary_hue="blue")) as demo:
 
198
  expected_diagnosis = gr.Textbox(label="🎯 Очікуваний діагноз (англ.)", lines=2)
199
  with gr.Column(scale=1):
200
  gr.Markdown("### ⚙️ Налаштування")
 
201
  mode = gr.Radio(["instant", "question_only", "budgeted", "no_budget", "ensemble"], label="🔧 Режим", value="budgeted")
202
  budget = gr.Slider(500, 10000, 3000, step=500, label="💵 Бюджет ($)")
203
  max_iterations = gr.Slider(1, 15, 8, step=1, label="🔄 Макс. ітерацій")
 
204
  model_name = gr.Dropdown(["gemini/gemini-2.5-flash", "gpt-4o", "claude-3-5-sonnet", "gpt-4", "gpt-4-turbo"], label="🤖 LLM Модель", value="gemini/gemini-2.5-flash")
 
205
  diagnose_btn = gr.Button("🚀 Запустити діагностику", variant="primary", size="lg")
206
 
207
  sample_selector.change(interface.load_sample_case, inputs=sample_selector, outputs=[patient_info, case_name, expected_diagnosis])
 
214
  with gr.Column(scale=1):
215
  recommendations = gr.Markdown()
216
 
217
+ gr.Markdown("---")
 
 
 
 
218
  with gr.Row():
219
+ with gr.Column(scale=1):
220
+ agent_activity_report = gr.Markdown(label="🤖 Активність агентів")
221
+ with gr.Column(scale=2):
222
+ structured_conversations = gr.Markdown(label="🗣️ Структуровані розмови")
223
 
224
  diagnose_btn.click(
225
  interface.diagnose_with_full_logging,
226
+ inputs=[case_name, patient_info, mode, budget, max_iterations, model_name, expected_diagnosis],
227
+ outputs=[main_result, detailed_analysis, recommendations, agent_activity_report, structured_conversations],
228
+ # FIX: Додаємо параметр для відображення прогрес-бару
229
+ show_progress="full"
230
  )
231
 
232
  with gr.Tab("📊 Аналіз логів"):
233
  gr.Markdown("### 🔍 Аналіз збережених діагностичних сесій")
 
234
 
235
  return demo
236
 
237
  if __name__ == "__main__":
238
  demo = create_updated_gradio_interface()
239
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)