Spaces:
Sleeping
Sleeping
File size: 8,344 Bytes
143d94c cb898e1 143d94c 0091076 143d94c 0091076 143d94c 0091076 143d94c 0091076 143d94c 0091076 143d94c 0091076 cb898e1 143d94c 0091076 143d94c 0091076 143d94c 0091076 143d94c 0091076 cb898e1 0091076 143d94c 0091076 143d94c 0091076 cb898e1 143d94c 20d0a16 143d94c 20d0a16 cb898e1 143d94c cb898e1 143d94c 0091076 143d94c 20d0a16 cb898e1 0091076 143d94c 0091076 20d0a16 cb898e1 0091076 cb898e1 0091076 cb898e1 143d94c 20d0a16 cb898e1 20d0a16 0091076 cb898e1 0091076 20d0a16 cb898e1 20d0a16 cb898e1 143d94c 0091076 143d94c 0091076 143d94c 0091076 143d94c 0091076 143d94c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
#!/usr/bin/env python3
"""
Покращений модуль для повного логування діагностичних сесій MAI-DX
з детальним захопленням розмов між агентами. (ВЕРСІЯ З ВИПРАВЛЕНИМ ПАРСЕРОМ)
"""
import os
import io
import sys
import json
import time
import uuid
import re
import logging
from datetime import datetime
from typing import List, Dict, Any, Optional, Callable
from dataclasses import dataclass, asdict, field
@dataclass
class AgentMessage:
timestamp: str
agent_name: str
message_type: str
content: str
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class AgentConversation:
round_number: int
start_time: str
end_time: Optional[str] = None
messages: List[AgentMessage] = field(default_factory=list)
decision: Optional[str] = None
cost_incurred: float = 0.0
@dataclass
class DiagnosisSession:
case_id: str
timestamp: str
case_name: str
patient_info: str
mode: str
budget: int = 0
diagnosis: str = "N/A"
confidence: float = 0.0
cost: float = 0.0
iterations: int = 0
duration: float = 0.0
status: str = "In Progress"
reasoning: str = "N/A"
conversations: List[AgentConversation] = field(default_factory=list)
raw_output: str = ""
class EnhancedOutputCapture:
def __init__(self):
self.captured_output = io.StringIO()
self.original_stdout = sys.stdout
def __enter__(self):
sys.stdout = self
return self
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout = self.original_stdout
def write(self, text):
self.captured_output.write(text)
# Уникаємо подвійного виводу в консоль, якщо Gradio робить це сам
# self.original_stdout.write(text)
def flush(self):
self.original_stdout.flush()
def get_value(self):
return self._strip_ansi_codes(self.captured_output.getvalue())
@staticmethod
def _strip_ansi_codes(text):
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
return ansi_escape.sub('', text)
class MAIDxConversationLogger:
def __init__(self, log_dir: str = "mai_dx_logs"):
self.log_dir = log_dir
os.makedirs(self.log_dir, exist_ok=True)
def create_session(self, case_name: str, patient_info: str, mode: str, budget: int) -> DiagnosisSession:
case_id = f"case_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}"
return DiagnosisSession(
case_id=case_id,
timestamp=datetime.now().isoformat(),
case_name=case_name,
patient_info=patient_info,
mode=mode,
budget=budget
)
def finalize_and_save_session(self, session: DiagnosisSession, result: Any, raw_output: str, duration: float) -> DiagnosisSession:
session.raw_output = raw_output
# Головний виклик, що парсить лог
self._parse_captured_output(session, raw_output)
session.diagnosis = getattr(result, 'final_diagnosis', 'N/A')
session.confidence = getattr(result, 'accuracy_score', 0.0)
session.cost = getattr(result, 'total_cost', 0.0)
session.reasoning = getattr(result, 'accuracy_reasoning', 'N/A')
session.iterations = len(session.conversations)
session.status = "✅ Успішно" if session.confidence >= 3.0 else "⚠️ Потребує перегляду"
session.duration = duration
self._save_session_to_file(session)
self.export_conversation_html(session)
return session
def _parse_captured_output(self, session: DiagnosisSession, captured_text: str):
"""
FIXED: Повністю перероблений, більш надійний парсер, який не залежить
від рядка "Starting Diagnostic Loop".
"""
lines = captured_text.split('\n')
current_round: Optional[AgentConversation] = None
current_agent: Optional[str] = None
buffer: List[str] = []
in_agent_output = False
round_number = 0
for line in lines:
stripped_line = line.strip()
# Визначаємо початок блоку агента
if "Agent Name" in line and ("╭" in line or "┌" in line):
# Якщо це перший агент у раунді, створюємо новий раунд
if not current_round or in_agent_output: # in_agent_output check for nested blocks
round_number += 1
if current_round:
current_round.end_time = datetime.now().isoformat()
current_round = AgentConversation(round_number=round_number, start_time=datetime.now().isoformat())
session.conversations.append(current_round)
agent_match = re.search(r'Agent Name (.*?) (?:\[|\s)', line)
if agent_match:
current_agent = agent_match.group(1).strip()
in_agent_output = True
buffer = []
continue
# Визначаємо кінець блоку агента
if in_agent_output and ("╰" in line or "└" in line):
if buffer and current_agent and current_round:
self._add_agent_message(current_round, current_agent, '\n'.join(buffer))
in_agent_output, current_agent, buffer = False, None, []
continue
# Збираємо вміст блоку
if in_agent_output:
# Очищуємо рядки від символів рамок
clean_line = re.sub(r'^[│|]\s*|\s*[│|]\s*$', '', line)
buffer.append(clean_line)
continue
if current_round:
current_round.end_time = datetime.now().isoformat()
def _add_agent_message(self, conversation: AgentConversation, agent_name: str, content: str):
message_type = "output"
# Видаляємо зайві порожні рядки на початку та в кінці
formatted_content = content.strip()
# Покращуємо визначення типу повідомлення
if "Structured Output - Attempting Function Call Execution" in formatted_content:
message_type = "function_call"
elif "Score:" in formatted_content and "Justification:" in formatted_content:
message_type = "judgement"
elif "No tests have been proposed" in formatted_content:
message_type = "status_update"
message = AgentMessage(
datetime.now().isoformat(), agent_name, message_type, formatted_content, {'raw_content': content}
)
conversation.messages.append(message)
def _save_session_to_file(self, session: DiagnosisSession):
file_path = os.path.join(self.log_dir, f"{session.case_id}.json")
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(asdict(session), f, ensure_ascii=False, indent=2)
raw_output_path = os.path.join(self.log_dir, f"{session.case_id}_raw.txt")
with open(raw_output_path, 'w', encoding='utf-8') as f:
f.write(session.raw_output)
def export_conversation_html(self, session: DiagnosisSession) -> str:
html_path = os.path.join(self.log_dir, f"{session.case_id}_conversation.html")
html_content = f"<html><head><title>Log - {session.case_id}</title></head><body>"
html_content += f"<h1>Session: {session.case_id}</h1>"
for conv in session.conversations:
html_content += f"<h2>Round {conv.round_number}</h2>"
for msg in conv.messages:
html_content += f"<div><b>{msg.agent_name}</b> [{msg.message_type}]:<pre>{msg.content}</pre></div>"
html_content += "</body></html>"
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_content)
return html_path |