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

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

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