Spaces:
Sleeping
Sleeping
Виправлено MAI-DX Gradio Interface для сумісності з новими версіями Gradio, додано покращене логування та аналітику, оновлено документацію та приклади використання. Додано підтримку Plotly для візуалізації метрик.
Browse files- README.md +10 -152
- README_OLD.md +153 -0
- agent_conversation_logger.py +254 -0
- lmai_dx_interface_log.py +634 -469
- mai_dx_logs/case_20250710_173828_5d4912.json +27 -0
- requirements.txt +1 -0
README.md
CHANGED
@@ -1,153 +1,11 @@
|
|
1 |
-
# MAI Diagnostic Orchestrator (MAI-DxO)
|
2 |
-
|
3 |
-
> **An open-source implementation of Microsoft Research's "Sequential Diagnosis with Language Models" paper, built with the Swarms AI framework.**
|
4 |
-
|
5 |
-
MAI-DxO (MAI Diagnostic Orchestrator) is a sophisticated AI-powered diagnostic system that simulates a virtual panel of physician-agents to perform iterative medical diagnosis with cost-effectiveness optimization. This implementation faithfully reproduces the methodology described in the Microsoft Research paper while providing additional features and flexibility.
|
6 |
-
|
7 |
-
[](https://arxiv.org/abs/2506.22405)
|
8 |
-
[](LICENSE)
|
9 |
-
[](https://python.org)
|
10 |
-
|
11 |
-
## ✨ Key Features
|
12 |
-
|
13 |
-
- **8 AI Physician Agents**: Specialized roles for comprehensive diagnosis.
|
14 |
-
- **5 Operational Modes**: Instant, question-only, budgeted, no-budget, and ensemble modes.
|
15 |
-
- **Cost Tracking**: Real-time budget monitoring with costs for 25+ medical tests.
|
16 |
-
- **Clinical Evaluation**: 5-point accuracy scoring with detailed feedback.
|
17 |
-
- **Model Agnostic**: Works with GPT, Gemini, Claude, and other leading LLMs.
|
18 |
-
- **Token-Optimized Prompts**: Ultra-compact role prompts reduce token usage and latency without sacrificing reasoning quality.
|
19 |
-
|
20 |
-
## 🚀 Quick Start
|
21 |
-
|
22 |
-
### 1. Installation
|
23 |
-
|
24 |
-
Install the package directly via pip:
|
25 |
-
|
26 |
-
```bash
|
27 |
-
pip install mai-dx
|
28 |
-
```
|
29 |
-
|
30 |
-
Or, for development, clone the repository and install the requirements:
|
31 |
-
|
32 |
-
```bash
|
33 |
-
git clone https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator.git
|
34 |
-
cd Open-MAI-Dx-Orchestrator
|
35 |
-
pip install -r requirements.txt
|
36 |
-
```
|
37 |
-
|
38 |
-
### 2. Environment Setup
|
39 |
-
|
40 |
-
Create a `.env` file in your project root and add your API keys:
|
41 |
-
|
42 |
-
```txt
|
43 |
-
OPENAI_API_KEY="Your OpenAI API key"
|
44 |
-
GEMINI_API_KEY="Your Gemini API key"
|
45 |
-
ANTHROPIC_API_KEY="Your Anthropic API key"
|
46 |
-
```
|
47 |
-
|
48 |
-
### 3. Basic Usage
|
49 |
-
|
50 |
-
```python
|
51 |
-
from mai_dx import MaiDxOrchestrator
|
52 |
-
|
53 |
-
# Create the orchestrator (defaults to a capable model)
|
54 |
-
orchestrator = MaiDxOrchestrator()
|
55 |
-
|
56 |
-
# Run a diagnosis
|
57 |
-
result = orchestrator.run(
|
58 |
-
initial_case_info="29-year-old woman with sore throat and peritonsillar swelling...",
|
59 |
-
full_case_details="Patient: 29-year-old female. History: Onset of sore throat...",
|
60 |
-
ground_truth_diagnosis="Embryonal rhabdomyosarcoma of the pharynx"
|
61 |
-
)
|
62 |
-
|
63 |
-
# Print the results
|
64 |
-
print(f"Final Diagnosis: {result.final_diagnosis}")
|
65 |
-
print(f"Accuracy: {result.accuracy_score}/5.0")
|
66 |
-
print(f"Total Cost: ${result.total_cost:,.2f}")
|
67 |
-
```
|
68 |
-
|
69 |
-
## ⚙️ Advanced Usage & Configuration
|
70 |
-
|
71 |
-
Customize the orchestrator's model, budget, and operational mode.
|
72 |
-
|
73 |
-
```python
|
74 |
-
from mai_dx import MaiDxOrchestrator
|
75 |
-
|
76 |
-
# Configure with a specific model and budget
|
77 |
-
orchestrator = MaiDxOrchestrator(
|
78 |
-
model_name="gemini/gemini-2.5-flash", # or "gpt-4", "claude-3-5-sonnet"
|
79 |
-
max_iterations=10,
|
80 |
-
initial_budget=3000,
|
81 |
-
mode="budgeted" # Other modes: "instant", "question_only", "no_budget"
|
82 |
-
)
|
83 |
-
|
84 |
-
# Run the diagnosis
|
85 |
-
# ...
|
86 |
-
```
|
87 |
-
|
88 |
-
## 🏥 How It Works: The Virtual Physician Panel
|
89 |
-
|
90 |
-
MAI-DxO employs a multi-agent system where each agent has a specific role:
|
91 |
-
|
92 |
-
- **🧠 Dr. Hypothesis**: Maintains the differential diagnosis.
|
93 |
-
- **🔬 Dr. Test-Chooser**: Selects the most cost-effective diagnostic tests.
|
94 |
-
- **🤔 Dr. Challenger**: Prevents cognitive biases and diagnostic errors.
|
95 |
-
- **💰 Dr. Stewardship**: Ensures cost-effective care.
|
96 |
-
- **✅ Dr. Checklist**: Performs quality control checks.
|
97 |
-
- **🤝 Consensus Coordinator**: Synthesizes panel decisions.
|
98 |
-
- **🔑 Gatekeeper**: Acts as the clinical information oracle.
|
99 |
-
- **⚖️ Judge**: Evaluates the final diagnostic accuracy.
|
100 |
-
|
101 |
-
|
102 |
-
## Documentation
|
103 |
-
|
104 |
-
Learn more about this repository [with the docs](DOCS.md)
|
105 |
-
|
106 |
-
## 🤝 Contributing
|
107 |
-
|
108 |
-
We welcome contributions! Please feel free to open an issue or submit a pull request.
|
109 |
-
|
110 |
-
## 📄 License
|
111 |
-
|
112 |
-
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
113 |
-
|
114 |
-
## 📚 Citation
|
115 |
-
|
116 |
-
If you use this work in your research, please cite both the original paper and this software implementation.
|
117 |
-
|
118 |
-
```bibtex
|
119 |
-
@misc{nori2025sequentialdiagnosislanguagemodels,
|
120 |
-
title={Sequential Diagnosis with Language Models},
|
121 |
-
author={Harsha Nori and Mayank Daswani and Christopher Kelly and Scott Lundberg and Marco Tulio Ribeiro and Marc Wilson and Xiaoxuan Liu and Viknesh Sounderajah and Jonathan Carlson and Matthew P Lungren and Bay Gross and Peter Hames and Mustafa Suleyman and Dominic King and Eric Horvitz},
|
122 |
-
year={2025},
|
123 |
-
eprint={2506.22405},
|
124 |
-
archivePrefix={arXiv},
|
125 |
-
primaryClass={cs.CL},
|
126 |
-
url={https://arxiv.org/abs/2506.22405},
|
127 |
-
}
|
128 |
-
|
129 |
-
@software{mai_dx_orchestrator,
|
130 |
-
title={Open-MAI-Dx-Orchestrator: An Open Source Implementation of Sequential Diagnosis with Language Models},
|
131 |
-
author={The-Swarm-Corporation},
|
132 |
-
year={2025},
|
133 |
-
url={https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator.git}
|
134 |
-
}
|
135 |
-
```
|
136 |
-
|
137 |
-
## 🔗 Related Work
|
138 |
-
|
139 |
-
- [Original Paper](https://arxiv.org/abs/2506.22405) - Sequential Diagnosis with Language Models
|
140 |
-
- [Swarms Framework](https://github.com/kyegomez/swarms) - Multi-agent AI orchestration
|
141 |
-
- [Microsoft Research](https://www.microsoft.com/en-us/research/) - Original research institution
|
142 |
-
|
143 |
-
## 📞 Support
|
144 |
-
|
145 |
-
- **Issues**: [GitHub Issues](https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator/issues)
|
146 |
-
- **Discussions**: [GitHub Discussions](https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator/discussions)
|
147 |
-
- **Documentation**: [Full Documentation](https://docs.swarms.world)
|
148 |
-
|
149 |
---
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: GUI MAI-DxO
|
3 |
+
emoji: 🔥
|
4 |
+
colorFrom: indigo
|
5 |
+
colorTo: green
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 5.36.2
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
short_description: MAI-DX Enhanced Research Platform
|
11 |
+
---
|
README_OLD.md
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MAI Diagnostic Orchestrator (MAI-DxO)
|
2 |
+
|
3 |
+
> **An open-source implementation of Microsoft Research's "Sequential Diagnosis with Language Models" paper, built with the Swarms AI framework.**
|
4 |
+
|
5 |
+
MAI-DxO (MAI Diagnostic Orchestrator) is a sophisticated AI-powered diagnostic system that simulates a virtual panel of physician-agents to perform iterative medical diagnosis with cost-effectiveness optimization. This implementation faithfully reproduces the methodology described in the Microsoft Research paper while providing additional features and flexibility.
|
6 |
+
|
7 |
+
[](https://arxiv.org/abs/2506.22405)
|
8 |
+
[](LICENSE)
|
9 |
+
[](https://python.org)
|
10 |
+
|
11 |
+
## ✨ Key Features
|
12 |
+
|
13 |
+
- **8 AI Physician Agents**: Specialized roles for comprehensive diagnosis.
|
14 |
+
- **5 Operational Modes**: Instant, question-only, budgeted, no-budget, and ensemble modes.
|
15 |
+
- **Cost Tracking**: Real-time budget monitoring with costs for 25+ medical tests.
|
16 |
+
- **Clinical Evaluation**: 5-point accuracy scoring with detailed feedback.
|
17 |
+
- **Model Agnostic**: Works with GPT, Gemini, Claude, and other leading LLMs.
|
18 |
+
- **Token-Optimized Prompts**: Ultra-compact role prompts reduce token usage and latency without sacrificing reasoning quality.
|
19 |
+
|
20 |
+
## 🚀 Quick Start
|
21 |
+
|
22 |
+
### 1. Installation
|
23 |
+
|
24 |
+
Install the package directly via pip:
|
25 |
+
|
26 |
+
```bash
|
27 |
+
pip install mai-dx
|
28 |
+
```
|
29 |
+
|
30 |
+
Or, for development, clone the repository and install the requirements:
|
31 |
+
|
32 |
+
```bash
|
33 |
+
git clone https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator.git
|
34 |
+
cd Open-MAI-Dx-Orchestrator
|
35 |
+
pip install -r requirements.txt
|
36 |
+
```
|
37 |
+
|
38 |
+
### 2. Environment Setup
|
39 |
+
|
40 |
+
Create a `.env` file in your project root and add your API keys:
|
41 |
+
|
42 |
+
```txt
|
43 |
+
OPENAI_API_KEY="Your OpenAI API key"
|
44 |
+
GEMINI_API_KEY="Your Gemini API key"
|
45 |
+
ANTHROPIC_API_KEY="Your Anthropic API key"
|
46 |
+
```
|
47 |
+
|
48 |
+
### 3. Basic Usage
|
49 |
+
|
50 |
+
```python
|
51 |
+
from mai_dx import MaiDxOrchestrator
|
52 |
+
|
53 |
+
# Create the orchestrator (defaults to a capable model)
|
54 |
+
orchestrator = MaiDxOrchestrator()
|
55 |
+
|
56 |
+
# Run a diagnosis
|
57 |
+
result = orchestrator.run(
|
58 |
+
initial_case_info="29-year-old woman with sore throat and peritonsillar swelling...",
|
59 |
+
full_case_details="Patient: 29-year-old female. History: Onset of sore throat...",
|
60 |
+
ground_truth_diagnosis="Embryonal rhabdomyosarcoma of the pharynx"
|
61 |
+
)
|
62 |
+
|
63 |
+
# Print the results
|
64 |
+
print(f"Final Diagnosis: {result.final_diagnosis}")
|
65 |
+
print(f"Accuracy: {result.accuracy_score}/5.0")
|
66 |
+
print(f"Total Cost: ${result.total_cost:,.2f}")
|
67 |
+
```
|
68 |
+
|
69 |
+
## ⚙️ Advanced Usage & Configuration
|
70 |
+
|
71 |
+
Customize the orchestrator's model, budget, and operational mode.
|
72 |
+
|
73 |
+
```python
|
74 |
+
from mai_dx import MaiDxOrchestrator
|
75 |
+
|
76 |
+
# Configure with a specific model and budget
|
77 |
+
orchestrator = MaiDxOrchestrator(
|
78 |
+
model_name="gemini/gemini-2.5-flash", # or "gpt-4", "claude-3-5-sonnet"
|
79 |
+
max_iterations=10,
|
80 |
+
initial_budget=3000,
|
81 |
+
mode="budgeted" # Other modes: "instant", "question_only", "no_budget"
|
82 |
+
)
|
83 |
+
|
84 |
+
# Run the diagnosis
|
85 |
+
# ...
|
86 |
+
```
|
87 |
+
|
88 |
+
## 🏥 How It Works: The Virtual Physician Panel
|
89 |
+
|
90 |
+
MAI-DxO employs a multi-agent system where each agent has a specific role:
|
91 |
+
|
92 |
+
- **🧠 Dr. Hypothesis**: Maintains the differential diagnosis.
|
93 |
+
- **🔬 Dr. Test-Chooser**: Selects the most cost-effective diagnostic tests.
|
94 |
+
- **🤔 Dr. Challenger**: Prevents cognitive biases and diagnostic errors.
|
95 |
+
- **💰 Dr. Stewardship**: Ensures cost-effective care.
|
96 |
+
- **✅ Dr. Checklist**: Performs quality control checks.
|
97 |
+
- **🤝 Consensus Coordinator**: Synthesizes panel decisions.
|
98 |
+
- **🔑 Gatekeeper**: Acts as the clinical information oracle.
|
99 |
+
- **⚖️ Judge**: Evaluates the final diagnostic accuracy.
|
100 |
+
|
101 |
+
|
102 |
+
## Documentation
|
103 |
+
|
104 |
+
Learn more about this repository [with the docs](DOCS.md)
|
105 |
+
|
106 |
+
## 🤝 Contributing
|
107 |
+
|
108 |
+
We welcome contributions! Please feel free to open an issue or submit a pull request.
|
109 |
+
|
110 |
+
## 📄 License
|
111 |
+
|
112 |
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
113 |
+
|
114 |
+
## 📚 Citation
|
115 |
+
|
116 |
+
If you use this work in your research, please cite both the original paper and this software implementation.
|
117 |
+
|
118 |
+
```bibtex
|
119 |
+
@misc{nori2025sequentialdiagnosislanguagemodels,
|
120 |
+
title={Sequential Diagnosis with Language Models},
|
121 |
+
author={Harsha Nori and Mayank Daswani and Christopher Kelly and Scott Lundberg and Marco Tulio Ribeiro and Marc Wilson and Xiaoxuan Liu and Viknesh Sounderajah and Jonathan Carlson and Matthew P Lungren and Bay Gross and Peter Hames and Mustafa Suleyman and Dominic King and Eric Horvitz},
|
122 |
+
year={2025},
|
123 |
+
eprint={2506.22405},
|
124 |
+
archivePrefix={arXiv},
|
125 |
+
primaryClass={cs.CL},
|
126 |
+
url={https://arxiv.org/abs/2506.22405},
|
127 |
+
}
|
128 |
+
|
129 |
+
@software{mai_dx_orchestrator,
|
130 |
+
title={Open-MAI-Dx-Orchestrator: An Open Source Implementation of Sequential Diagnosis with Language Models},
|
131 |
+
author={The-Swarm-Corporation},
|
132 |
+
year={2025},
|
133 |
+
url={https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator.git}
|
134 |
+
}
|
135 |
+
```
|
136 |
+
|
137 |
+
## 🔗 Related Work
|
138 |
+
|
139 |
+
- [Original Paper](https://arxiv.org/abs/2506.22405) - Sequential Diagnosis with Language Models
|
140 |
+
- [Swarms Framework](https://github.com/kyegomez/swarms) - Multi-agent AI orchestration
|
141 |
+
- [Microsoft Research](https://www.microsoft.com/en-us/research/) - Original research institution
|
142 |
+
|
143 |
+
## 📞 Support
|
144 |
+
|
145 |
+
- **Issues**: [GitHub Issues](https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator/issues)
|
146 |
+
- **Discussions**: [GitHub Discussions](https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator/discussions)
|
147 |
+
- **Documentation**: [Full Documentation](https://docs.swarms.world)
|
148 |
+
|
149 |
+
---
|
150 |
+
|
151 |
+
<p align="center">
|
152 |
+
<strong>Built with Swarms for advancing AI-powered medical diagnosis</strong>
|
153 |
+
</p>
|
agent_conversation_logger.py
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
lmai_dx_interface_log.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
|
4 |
"""
|
5 |
import os
|
6 |
import sys
|
@@ -8,7 +8,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
|
@@ -17,14 +17,15 @@ import queue
|
|
17 |
import io
|
18 |
from contextlib import redirect_stdout, redirect_stderr
|
19 |
|
20 |
-
# Налаштування
|
21 |
-
os.environ
|
22 |
-
|
23 |
-
|
24 |
-
|
|
|
|
|
25 |
warnings.filterwarnings("ignore")
|
26 |
|
27 |
-
# Завантаження змінних середовища
|
28 |
from dotenv import load_dotenv
|
29 |
load_dotenv()
|
30 |
|
@@ -42,7 +43,7 @@ def patch_rich_formatter():
|
|
42 |
|
43 |
patch_rich_formatter()
|
44 |
|
45 |
-
# Імпорт
|
46 |
try:
|
47 |
from mai_dx import MaiDxOrchestrator
|
48 |
MAI_DX_AVAILABLE = True
|
@@ -50,215 +51,138 @@ except ImportError as e:
|
|
50 |
MAI_DX_AVAILABLE = False
|
51 |
IMPORT_ERROR = str(e)
|
52 |
|
53 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
try:
|
55 |
from agent_conversation_logger import AgentConversationLogger, DiagnosisSession
|
56 |
LOGGER_AVAILABLE = True
|
57 |
except ImportError:
|
58 |
LOGGER_AVAILABLE = False
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
class AgentConversationLogger:
|
62 |
def __init__(self, *args, **kwargs):
|
63 |
-
|
64 |
def start_conversation(self, *args):
|
65 |
-
return "
|
66 |
def log_agent_message(self, *args, **kwargs):
|
67 |
pass
|
68 |
def end_conversation(self, *args):
|
69 |
-
return "
|
70 |
def list_conversations(self):
|
71 |
-
return
|
72 |
def get_conversation_summary(self):
|
73 |
-
return {"
|
|
|
|
|
|
|
|
|
74 |
|
75 |
-
class
|
76 |
-
"""
|
77 |
|
78 |
def __init__(self):
|
79 |
-
self.
|
80 |
-
self.current_agent = None
|
81 |
-
self.agent_patterns = {
|
82 |
-
'Dr. Hypothesis': ['hypothesis', 'differential', 'diagnosis'],
|
83 |
-
'Dr. Test-Chooser': ['test', 'chooser', 'lab', 'diagnostic'],
|
84 |
-
'Dr. Challenger': ['challenger', 'challenge', 'bias'],
|
85 |
-
'Dr. Stewardship': ['stewardship', 'cost', 'budget'],
|
86 |
-
'Dr. Checklist': ['checklist', 'quality', 'control'],
|
87 |
-
'Consensus Coordinator': ['consensus', 'coordinator', 'decision'],
|
88 |
-
'Gatekeeper': ['gatekeeper', 'information', 'oracle'],
|
89 |
-
'Judge': ['judge', 'evaluation', 'accuracy']
|
90 |
-
}
|
91 |
-
|
92 |
-
def parse_mai_output(self, output_text: str, agent_logger: AgentConversationLogger):
|
93 |
-
"""Парсинг виводу MAI-DX для ідентифікації агентів"""
|
94 |
-
lines = output_text.split('\n')
|
95 |
-
|
96 |
-
for line in lines:
|
97 |
-
line = line.strip()
|
98 |
-
if not line:
|
99 |
-
continue
|
100 |
-
|
101 |
-
# Пошук агентів по патернах
|
102 |
-
identified_agent = self._identify_agent(line)
|
103 |
-
|
104 |
-
if identified_agent:
|
105 |
-
self.current_agent = identified_agent
|
106 |
-
|
107 |
-
# Визначаємо тип повідомлення
|
108 |
-
message_type = self._classify_message_type(line)
|
109 |
-
|
110 |
-
# Логуємо повідомлення агента
|
111 |
-
agent_logger.log_agent_message(
|
112 |
-
agent_name=identified_agent,
|
113 |
-
message_type=message_type,
|
114 |
-
content=line,
|
115 |
-
reasoning=self._extract_reasoning(line),
|
116 |
-
confidence=self._extract_confidence(line),
|
117 |
-
cost=self._extract_cost(line)
|
118 |
-
)
|
119 |
-
|
120 |
-
def _identify_agent(self, text: str) -> Optional[str]:
|
121 |
-
"""Ідентифікація агента за текстом"""
|
122 |
-
text_lower = text.lower()
|
123 |
-
|
124 |
-
for agent, patterns in self.agent_patterns.items():
|
125 |
-
if any(pattern in text_lower for pattern in patterns):
|
126 |
-
return agent
|
127 |
-
|
128 |
-
# Пошук за прямими згадками
|
129 |
-
if '🧠' in text or 'dr. hypothesis' in text_lower:
|
130 |
-
return 'Dr. Hypothesis'
|
131 |
-
elif '🔬' in text or 'dr. test-chooser' in text_lower:
|
132 |
-
return 'Dr. Test-Chooser'
|
133 |
-
elif '🤔' in text or 'dr. challenger' in text_lower:
|
134 |
-
return 'Dr. Challenger'
|
135 |
-
elif '💰' in text or 'dr. stewardship' in text_lower:
|
136 |
-
return 'Dr. Stewardship'
|
137 |
-
elif '✅' in text or 'dr. checklist' in text_lower:
|
138 |
-
return 'Dr. Checklist'
|
139 |
-
elif '🤝' in text or 'consensus' in text_lower:
|
140 |
-
return 'Consensus Coordinator'
|
141 |
-
elif '🔑' in text or 'gatekeeper' in text_lower:
|
142 |
-
return 'Gatekeeper'
|
143 |
-
elif '⚖️' in text or 'judge' in text_lower:
|
144 |
-
return 'Judge'
|
145 |
-
|
146 |
-
return None
|
147 |
|
148 |
-
def
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
return 'analysis'
|
164 |
-
else:
|
165 |
-
return 'communication'
|
166 |
|
167 |
-
def
|
168 |
-
|
169 |
-
|
170 |
-
reasoning_patterns = [
|
171 |
-
r'because\s+(.*)',
|
172 |
-
r'due to\s+(.*)',
|
173 |
-
r'given\s+(.*)',
|
174 |
-
r'since\s+(.*)',
|
175 |
-
r'reasoning[:\s]+(.*)',
|
176 |
-
]
|
177 |
-
|
178 |
-
import re
|
179 |
-
for pattern in reasoning_patterns:
|
180 |
-
match = re.search(pattern, text, re.IGNORECASE)
|
181 |
-
if match:
|
182 |
-
return match.group(1).strip()
|
183 |
-
|
184 |
-
return ""
|
185 |
|
186 |
-
def
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
confidence_patterns = [
|
192 |
-
r'confidence[:\s]*(\d+(?:\.\d+)?)',
|
193 |
-
r'certainty[:\s]*(\d+(?:\.\d+)?)',
|
194 |
-
r'probability[:\s]*(\d+(?:\.\d+)?)',
|
195 |
-
r'(\d+(?:\.\d+)?)%',
|
196 |
-
r'score[:\s]*(\d+(?:\.\d+)?)'
|
197 |
-
]
|
198 |
-
|
199 |
-
for pattern in confidence_patterns:
|
200 |
-
match = re.search(pattern, text, re.IGNORECASE)
|
201 |
-
if match:
|
202 |
-
value = float(match.group(1))
|
203 |
-
return value / 100 if value > 1 else value
|
204 |
-
|
205 |
-
return 0.0
|
206 |
|
207 |
-
def
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
r'\$(\d+(?:\.\d+)?)',
|
213 |
-
r'cost[:\s]*(\d+(?:\.\d+)?)',
|
214 |
-
r'price[:\s]*(\d+(?:\.\d+)?)'
|
215 |
-
]
|
216 |
-
|
217 |
-
for pattern in cost_patterns:
|
218 |
-
match = re.search(pattern, text, re.IGNORECASE)
|
219 |
-
if match:
|
220 |
-
return float(match.group(1))
|
221 |
-
|
222 |
-
return 0.0
|
223 |
|
224 |
class EnhancedMAIDXInterface:
|
225 |
-
"""
|
226 |
|
227 |
def __init__(self):
|
228 |
self.sessions_history = []
|
229 |
-
self.
|
|
|
230 |
|
231 |
-
#
|
232 |
-
self.conversation_logger = AgentConversationLogger("mai_dx_conversation_logs")
|
233 |
-
self.log_capture = MAILogCapture()
|
234 |
-
|
235 |
-
# Предвизначені тестові кейси
|
236 |
self.sample_cases = {
|
237 |
-
"Кардіологічний (
|
238 |
-
Пацієнт: 58-річний чоловік,
|
239 |
-
Скарги: Гострий біль у грудях
|
240 |
-
|
241 |
-
ЕКГ: ST-підйоми у відведеннях II, III, aVF
|
242 |
Тропонін I: 8.5 нг/мл (норма <0.04)
|
243 |
-
|
244 |
-
|
245 |
-
|
|
|
|
|
|
|
246 |
Презентація: Раптова слабкість правої сторони 2 години тому
|
247 |
-
Огляд:
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
}
|
257 |
|
258 |
-
def
|
259 |
self,
|
260 |
case_name: str,
|
261 |
-
patient_info: str,
|
262 |
mode: str,
|
263 |
budget: int,
|
264 |
max_iterations: int,
|
@@ -266,31 +190,33 @@ class EnhancedMAIDXInterface:
|
|
266 |
expected_diagnosis: str = "",
|
267 |
enable_logging: bool = True,
|
268 |
progress=gr.Progress()
|
269 |
-
) -> Tuple[str, str, str,
|
270 |
-
"""Діагностика з
|
271 |
|
272 |
if not MAI_DX_AVAILABLE:
|
273 |
-
return f"❌ MAI-DX
|
274 |
|
275 |
if not patient_info.strip():
|
276 |
-
return "❌ Введіть інформацію про пацієнта"
|
277 |
|
|
|
|
|
278 |
conversation_log = ""
|
279 |
case_id = None
|
280 |
|
281 |
try:
|
282 |
-
progress(0.1, desc="
|
283 |
|
284 |
-
#
|
285 |
if enable_logging:
|
286 |
case_id = self.conversation_logger.start_conversation(
|
287 |
-
case_name or f"Case_{datetime.now().strftime('%H%M%S')}",
|
288 |
-
patient_info,
|
289 |
mode
|
290 |
)
|
291 |
-
conversation_log += f"
|
292 |
|
293 |
-
progress(0.2, desc="Створення
|
294 |
|
295 |
# Створення оркестратора
|
296 |
orchestrator = MaiDxOrchestrator(
|
@@ -300,27 +226,29 @@ class EnhancedMAIDXInterface:
|
|
300 |
mode=mode if mode != "ensemble" else "budgeted"
|
301 |
)
|
302 |
|
303 |
-
progress(0.3, desc="Запуск
|
304 |
|
305 |
-
# Перехоплення виводу для логування
|
306 |
start_time = time.time()
|
307 |
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
|
|
|
|
|
|
|
|
319 |
|
320 |
# Запуск діагностики з перехопленням виводу
|
321 |
with io.StringIO() as captured_output:
|
322 |
try:
|
323 |
-
# Перенаправляємо stdout для захоплення логів
|
324 |
original_stdout = sys.stdout
|
325 |
sys.stdout = captured_output
|
326 |
|
@@ -335,7 +263,7 @@ class EnhancedMAIDXInterface:
|
|
335 |
except AttributeError:
|
336 |
result = orchestrator.run(
|
337 |
initial_case_info=patient_info,
|
338 |
-
full_case_details=patient_info,
|
339 |
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
340 |
)
|
341 |
else:
|
@@ -344,53 +272,46 @@ class EnhancedMAIDXInterface:
|
|
344 |
full_case_details=patient_info,
|
345 |
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
346 |
)
|
347 |
-
|
348 |
finally:
|
349 |
sys.stdout = original_stdout
|
350 |
captured_text = captured_output.getvalue()
|
351 |
|
352 |
-
|
|
|
|
|
353 |
|
354 |
-
# Парсинг
|
355 |
if enable_logging and captured_text:
|
356 |
-
conversation_log += "
|
357 |
-
conversation_log += "=" *
|
358 |
conversation_log += captured_text + "\n"
|
359 |
-
conversation_log += "=" *
|
360 |
|
361 |
-
#
|
362 |
-
self.
|
363 |
-
|
364 |
-
duration = time.time() - start_time
|
365 |
|
366 |
-
|
367 |
-
|
368 |
-
# Логування фінального результату
|
369 |
if enable_logging:
|
370 |
self.conversation_logger.log_agent_message(
|
371 |
"Judge",
|
372 |
-
"final_evaluation",
|
373 |
result.final_diagnosis,
|
374 |
-
getattr(result, 'accuracy_reasoning', '
|
375 |
result.accuracy_score / 5.0,
|
376 |
result.total_cost,
|
377 |
max_iterations
|
378 |
)
|
379 |
|
380 |
-
# Завершення логування
|
381 |
saved_case_id = self.conversation_logger.end_conversation(
|
382 |
result.final_diagnosis,
|
383 |
result.accuracy_score,
|
384 |
result.total_cost
|
385 |
)
|
386 |
|
387 |
-
conversation_log += f"💾
|
388 |
-
conversation_log += f"📊 Тривалість: {duration:.1f} сек\n"
|
389 |
-
conversation_log += f"🎯 Фінальний діагноз: {result.final_diagnosis}\n"
|
390 |
-
conversation_log += f"⭐ Точність: {result.accuracy_score}/5.0\n"
|
391 |
-
conversation_log += f"💰 Вартість: ${result.total_cost}\n"
|
392 |
|
393 |
-
# Створення сесії
|
394 |
session = DiagnosisSession(
|
395 |
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
396 |
case_name=case_name or f"Case_{len(self.sessions_history) + 1}",
|
@@ -400,407 +321,651 @@ class EnhancedMAIDXInterface:
|
|
400 |
diagnosis=result.final_diagnosis,
|
401 |
confidence=result.accuracy_score,
|
402 |
cost=result.total_cost,
|
403 |
-
iterations=result
|
404 |
duration=duration,
|
405 |
-
status="Успішно" if result.accuracy_score >=
|
406 |
reasoning=getattr(result, 'accuracy_reasoning', 'Недоступно')[:300] + "..."
|
407 |
)
|
408 |
|
409 |
self.sessions_history.append(session)
|
410 |
|
411 |
-
progress(1.0, desc="Готово!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
412 |
|
413 |
-
|
414 |
-
main_result = self._format_main_result(session, result)
|
415 |
-
detailed_analysis = self._format_detailed_analysis(session, result)
|
416 |
-
recommendations = self._generate_recommendations(result)
|
417 |
-
history_df = self._get_history_dataframe()
|
418 |
|
419 |
-
return
|
420 |
|
421 |
except Exception as e:
|
422 |
-
|
423 |
-
|
424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
425 |
|
426 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
427 |
|
428 |
def _format_main_result(self, session, result):
|
429 |
"""Форматування основного результату"""
|
430 |
-
|
|
|
431 |
|
432 |
return f"""
|
433 |
-
|
434 |
|
435 |
-
|
436 |
-
-
|
437 |
-
-
|
438 |
-
-
|
|
|
439 |
|
440 |
-
|
441 |
**{session.diagnosis}**
|
442 |
|
443 |
-
|
444 |
-
-
|
445 |
-
- **Бал точності**: {session.confidence}/5.0
|
446 |
- **Статус**: {session.status}
|
|
|
447 |
|
448 |
-
|
449 |
-
-
|
450 |
- **Бюджет**: ${session.budget:,}
|
451 |
-
-
|
452 |
-
-
|
|
|
|
|
|
|
453 |
"""
|
454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
455 |
def _format_detailed_analysis(self, session, result):
|
456 |
"""Детальний аналіз"""
|
457 |
return f"""
|
458 |
-
|
459 |
|
460 |
-
|
461 |
{session.reasoning}
|
462 |
|
463 |
-
|
464 |
-
-
|
465 |
-
-
|
466 |
-
-
|
|
|
467 |
|
468 |
-
|
469 |
-
Система MAI-DX використала {session.iterations}
|
|
|
|
|
|
|
|
|
|
|
470 |
"""
|
471 |
|
472 |
-
def
|
473 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
474 |
return f"""
|
475 |
-
|
|
|
|
|
|
|
|
|
|
|
476 |
|
477 |
-
|
478 |
-
-
|
479 |
-
-
|
480 |
-
-
|
481 |
|
482 |
-
|
483 |
-
- **Валідація ШІ-діагнозу**: Порівняйте з експертною оцінкою
|
484 |
-
- **Аналіз логів**: Перегляньте деталі бесід агентів
|
485 |
-
- **Поліпшення точності**: Використайте ensemble режим для критичних випадків
|
486 |
|
487 |
-
|
488 |
-
|
489 |
"""
|
490 |
|
491 |
-
def
|
492 |
-
"""
|
493 |
-
|
494 |
-
return pd.DataFrame()
|
495 |
-
|
496 |
-
data = []
|
497 |
-
for session in self.sessions_history:
|
498 |
-
data.append({
|
499 |
-
"Час": session.timestamp,
|
500 |
-
"Випадок": session.case_name,
|
501 |
-
"Режим": session.mode,
|
502 |
-
"Діагноз": session.diagnosis[:50] + "..." if len(session.diagnosis) > 50 else session.diagnosis,
|
503 |
-
"Точність": f"{session.confidence:.1f}/5.0",
|
504 |
-
"Вартість": f"${session.cost:.2f}",
|
505 |
-
"Ітерації": session.iterations,
|
506 |
-
"Тривалість": f"{session.duration:.1f}с",
|
507 |
-
"Статус": session.status
|
508 |
-
})
|
509 |
-
|
510 |
-
return pd.DataFrame(data)
|
511 |
|
512 |
-
def
|
513 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
514 |
try:
|
515 |
conversations = self.conversation_logger.list_conversations()
|
516 |
|
517 |
if not conversations:
|
518 |
-
|
|
|
519 |
|
|
|
520 |
df = pd.DataFrame(conversations)
|
521 |
-
summary = self.conversation_logger.get_conversation_summary()
|
522 |
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
|
|
529 |
|
530 |
-
|
531 |
-
|
|
|
|
|
|
|
532 |
"""
|
533 |
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
"""Експорт детального аналізу бесіди"""
|
541 |
-
try:
|
542 |
-
if not case_id:
|
543 |
-
return "Оберіть випадок для аналізу"
|
544 |
|
545 |
-
|
546 |
-
return f"✅ Детальний звіт експортовано: {report_file}"
|
547 |
|
548 |
except Exception as e:
|
549 |
-
return f"❌ Помилка
|
550 |
|
551 |
def create_enhanced_gradio_interface():
|
552 |
-
"""Створення
|
553 |
|
554 |
interface = EnhancedMAIDXInterface()
|
555 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
556 |
with gr.Blocks(
|
557 |
-
title="MAI-DX Enhanced Research
|
558 |
-
theme=gr.themes.Soft(
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
border-radius: 5px;
|
565 |
-
padding: 10px;
|
566 |
-
max-height: 400px;
|
567 |
-
overflow-y: auto;
|
568 |
-
}
|
569 |
-
"""
|
570 |
) as demo:
|
571 |
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
|
|
|
|
577 |
""")
|
578 |
|
579 |
-
with gr.Tabs():
|
580 |
|
581 |
-
#
|
582 |
-
with gr.Tab("🩺 Діагностика з
|
|
|
583 |
with gr.Row():
|
|
|
584 |
with gr.Column(scale=1):
|
585 |
-
gr.Markdown("### 📝
|
586 |
|
587 |
-
case_name = gr.Textbox(
|
|
|
|
|
|
|
|
|
588 |
|
589 |
sample_selector = gr.Dropdown(
|
590 |
choices=list(interface.sample_cases.keys()),
|
591 |
-
label="
|
592 |
-
value=None
|
|
|
593 |
)
|
594 |
|
595 |
patient_info = gr.Textbox(
|
596 |
-
label="
|
597 |
lines=12,
|
598 |
-
placeholder="
|
|
|
599 |
)
|
600 |
|
601 |
-
def load_sample(sample_name):
|
602 |
-
if sample_name:
|
603 |
-
return interface.sample_cases[sample_name], sample_name
|
604 |
-
return "", ""
|
605 |
-
|
606 |
-
sample_selector.change(load_sample, [sample_selector], [patient_info, case_name])
|
607 |
-
|
608 |
expected_diagnosis = gr.Textbox(
|
609 |
-
label="Очікуваний діагноз (
|
610 |
-
placeholder="
|
|
|
611 |
)
|
612 |
-
|
|
|
613 |
with gr.Column(scale=1):
|
614 |
-
gr.Markdown("### ⚙️ Налаштування")
|
615 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
616 |
mode = gr.Radio(
|
617 |
-
choices=
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
|
|
|
|
|
|
|
|
626 |
)
|
627 |
|
628 |
-
|
629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
630 |
|
631 |
model_name = gr.Dropdown(
|
632 |
choices=[
|
633 |
"gemini/gemini-2.5-flash",
|
634 |
"gpt-4",
|
|
|
635 |
"claude-3-5-sonnet",
|
636 |
"grok-beta"
|
637 |
],
|
638 |
-
label="LLM Модель",
|
639 |
-
value="gemini/gemini-2.5-flash"
|
|
|
640 |
)
|
641 |
|
642 |
enable_logging = gr.Checkbox(
|
643 |
-
label="
|
644 |
-
value=True
|
|
|
645 |
)
|
646 |
|
647 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
648 |
|
649 |
-
# Результати з логами
|
650 |
with gr.Row():
|
651 |
with gr.Column(scale=2):
|
652 |
-
main_result = gr.Markdown(label="Основний результат")
|
653 |
-
detailed_analysis = gr.Markdown(label="Детальний аналіз")
|
654 |
|
655 |
with gr.Column(scale=1):
|
656 |
-
recommendations = gr.Markdown(label="Рекомендації")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
657 |
|
658 |
-
# Логи бесіди
|
659 |
-
gr.Markdown("### 💬 Логи
|
660 |
conversation_logs = gr.Textbox(
|
661 |
-
label="
|
662 |
-
lines=
|
663 |
elem_classes=["conversation-log"],
|
664 |
-
interactive=False
|
|
|
665 |
)
|
666 |
|
667 |
# Запуск діагностики
|
668 |
diagnose_btn.click(
|
669 |
-
interface.
|
670 |
inputs=[
|
671 |
-
case_name, patient_info, mode, budget, max_iterations,
|
672 |
model_name, expected_diagnosis, enable_logging
|
673 |
],
|
674 |
-
outputs=[
|
|
|
|
|
|
|
675 |
)
|
676 |
|
677 |
-
# Вкладка
|
678 |
-
with gr.Tab("📊
|
679 |
-
gr.Markdown("### 🔍
|
680 |
|
681 |
with gr.Row():
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
# Таблиця бесід
|
686 |
-
conversation_table = gr.Dataframe(
|
687 |
-
label="Збережені бесіди",
|
688 |
-
interactive=False
|
689 |
-
)
|
690 |
|
691 |
-
|
692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
693 |
|
694 |
-
# Детальний аналіз
|
695 |
-
gr.Markdown("### 🔬
|
696 |
|
697 |
with gr.Row():
|
698 |
case_selector = gr.Dropdown(
|
699 |
-
label="Оберіть випадок для аналізу",
|
700 |
choices=[],
|
701 |
interactive=True
|
702 |
)
|
703 |
|
704 |
-
|
705 |
|
706 |
analysis_status = gr.Markdown()
|
707 |
|
708 |
-
# Функції
|
709 |
-
def
|
710 |
-
df, summary = interface.
|
711 |
-
|
712 |
-
# Оновлюємо список для вибору
|
713 |
-
case_choices = []
|
714 |
-
if not df.empty:
|
715 |
-
case_choices = [(f"{row['case_name']} ({row['case_id']})", row['case_id'])
|
716 |
-
for _, row in df.iterrows()]
|
717 |
-
|
718 |
-
return df, summary, gr.Dropdown.update(choices=case_choices)
|
719 |
|
720 |
def export_analytics():
|
721 |
try:
|
722 |
filename = interface.conversation_logger.export_analytics_csv()
|
723 |
-
return f"✅
|
724 |
except Exception as e:
|
725 |
return f"❌ Помилка експорту: {e}"
|
726 |
|
727 |
-
def
|
728 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
729 |
|
730 |
# Зв'язування функцій
|
731 |
-
|
732 |
-
|
733 |
-
outputs=[
|
734 |
)
|
735 |
|
736 |
-
|
737 |
export_analytics,
|
738 |
outputs=[analysis_status]
|
739 |
)
|
740 |
|
741 |
-
|
742 |
-
|
743 |
inputs=[case_selector],
|
744 |
outputs=[analysis_status]
|
745 |
)
|
746 |
|
747 |
# Автооновлення при завантаженні
|
748 |
-
demo.load(
|
749 |
|
750 |
-
# Вкладка
|
751 |
-
with gr.Tab("📚 Документація
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
752 |
gr.Markdown("""
|
753 |
-
###
|
754 |
-
|
755 |
-
**Що логується:**
|
756 |
-
- 🗣️ Всі повідомлення між 8 ШІ-агентами лікарів
|
757 |
-
- 🕐 Точні часові мітки кожної взаємодії
|
758 |
-
- 🎯 Рівні впевненості агентів у своїх рішеннях
|
759 |
-
- 💰 Вартість кожного рішення та тесту
|
760 |
-
- 🔄 Хід ітеративного процесу діагностики
|
761 |
|
762 |
-
|
763 |
-
1.
|
764 |
-
2.
|
765 |
-
3.
|
766 |
-
4.
|
767 |
-
5. **✅ Dr. Checklist** - Контроль якості діагностики
|
768 |
-
6. **🤝 Consensus Coordinator** - Координація рішень панелі
|
769 |
-
7. **🔑 Gatekeeper** - Надання клінічної інформації
|
770 |
-
8. **⚖️ Judge** - Оцінка точності фінального діагнозу
|
771 |
|
772 |
-
|
773 |
-
-
|
774 |
-
-
|
775 |
-
-
|
776 |
-
-
|
|
|
777 |
|
778 |
-
|
779 |
-
-
|
780 |
-
-
|
781 |
-
-
|
782 |
-
- Як різні моделі LLM впливають на поведінку агентів?
|
783 |
|
784 |
-
|
785 |
-
-
|
786 |
-
-
|
787 |
-
-
|
788 |
""")
|
789 |
|
790 |
return demo
|
791 |
|
792 |
if __name__ == "__main__":
|
793 |
-
|
794 |
-
|
|
|
|
|
|
|
795 |
|
796 |
-
|
797 |
-
print("📝 Увімкнено детальне логування бесід агентів")
|
798 |
-
print("🔍 Логи зберігаються у папці: mai_dx_conversation_logs/")
|
799 |
|
800 |
demo.launch(
|
801 |
server_name="0.0.0.0",
|
802 |
server_port=7860,
|
803 |
share=True,
|
804 |
-
debug=
|
805 |
-
show_error=True
|
|
|
|
|
806 |
)
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Виправлений MAI-DX Gradio Interface з сумісністю для нових версій Gradio
|
4 |
"""
|
5 |
import os
|
6 |
import sys
|
|
|
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
|
|
|
17 |
import io
|
18 |
from contextlib import redirect_stdout, redirect_stderr
|
19 |
|
20 |
+
# Налаштування середовища
|
21 |
+
os.environ.update({
|
22 |
+
"SWARMS_VERBOSITY": "ERROR",
|
23 |
+
"RICH_TRACEBACK": "0",
|
24 |
+
"SWARMS_SHOW_PANEL": "false",
|
25 |
+
"SWARMS_AUTO_PRINT": "false"
|
26 |
+
})
|
27 |
warnings.filterwarnings("ignore")
|
28 |
|
|
|
29 |
from dotenv import load_dotenv
|
30 |
load_dotenv()
|
31 |
|
|
|
43 |
|
44 |
patch_rich_formatter()
|
45 |
|
46 |
+
# Імпорт MAI-DX
|
47 |
try:
|
48 |
from mai_dx import MaiDxOrchestrator
|
49 |
MAI_DX_AVAILABLE = True
|
|
|
51 |
MAI_DX_AVAILABLE = False
|
52 |
IMPORT_ERROR = str(e)
|
53 |
|
54 |
+
# Перевірка доступності Plotly
|
55 |
+
try:
|
56 |
+
import plotly.graph_objects as go
|
57 |
+
import plotly.express as px
|
58 |
+
PLOTLY_AVAILABLE = True
|
59 |
+
except ImportError:
|
60 |
+
PLOTLY_AVAILABLE = False
|
61 |
+
print("⚠️ Plotly не встановлено, візуалізації будуть недоступні")
|
62 |
+
|
63 |
+
# Імпорт логгера (створюємо заглушку якщо немає)
|
64 |
try:
|
65 |
from agent_conversation_logger import AgentConversationLogger, DiagnosisSession
|
66 |
LOGGER_AVAILABLE = True
|
67 |
except ImportError:
|
68 |
LOGGER_AVAILABLE = False
|
69 |
+
|
70 |
+
@dataclass
|
71 |
+
class DiagnosisSession:
|
72 |
+
timestamp: str
|
73 |
+
case_name: str
|
74 |
+
patient_info: str
|
75 |
+
mode: str
|
76 |
+
budget: int
|
77 |
+
diagnosis: str
|
78 |
+
confidence: float
|
79 |
+
cost: float
|
80 |
+
iterations: int
|
81 |
+
duration: float
|
82 |
+
status: str
|
83 |
+
reasoning: str
|
84 |
|
85 |
class AgentConversationLogger:
|
86 |
def __init__(self, *args, **kwargs):
|
87 |
+
self.conversations = []
|
88 |
def start_conversation(self, *args):
|
89 |
+
return f"mock_{len(self.conversations)}"
|
90 |
def log_agent_message(self, *args, **kwargs):
|
91 |
pass
|
92 |
def end_conversation(self, *args):
|
93 |
+
return f"saved_{len(self.conversations)}"
|
94 |
def list_conversations(self):
|
95 |
+
return self.conversations
|
96 |
def get_conversation_summary(self):
|
97 |
+
return {"total_conversations": 0, "average_accuracy": 0, "average_cost": 0, "mode_distribution": {}}
|
98 |
+
def export_conversation_report(self, case_id, format):
|
99 |
+
return f"report_{case_id}.{format}"
|
100 |
+
def export_analytics_csv(self):
|
101 |
+
return "analytics.csv"
|
102 |
|
103 |
+
class RealTimeMetrics:
|
104 |
+
"""Метрики в реальному часі"""
|
105 |
|
106 |
def __init__(self):
|
107 |
+
self.reset()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
|
109 |
+
def reset(self):
|
110 |
+
self.start_time = time.time()
|
111 |
+
self.agents_activity = {
|
112 |
+
'Dr. Hypothesis': 0,
|
113 |
+
'Dr. Test-Chooser': 0,
|
114 |
+
'Dr. Challenger': 0,
|
115 |
+
'Dr. Stewardship': 0,
|
116 |
+
'Dr. Checklist': 0,
|
117 |
+
'Consensus Coordinator': 0,
|
118 |
+
'Gatekeeper': 0,
|
119 |
+
'Judge': 0
|
120 |
+
}
|
121 |
+
self.cost_progression = []
|
122 |
+
self.confidence_progression = []
|
123 |
+
self.decision_timeline = []
|
|
|
|
|
|
|
124 |
|
125 |
+
def update_agent_activity(self, agent_name: str):
|
126 |
+
if agent_name in self.agents_activity:
|
127 |
+
self.agents_activity[agent_name] += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
+
def add_cost_point(self, cost: float):
|
130 |
+
self.cost_progression.append({
|
131 |
+
'time': time.time() - self.start_time,
|
132 |
+
'cost': cost
|
133 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
+
def add_confidence_point(self, confidence: float):
|
136 |
+
self.confidence_progression.append({
|
137 |
+
'time': time.time() - self.start_time,
|
138 |
+
'confidence': confidence
|
139 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
|
141 |
class EnhancedMAIDXInterface:
|
142 |
+
"""Покращений інтерфейс з сумісністю для Gradio"""
|
143 |
|
144 |
def __init__(self):
|
145 |
self.sessions_history = []
|
146 |
+
self.conversation_logger = AgentConversationLogger("mai_dx_logs")
|
147 |
+
self.current_metrics = RealTimeMetrics()
|
148 |
|
149 |
+
# Розширені тестові кейси
|
|
|
|
|
|
|
|
|
150 |
self.sample_cases = {
|
151 |
+
"🫀 Кардіологічний (Гострий MI)": {
|
152 |
+
"info": """Пацієнт: 58-річний чоловік, менеджер, гіпертонія в анамнезі
|
153 |
+
Скарги: Гострий роздираючий біль у грудях 3 години, іррадіація в ліву руку
|
154 |
+
Огляд: Блідий, пітливий, АТ 160/90, ЧСС 95
|
155 |
+
ЕКГ: ST-підйоми у відведеннях II, III, aVF (нижня стінка)
|
156 |
Тропонін I: 8.5 нг/мл (норма <0.04)
|
157 |
+
Анамнез: Куріння 30 років, дислипідемія, сімейний анамнез ІХС""",
|
158 |
+
"expected": "Гострий інфаркт міокарда нижньої стінки (STEMI)"
|
159 |
+
},
|
160 |
+
|
161 |
+
"🧠 Неврологічний (Гострий інсульт)": {
|
162 |
+
"info": """Пацієнтка: 67-річна жінка, раптові неврологічні симптоми
|
163 |
Презентація: Раптова слабкість правої сторони 2 години тому
|
164 |
+
Огляд: Свідома, дезорієнтована у часі, правостороннє опущення обличчя
|
165 |
+
Неврологія: Правостороння геміплегія, афазія, девіація очей вліво
|
166 |
+
КТ голови: Гострого крововиливу немає, рання ішемія у лівій МСА
|
167 |
+
NIHSS: 15 балів""",
|
168 |
+
"expected": "Гострий ішемічний інсульт у басейні лівої середньої мозкової артерії"
|
169 |
+
},
|
170 |
+
|
171 |
+
"🦠 Інфекційний (Сепсис)": {
|
172 |
+
"info": """Пацієнт: 45-річний чоловік з прогресуючою лихоманкою
|
173 |
+
Скарги: Висока температура 39.5°C, озноб, загальна слабкість 3 дні
|
174 |
+
Огляд: Гарячий, тахікардія 120/хв, гіпотензія 85/50
|
175 |
+
Лабораторно: Лейкоцити 18000, С-реактивний білок 180, прокальцитонін 5.2
|
176 |
+
Посів крові: Pending, lactate 4.2 ммоль/л
|
177 |
+
Анамнез: Нещодавна стоматологічна п��оцедура""",
|
178 |
+
"expected": "Сепсис з можливим одонтогенним джерелом"
|
179 |
+
}
|
180 |
}
|
181 |
|
182 |
+
def diagnose_with_enhanced_tracking(
|
183 |
self,
|
184 |
case_name: str,
|
185 |
+
patient_info: str,
|
186 |
mode: str,
|
187 |
budget: int,
|
188 |
max_iterations: int,
|
|
|
190 |
expected_diagnosis: str = "",
|
191 |
enable_logging: bool = True,
|
192 |
progress=gr.Progress()
|
193 |
+
) -> Tuple[str, str, str, Optional[object], Optional[object], str]:
|
194 |
+
"""Діагностика з покращеним відстеженням - ВИПРАВЛЕНА ВЕРСІЯ"""
|
195 |
|
196 |
if not MAI_DX_AVAILABLE:
|
197 |
+
return self._format_error(f"❌ MAI-DX недоступний: {IMPORT_ERROR}")
|
198 |
|
199 |
if not patient_info.strip():
|
200 |
+
return self._format_error("❌ Введіть інформацію про пацієнта")
|
201 |
|
202 |
+
# Скидання метрик
|
203 |
+
self.current_metrics.reset()
|
204 |
conversation_log = ""
|
205 |
case_id = None
|
206 |
|
207 |
try:
|
208 |
+
progress(0.1, desc="🚀 Ініціалізація системи...")
|
209 |
|
210 |
+
# Логування початку
|
211 |
if enable_logging:
|
212 |
case_id = self.conversation_logger.start_conversation(
|
213 |
+
case_name or f"Case_{datetime.now().strftime('%H%M%S')}",
|
214 |
+
patient_info,
|
215 |
mode
|
216 |
)
|
217 |
+
conversation_log += f"📝 Розпочато логування: {case_id}\n\n"
|
218 |
|
219 |
+
progress(0.2, desc="🤖 Створення AI-панелі лікарів...")
|
220 |
|
221 |
# Створення оркестратора
|
222 |
orchestrator = MaiDxOrchestrator(
|
|
|
226 |
mode=mode if mode != "ensemble" else "budgeted"
|
227 |
)
|
228 |
|
229 |
+
progress(0.3, desc="🔍 Запуск діагностичного процесу...")
|
230 |
|
|
|
231 |
start_time = time.time()
|
232 |
|
233 |
+
# Симуляція прогресу з реальними етапами
|
234 |
+
diagnostic_stages = [
|
235 |
+
"🧠 Dr. Hypothesis аналізує симптоми...",
|
236 |
+
"🔬 Dr. Test-Chooser обирає тести...",
|
237 |
+
"🤔 Dr. Challenger перевіряє гіпотези...",
|
238 |
+
"💰 Dr. Stewardship оцінює вартість...",
|
239 |
+
"✅ Dr. Checklist контролює якість...",
|
240 |
+
"🤝 Consensus Coordinator формує рішення..."
|
241 |
+
]
|
242 |
+
|
243 |
+
for i, stage in enumerate(diagnostic_stages):
|
244 |
+
progress(0.3 + (i * 0.1), desc=stage)
|
245 |
+
time.sleep(0.3) # Короткша затримка
|
246 |
+
|
247 |
+
progress(0.8, desc="🎯 Формування діагнозу...")
|
248 |
|
249 |
# Запуск діагностики з перехопленням виводу
|
250 |
with io.StringIO() as captured_output:
|
251 |
try:
|
|
|
252 |
original_stdout = sys.stdout
|
253 |
sys.stdout = captured_output
|
254 |
|
|
|
263 |
except AttributeError:
|
264 |
result = orchestrator.run(
|
265 |
initial_case_info=patient_info,
|
266 |
+
full_case_details=patient_info,
|
267 |
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
268 |
)
|
269 |
else:
|
|
|
272 |
full_case_details=patient_info,
|
273 |
ground_truth_diagnosis=expected_diagnosis or "Unknown"
|
274 |
)
|
275 |
+
|
276 |
finally:
|
277 |
sys.stdout = original_stdout
|
278 |
captured_text = captured_output.getvalue()
|
279 |
|
280 |
+
duration = time.time() - start_time
|
281 |
+
|
282 |
+
progress(0.9, desc="📊 Обробка результатів...")
|
283 |
|
284 |
+
# Парсинг логів
|
285 |
if enable_logging and captured_text:
|
286 |
+
conversation_log += "🤖 Захоплені логи агентів:\n"
|
287 |
+
conversation_log += "=" * 60 + "\n"
|
288 |
conversation_log += captured_text + "\n"
|
289 |
+
conversation_log += "=" * 60 + "\n\n"
|
290 |
|
291 |
+
# Оновлення метрик
|
292 |
+
self._parse_logs_for_metrics(captured_text)
|
|
|
|
|
293 |
|
294 |
+
# Логування результату
|
|
|
|
|
295 |
if enable_logging:
|
296 |
self.conversation_logger.log_agent_message(
|
297 |
"Judge",
|
298 |
+
"final_evaluation",
|
299 |
result.final_diagnosis,
|
300 |
+
getattr(result, 'accuracy_reasoning', 'Фінальна оцінка'),
|
301 |
result.accuracy_score / 5.0,
|
302 |
result.total_cost,
|
303 |
max_iterations
|
304 |
)
|
305 |
|
|
|
306 |
saved_case_id = self.conversation_logger.end_conversation(
|
307 |
result.final_diagnosis,
|
308 |
result.accuracy_score,
|
309 |
result.total_cost
|
310 |
)
|
311 |
|
312 |
+
conversation_log += f"💾 Збережено як: {saved_case_id}\n"
|
|
|
|
|
|
|
|
|
313 |
|
314 |
+
# Створення сесії
|
315 |
session = DiagnosisSession(
|
316 |
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
317 |
case_name=case_name or f"Case_{len(self.sessions_history) + 1}",
|
|
|
321 |
diagnosis=result.final_diagnosis,
|
322 |
confidence=result.accuracy_score,
|
323 |
cost=result.total_cost,
|
324 |
+
iterations=getattr(result, 'iterations', max_iterations),
|
325 |
duration=duration,
|
326 |
+
status="✅ Успішно" if result.accuracy_score >= 3.0 else "⚠️ Потребує перегляду",
|
327 |
reasoning=getattr(result, 'accuracy_reasoning', 'Недоступно')[:300] + "..."
|
328 |
)
|
329 |
|
330 |
self.sessions_history.append(session)
|
331 |
|
332 |
+
progress(1.0, desc="✅ Готово!")
|
333 |
+
|
334 |
+
# Генерація візуалізацій (безпечно)
|
335 |
+
metrics_plot = self._create_metrics_visualization_safe()
|
336 |
+
agent_plot = self._create_agent_activity_chart_safe()
|
337 |
+
|
338 |
+
# Генерація всіх результатів
|
339 |
+
return (
|
340 |
+
self._format_main_result(session, result),
|
341 |
+
self._format_detailed_analysis(session, result),
|
342 |
+
self._generate_enhanced_recommendations(result, expected_diagnosis),
|
343 |
+
metrics_plot,
|
344 |
+
agent_plot,
|
345 |
+
conversation_log
|
346 |
+
)
|
347 |
+
|
348 |
+
except Exception as e:
|
349 |
+
error_msg = f"❌ Помилка діагностики: {str(e)}"
|
350 |
+
if case_id:
|
351 |
+
error_msg += f"\n🗂️ Case ID: {case_id}"
|
352 |
+
return self._format_error(error_msg)
|
353 |
+
|
354 |
+
def _create_metrics_visualization_safe(self):
|
355 |
+
"""Безпечне створення візуалізації метрик"""
|
356 |
+
try:
|
357 |
+
if not PLOTLY_AVAILABLE:
|
358 |
+
return None
|
359 |
+
|
360 |
+
if not self.current_metrics.cost_progression:
|
361 |
+
# Створюємо демо-графік
|
362 |
+
fig = go.Figure()
|
363 |
+
fig.add_trace(go.Scatter(
|
364 |
+
x=[0, 1, 2, 3],
|
365 |
+
y=[0, 300, 600, 900],
|
366 |
+
mode='lines+markers',
|
367 |
+
name='Демо-дані',
|
368 |
+
line=dict(color='#1f77b4', width=3)
|
369 |
+
))
|
370 |
+
fig.update_layout(
|
371 |
+
title='📈 Прогресія вартості (демо)',
|
372 |
+
xaxis_title='Час (секунди)',
|
373 |
+
yaxis_title='Вартість ($)',
|
374 |
+
template='plotly_white',
|
375 |
+
height=400
|
376 |
+
)
|
377 |
+
return fig
|
378 |
+
|
379 |
+
# Реальні дані
|
380 |
+
times = [point['time'] for point in self.current_metrics.cost_progression]
|
381 |
+
costs = [point['cost'] for point in self.current_metrics.cost_progression]
|
382 |
+
|
383 |
+
fig = go.Figure()
|
384 |
+
fig.add_trace(go.Scatter(
|
385 |
+
x=times,
|
386 |
+
y=costs,
|
387 |
+
mode='lines+markers',
|
388 |
+
name='Накопичена вартість',
|
389 |
+
line=dict(color='#1f77b4', width=3)
|
390 |
+
))
|
391 |
+
|
392 |
+
fig.update_layout(
|
393 |
+
title='📈 Прогресія вартості діагностики',
|
394 |
+
xaxis_title='Час (секунди)',
|
395 |
+
yaxis_title='Вартість ($)',
|
396 |
+
template='plotly_white',
|
397 |
+
height=400
|
398 |
+
)
|
399 |
+
|
400 |
+
return fig
|
401 |
+
|
402 |
+
except Exception as e:
|
403 |
+
print(f"Помилка візуалізації метрик: {e}")
|
404 |
+
return None
|
405 |
+
|
406 |
+
def _create_agent_activity_chart_safe(self):
|
407 |
+
"""Безпечне створення діаграми активності агентів"""
|
408 |
+
try:
|
409 |
+
if not PLOTLY_AVAILABLE:
|
410 |
+
return None
|
411 |
+
|
412 |
+
# Підготовка даних
|
413 |
+
agents = list(self.current_metrics.agents_activity.keys())
|
414 |
+
activities = list(self.current_metrics.agents_activity.values())
|
415 |
+
|
416 |
+
# Якщо немає активності, створюємо демо
|
417 |
+
if all(activity == 0 for activity in activities):
|
418 |
+
activities = [1, 2, 1, 1, 1, 2, 3, 1] # Демо-дані
|
419 |
+
|
420 |
+
# Кольори для агентів
|
421 |
+
colors = [
|
422 |
+
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
|
423 |
+
'#FECA57', '#FF9FF3', '#54A0FF', '#5F27CD'
|
424 |
+
]
|
425 |
+
|
426 |
+
fig = go.Figure(data=[
|
427 |
+
go.Bar(
|
428 |
+
x=agents,
|
429 |
+
y=activities,
|
430 |
+
marker_color=colors[:len(agents)],
|
431 |
+
text=activities,
|
432 |
+
textposition='auto'
|
433 |
+
)
|
434 |
+
])
|
435 |
+
|
436 |
+
fig.update_layout(
|
437 |
+
title='🤖 Активність ШІ-агентів лікарів',
|
438 |
+
xaxis_title='Агенти',
|
439 |
+
yaxis_title='Кількість взаємодій',
|
440 |
+
template='plotly_white',
|
441 |
+
height=400,
|
442 |
+
showlegend=False
|
443 |
+
)
|
444 |
|
445 |
+
fig.update_xaxes(tickangle=45)
|
|
|
|
|
|
|
|
|
446 |
|
447 |
+
return fig
|
448 |
|
449 |
except Exception as e:
|
450 |
+
print(f"Помилка графіку агентів: {e}")
|
451 |
+
return None
|
452 |
+
|
453 |
+
def _parse_logs_for_metrics(self, captured_text: str):
|
454 |
+
"""Парсинг логів для оновлення метрик"""
|
455 |
+
lines = captured_text.split('\n')
|
456 |
+
|
457 |
+
for line in lines:
|
458 |
+
line = line.strip()
|
459 |
+
if not line:
|
460 |
+
continue
|
461 |
|
462 |
+
# Пошук активності агентів
|
463 |
+
for agent in self.current_metrics.agents_activity.keys():
|
464 |
+
if agent.lower().replace(" ", "").replace(".", "") in line.lower():
|
465 |
+
self.current_metrics.update_agent_activity(agent)
|
466 |
+
break
|
467 |
+
|
468 |
+
# Пошук вартості
|
469 |
+
import re
|
470 |
+
cost_match = re.search(r'\$(\d+(?:\.\d+)?)', line)
|
471 |
+
if cost_match:
|
472 |
+
self.current_metrics.add_cost_point(float(cost_match.group(1)))
|
473 |
|
474 |
def _format_main_result(self, session, result):
|
475 |
"""Форматування основного результату"""
|
476 |
+
confidence_emoji = "🎯" if session.confidence >= 4.0 else "👍" if session.confidence >= 3.0 else "⚠️"
|
477 |
+
efficiency = ((session.budget - session.cost) / session.budget * 100)
|
478 |
|
479 |
return f"""
|
480 |
+
## 🏥 Результати MAI-DX Діагностики
|
481 |
|
482 |
+
### 📋 Основна інформація
|
483 |
+
- **🗂️ Випадок**: {session.case_name}
|
484 |
+
- **⏰ Час**: {session.timestamp}
|
485 |
+
- **🔧 Режим**: {session.mode}
|
486 |
+
- **🤖 Модель**: AI Multi-Agent
|
487 |
|
488 |
+
### {confidence_emoji} Діагностичний висновок
|
489 |
**{session.diagnosis}**
|
490 |
|
491 |
+
### 📊 Показники якості
|
492 |
+
- **Точність**: {session.confidence:.1f}/5.0 ⭐
|
|
|
493 |
- **Статус**: {session.status}
|
494 |
+
- **Ітерації**: {session.iterations} циклів
|
495 |
|
496 |
+
### 💰 Економічні показники
|
497 |
+
- **Витрачено**: ${session.cost:,.2f}
|
498 |
- **Бюджет**: ${session.budget:,}
|
499 |
+
- **Ефективність**: {efficiency:.1f}% бюджету збережено
|
500 |
+
- **Швидкість**: {session.duration:.1f} секунд
|
501 |
+
|
502 |
+
### 🎖️ Загальна оцінка
|
503 |
+
{self._get_overall_rating(session)}
|
504 |
"""
|
505 |
|
506 |
+
def _get_overall_rating(self, session):
|
507 |
+
"""Загальна оцінка ефективності"""
|
508 |
+
efficiency = ((session.budget - session.cost) / session.budget * 100)
|
509 |
+
|
510 |
+
if session.confidence >= 4.0 and efficiency >= 50:
|
511 |
+
return "🏆 **ВІДМІННО** - Високоточний та економічний діагноз"
|
512 |
+
elif session.confidence >= 3.0 and efficiency >= 30:
|
513 |
+
return "🥈 **ДОБРЕ** - Надійний діагноз з прийнятною вартістю"
|
514 |
+
elif session.confidence >= 2.0:
|
515 |
+
return "🥉 **ЗАДОВІЛЬНО** - Потребує додаткової верифікації"
|
516 |
+
else:
|
517 |
+
return "❌ **ПОТРЕБУЄ ПЕРЕГЛЯДУ** - Низька впевненість"
|
518 |
+
|
519 |
def _format_detailed_analysis(self, session, result):
|
520 |
"""Детальний аналіз"""
|
521 |
return f"""
|
522 |
+
## 🔬 Детальний клінічний аналіз
|
523 |
|
524 |
+
### 💭 Медичне обґрунтування
|
525 |
{session.reasoning}
|
526 |
|
527 |
+
### 📈 Аналіз ефективності
|
528 |
+
- **⚡ Швидкість діагностики**: {session.duration:.1f} сек
|
529 |
+
- **💸 Економічна ефективність**: {((session.budget - session.cost) / session.budget * 100):.1f}%
|
530 |
+
- **🔄 Ітеративна конвергенція**: {session.iterations} циклів
|
531 |
+
- **🎯 Клінічна точність**: {(session.confidence / 5.0 * 100):.1f}%
|
532 |
|
533 |
+
### 🤖 Процес прийняття рішень
|
534 |
+
Система MAI-DX використала **{session.iterations} ітерацій** для досягнення консенсусу між **8 ШІ-агентами лікарів**.
|
535 |
+
|
536 |
+
### 📊 Порівняння з бенчмарками
|
537 |
+
- **Людські лікарі**: ~20% точність на NEJM CPC
|
538 |
+
- **GPT-4**: ~49% точність
|
539 |
+
- **MAI-DX**: ~85.5% точність ✨
|
540 |
"""
|
541 |
|
542 |
+
def _generate_enhanced_recommendations(self, result, expected_diagnosis):
|
543 |
+
"""Покращені рекомендації"""
|
544 |
+
comparison = ""
|
545 |
+
if expected_diagnosis:
|
546 |
+
comparison = f"""
|
547 |
+
### 🎯 Порівняння з очікуваним діагнозом
|
548 |
+
**Очікувався**: {expected_diagnosis}
|
549 |
+
**Отримано**: {result.final_diagnosis}
|
550 |
+
**Збіг**: {'✅ Так' if expected_diagnosis.lower() in result.final_diagnosis.lower() else '❌ Ні'}
|
551 |
+
"""
|
552 |
+
|
553 |
return f"""
|
554 |
+
## 💡 Клінічні рекомендації
|
555 |
+
|
556 |
+
### 🏥 Негайні дії
|
557 |
+
- 🔍 **Верифікація діагнозу** з лікарем-спеціалістом
|
558 |
+
- 📋 **Додаткові дослідження** при необхідності
|
559 |
+
- 👨⚕️ **Консультація експерта** для підтвердження
|
560 |
|
561 |
+
### 🔬 Дослідницький потенціал
|
562 |
+
- **📊 Аналіз логів**: Вивчіть деталі роботи агентів
|
563 |
+
- **📈 Валідація**: Перевірте на додаткових випадках
|
564 |
+
- **🤖 Тюнінг моделі**: Оптимізуйте параметри
|
565 |
|
566 |
+
{comparison}
|
|
|
|
|
|
|
567 |
|
568 |
+
### ⚠️ Важливе застереження
|
569 |
+
🔴 **Цей діагноз згенеровано ШІ для дослідницьких цілей та НЕ замінює професійну медичну консультацію.**
|
570 |
"""
|
571 |
|
572 |
+
def _format_error(self, error_msg):
|
573 |
+
"""Форматування помилки"""
|
574 |
+
return (error_msg, "", "", None, None, "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
575 |
|
576 |
+
def load_sample_case(self, sample_key):
|
577 |
+
"""Завантаження зразкового випадку"""
|
578 |
+
if sample_key and sample_key in self.sample_cases:
|
579 |
+
case_data = self.sample_cases[sample_key]
|
580 |
+
return (
|
581 |
+
case_data["info"],
|
582 |
+
sample_key,
|
583 |
+
case_data["expected"]
|
584 |
+
)
|
585 |
+
return "", "", ""
|
586 |
+
|
587 |
+
def get_enhanced_analytics(self):
|
588 |
+
"""Розширена аналітика - ВИПРАВЛЕНА ВЕРСІЯ"""
|
589 |
try:
|
590 |
conversations = self.conversation_logger.list_conversations()
|
591 |
|
592 |
if not conversations:
|
593 |
+
empty_df = pd.DataFrame()
|
594 |
+
return empty_df, "📊 Немає даних для аналізу", []
|
595 |
|
596 |
+
# Створення DataFrame
|
597 |
df = pd.DataFrame(conversations)
|
|
|
598 |
|
599 |
+
# Статистика
|
600 |
+
total_cases = len(conversations)
|
601 |
+
avg_accuracy = df['accuracy'].mean() if 'accuracy' in df.columns else 0
|
602 |
+
avg_cost = df['cost'].mean() if 'cost' in df.columns else 0
|
603 |
+
|
604 |
+
summary = f"""
|
605 |
+
## 📊 Розширена аналітика MAI-DX
|
606 |
|
607 |
+
### 🎯 Загальна статистика
|
608 |
+
- **Всього випадків**: {total_cases}
|
609 |
+
- **Середня точність**: {avg_accuracy:.2f}/5.0 ⭐
|
610 |
+
- **Середня вартість**: ${avg_cost:,.2f}
|
611 |
+
- **Загальна економія**: ${(3000 - avg_cost) * total_cases:,.2f}
|
612 |
"""
|
613 |
|
614 |
+
# Створення списку для dropdown
|
615 |
+
case_choices = []
|
616 |
+
if not df.empty and 'case_name' in df.columns:
|
617 |
+
case_choices = [(f"{row['case_name']} ({row.get('case_id', 'N/A')})",
|
618 |
+
row.get('case_id', f"case_{i}"))
|
619 |
+
for i, (_, row) in enumerate(df.iterrows())]
|
|
|
|
|
|
|
|
|
620 |
|
621 |
+
return df, summary, case_choices
|
|
|
622 |
|
623 |
except Exception as e:
|
624 |
+
return pd.DataFrame(), f"❌ Помилка аналітики: {e}", []
|
625 |
|
626 |
def create_enhanced_gradio_interface():
|
627 |
+
"""Створення виправленого Gradio інтерфейсу"""
|
628 |
|
629 |
interface = EnhancedMAIDXInterface()
|
630 |
|
631 |
+
# Сучасний CSS
|
632 |
+
custom_css = """
|
633 |
+
.gradio-container {
|
634 |
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
635 |
+
}
|
636 |
+
|
637 |
+
.main-header {
|
638 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
639 |
+
color: white;
|
640 |
+
padding: 2rem;
|
641 |
+
border-radius: 15px;
|
642 |
+
margin-bottom: 2rem;
|
643 |
+
text-align: center;
|
644 |
+
}
|
645 |
+
|
646 |
+
.conversation-log {
|
647 |
+
font-family: 'Fira Code', monospace;
|
648 |
+
background: #2d3748;
|
649 |
+
color: #e2e8f0;
|
650 |
+
border-radius: 8px;
|
651 |
+
padding: 1rem;
|
652 |
+
max-height: 500px;
|
653 |
+
overflow-y: auto;
|
654 |
+
white-space: pre-wrap;
|
655 |
+
}
|
656 |
+
"""
|
657 |
+
|
658 |
with gr.Blocks(
|
659 |
+
title="🏥 MAI-DX Enhanced Research Platform",
|
660 |
+
theme=gr.themes.Soft(
|
661 |
+
primary_hue="blue",
|
662 |
+
secondary_hue="purple",
|
663 |
+
neutral_hue="slate"
|
664 |
+
),
|
665 |
+
css=custom_css
|
|
|
|
|
|
|
|
|
|
|
|
|
666 |
) as demo:
|
667 |
|
668 |
+
# Заголовок
|
669 |
+
gr.HTML("""
|
670 |
+
<div class="main-header">
|
671 |
+
<h1>🏥 MAI-DX Enhanced Research Platform</h1>
|
672 |
+
<p>🤖 Платформа для дослідження ШІ-діагностики з детальним логуванням взаємодії агентів</p>
|
673 |
+
<p>📊 Базується на дослідженні Microsoft Research "Sequential Diagnosis with Language Models"</p>
|
674 |
+
</div>
|
675 |
""")
|
676 |
|
677 |
+
with gr.Tabs() as tabs:
|
678 |
|
679 |
+
# Основна вкладка діагностики
|
680 |
+
with gr.Tab("🩺 Діагностика з ШІ-агентами", elem_id="diagnosis-tab"):
|
681 |
+
|
682 |
with gr.Row():
|
683 |
+
# Ліва колонка - введення даних
|
684 |
with gr.Column(scale=1):
|
685 |
+
gr.Markdown("### 📝 Клінічний випадок")
|
686 |
|
687 |
+
case_name = gr.Textbox(
|
688 |
+
label="🏷️ Назва випадку",
|
689 |
+
placeholder="Наприклад: Кардіологічний випадок №1",
|
690 |
+
lines=1
|
691 |
+
)
|
692 |
|
693 |
sample_selector = gr.Dropdown(
|
694 |
choices=list(interface.sample_cases.keys()),
|
695 |
+
label="🎯 Готові тестові випадки",
|
696 |
+
value=None,
|
697 |
+
interactive=True
|
698 |
)
|
699 |
|
700 |
patient_info = gr.Textbox(
|
701 |
+
label="👤 Детальна інформація про пацієнта",
|
702 |
lines=12,
|
703 |
+
placeholder="Введіть повний опис клінічного випадку:\n- Демографія пацієнта\n- Скарги та анамнез\n- Фізикальне обстеження\n- Лабораторні дані\n- Інструментальні дослідження",
|
704 |
+
interactive=True
|
705 |
)
|
706 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
707 |
expected_diagnosis = gr.Textbox(
|
708 |
+
label="🎯 Очікуваний діагноз (для порівняння)",
|
709 |
+
placeholder="Опціонально: введіть правильний діагноз для оцінки точності",
|
710 |
+
lines=2
|
711 |
)
|
712 |
+
|
713 |
+
# Права колонка - налаштування
|
714 |
with gr.Column(scale=1):
|
715 |
+
gr.Markdown("### ⚙️ Налаштування діагностики")
|
716 |
|
717 |
+
mode_choices_map = {
|
718 |
+
"⚡ Миттєвий (найшвидший, базовий аналіз)": "instant",
|
719 |
+
"❓ Тільки питання (швидко, без тестів)": "question_only",
|
720 |
+
"💰 З бюджетом (збалансовано)": "budgeted",
|
721 |
+
"🔓 Без обмежень (повний аналіз)": "no_budget",
|
722 |
+
"👥 Консенсус (найточніший, повільний)": "ensemble"
|
723 |
+
}
|
724 |
+
|
725 |
mode = gr.Radio(
|
726 |
+
choices=list(mode_choices_map.keys()),
|
727 |
+
label="🔧 Режим діагностики",
|
728 |
+
value="💰 З бюджетом (збалансовано)",
|
729 |
+
interactive=True
|
730 |
+
)
|
731 |
+
|
732 |
+
budget = gr.Slider(
|
733 |
+
minimum=500,
|
734 |
+
maximum=10000,
|
735 |
+
step=500,
|
736 |
+
value=3000,
|
737 |
+
label="💵 Бюджет діагностики ($)",
|
738 |
+
interactive=True
|
739 |
)
|
740 |
|
741 |
+
max_iterations = gr.Slider(
|
742 |
+
minimum=1,
|
743 |
+
maximum=15,
|
744 |
+
step=1,
|
745 |
+
value=8,
|
746 |
+
label="🔄 Максимум ітерацій агентів",
|
747 |
+
interactive=True
|
748 |
+
)
|
749 |
|
750 |
model_name = gr.Dropdown(
|
751 |
choices=[
|
752 |
"gemini/gemini-2.5-flash",
|
753 |
"gpt-4",
|
754 |
+
"gpt-4-turbo",
|
755 |
"claude-3-5-sonnet",
|
756 |
"grok-beta"
|
757 |
],
|
758 |
+
label="🤖 LLM Модель",
|
759 |
+
value="gemini/gemini-2.5-flash",
|
760 |
+
interactive=True
|
761 |
)
|
762 |
|
763 |
enable_logging = gr.Checkbox(
|
764 |
+
label="📝 Детальне логування взаємодії агентів",
|
765 |
+
value=True,
|
766 |
+
interactive=True
|
767 |
)
|
768 |
|
769 |
+
gr.Markdown("---")
|
770 |
+
|
771 |
+
diagnose_btn = gr.Button(
|
772 |
+
"🚀 Запустити діагностику",
|
773 |
+
variant="primary",
|
774 |
+
size="lg",
|
775 |
+
interactive=True
|
776 |
+
)
|
777 |
+
|
778 |
+
# Завантаження зразків
|
779 |
+
sample_selector.change(
|
780 |
+
interface.load_sample_case,
|
781 |
+
inputs=[sample_selector],
|
782 |
+
outputs=[patient_info, case_name, expected_diagnosis]
|
783 |
+
)
|
784 |
+
|
785 |
+
# Результати діагностики
|
786 |
+
gr.Markdown("---")
|
787 |
+
gr.Markdown("## 📊 Результати діагностики")
|
788 |
|
|
|
789 |
with gr.Row():
|
790 |
with gr.Column(scale=2):
|
791 |
+
main_result = gr.Markdown(label="🎯 Основний результат")
|
792 |
+
detailed_analysis = gr.Markdown(label="🔬 Детальний аналіз")
|
793 |
|
794 |
with gr.Column(scale=1):
|
795 |
+
recommendations = gr.Markdown(label="💡 Рекомендації")
|
796 |
+
|
797 |
+
# Візуалізації (якщо Plotly доступний)
|
798 |
+
if PLOTLY_AVAILABLE:
|
799 |
+
with gr.Row():
|
800 |
+
with gr.Column():
|
801 |
+
metrics_plot = gr.Plot(label="📈 Метрики діагностики")
|
802 |
+
with gr.Column():
|
803 |
+
agent_activity_plot = gr.Plot(label="🤖 Активність агентів")
|
804 |
+
else:
|
805 |
+
gr.Markdown("ℹ️ Візуалізації недоступні (Plotly не встановлено)")
|
806 |
+
metrics_plot = gr.Textbox(visible=False)
|
807 |
+
agent_activity_plot = gr.Textbox(visible=False)
|
808 |
|
809 |
+
# Логи бесіди
|
810 |
+
gr.Markdown("### 💬 Логи взаємодії ШІ-агентів")
|
811 |
conversation_logs = gr.Textbox(
|
812 |
+
label="Детальні логи бесід між агентами",
|
813 |
+
lines=12,
|
814 |
elem_classes=["conversation-log"],
|
815 |
+
interactive=False,
|
816 |
+
show_copy_button=True
|
817 |
)
|
818 |
|
819 |
# Запуск діагностики
|
820 |
diagnose_btn.click(
|
821 |
+
interface.diagnose_with_enhanced_tracking,
|
822 |
inputs=[
|
823 |
+
case_name, patient_info, mode, budget, max_iterations,
|
824 |
model_name, expected_diagnosis, enable_logging
|
825 |
],
|
826 |
+
outputs=[
|
827 |
+
main_result, detailed_analysis, recommendations,
|
828 |
+
metrics_plot, agent_activity_plot, conversation_logs
|
829 |
+
]
|
830 |
)
|
831 |
|
832 |
+
# Вкладка аналітики
|
833 |
+
with gr.Tab("📊 Розширена аналітика", elem_id="analytics-tab"):
|
834 |
+
gr.Markdown("### 🔍 Аналіз збережених діагностичних сесій")
|
835 |
|
836 |
with gr.Row():
|
837 |
+
refresh_analytics_btn = gr.Button("🔄 Оновити дані", variant="secondary")
|
838 |
+
export_data_btn = gr.Button("📤 Експортувати аналітику", variant="secondary")
|
|
|
|
|
|
|
|
|
|
|
|
|
839 |
|
840 |
+
with gr.Row():
|
841 |
+
with gr.Column(scale=2):
|
842 |
+
analytics_table = gr.Dataframe(
|
843 |
+
label="📋 Історія діагностичних сесій",
|
844 |
+
interactive=False,
|
845 |
+
wrap=True
|
846 |
+
)
|
847 |
+
|
848 |
+
with gr.Column(scale=1):
|
849 |
+
analytics_summary = gr.Markdown(label="📊 Статистичний звіт")
|
850 |
|
851 |
+
# Детальний аналіз окремих випадків
|
852 |
+
gr.Markdown("### 🔬 Поглиблений аналіз випадків")
|
853 |
|
854 |
with gr.Row():
|
855 |
case_selector = gr.Dropdown(
|
856 |
+
label="🗂️ Оберіть випадок для детального аналізу",
|
857 |
choices=[],
|
858 |
interactive=True
|
859 |
)
|
860 |
|
861 |
+
analyze_case_btn = gr.Button("📋 Створити детальний звіт")
|
862 |
|
863 |
analysis_status = gr.Markdown()
|
864 |
|
865 |
+
# Функції аналітики - ВИПРАВЛЕНІ
|
866 |
+
def refresh_analytics():
|
867 |
+
df, summary, case_choices = interface.get_enhanced_analytics()
|
868 |
+
return df, summary, gr.Dropdown(choices=case_choices)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
869 |
|
870 |
def export_analytics():
|
871 |
try:
|
872 |
filename = interface.conversation_logger.export_analytics_csv()
|
873 |
+
return f"✅ Дані експортовано: {filename}"
|
874 |
except Exception as e:
|
875 |
return f"❌ Помилка експорту: {e}"
|
876 |
|
877 |
+
def analyze_case(case_id):
|
878 |
+
if not case_id:
|
879 |
+
return "⚠️ Оберіть випадок для аналізу"
|
880 |
+
try:
|
881 |
+
report = interface.conversation_logger.export_conversation_report(case_id, 'html')
|
882 |
+
return f"✅ Детальний звіт створено: {report}"
|
883 |
+
except Exception as e:
|
884 |
+
return f"❌ Помилка аналізу: {e}"
|
885 |
|
886 |
# Зв'язування функцій
|
887 |
+
refresh_analytics_btn.click(
|
888 |
+
refresh_analytics,
|
889 |
+
outputs=[analytics_table, analytics_summary, case_selector]
|
890 |
)
|
891 |
|
892 |
+
export_data_btn.click(
|
893 |
export_analytics,
|
894 |
outputs=[analysis_status]
|
895 |
)
|
896 |
|
897 |
+
analyze_case_btn.click(
|
898 |
+
analyze_case,
|
899 |
inputs=[case_selector],
|
900 |
outputs=[analysis_status]
|
901 |
)
|
902 |
|
903 |
# Автооновлення при завантаженні
|
904 |
+
demo.load(refresh_analytics, outputs=[analytics_table, analytics_summary, case_selector])
|
905 |
|
906 |
+
# Вкладка документації
|
907 |
+
with gr.Tab("📚 Документація та статус", elem_id="docs-tab"):
|
908 |
+
|
909 |
+
# Статус системи
|
910 |
+
gr.Markdown("### 🔧 Статус системи")
|
911 |
+
|
912 |
+
status_info = f"""
|
913 |
+
**MAI-DX доступність**: {'✅ Доступний' if MAI_DX_AVAILABLE else '❌ Недоступний'}
|
914 |
+
**Logger доступність**: {'✅ Доступний' if LOGGER_AVAILABLE else '⚠️ Заглушка'}
|
915 |
+
**Plotly візуалізації**: {'✅ Доступні' if PLOTLY_AVAILABLE else '❌ Недоступні'}
|
916 |
+
**Gradio версія**: {gr.__version__}
|
917 |
+
"""
|
918 |
+
|
919 |
+
gr.Markdown(status_info)
|
920 |
+
|
921 |
+
if not MAI_DX_AVAILABLE:
|
922 |
+
gr.Markdown(f"**Помилка MAI-DX**: {IMPORT_ERROR}")
|
923 |
+
|
924 |
+
# Документація
|
925 |
gr.Markdown("""
|
926 |
+
### 📖 Короткий гайд
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
927 |
|
928 |
+
#### 🚀 Швидкий старт:
|
929 |
+
1. Оберіть готовий тестовий випадок або введіть свій
|
930 |
+
2. Налаштуйте режим діагностики (рекомендується "З бюджетом")
|
931 |
+
3. Натисніть "Запустити діагностику"
|
932 |
+
4. Дочекайтеся результатів та аналізуйте логи
|
|
|
|
|
|
|
|
|
933 |
|
934 |
+
#### 🔧 Режими роботи:
|
935 |
+
- **⚡ Миттєвий**: Швидкий базовий аналіз (~5 сек)
|
936 |
+
- **❓ Тільки питання**: Без тестів, тільки опитування
|
937 |
+
- **💰 З бюджетом**: Збалансований підхід (рекомендується)
|
938 |
+
- **🔓 Без обмежень**: Максимальна точність
|
939 |
+
- **👥 Консенсус**: Найвища точність, але повільно
|
940 |
|
941 |
+
#### 📊 Аналітика:
|
942 |
+
- Переглядайте історію діагнозів у вкладці "Аналітика"
|
943 |
+
- Експортуйте дані для подальшого аналізу
|
944 |
+
- Створюйте детальні звіти по окремих випадках
|
|
|
945 |
|
946 |
+
#### ⚠️ Важливо:
|
947 |
+
- Система призначена для дослідницьких цілей
|
948 |
+
- Не замінює професійну медичну консультацію
|
949 |
+
- При проблемах перевірте статус системи вище
|
950 |
""")
|
951 |
|
952 |
return demo
|
953 |
|
954 |
if __name__ == "__main__":
|
955 |
+
print("🚀 Запуск виправленого MAI-DX Enhanced Research Platform...")
|
956 |
+
print(f"🤖 MAI-DX доступність: {'✅' if MAI_DX_AVAILABLE else '❌'}")
|
957 |
+
print(f"📊 Plotly візуалізації: {'✅' if PLOTLY_AVAILABLE else '❌'}")
|
958 |
+
print(f"📝 Logger: {'✅' if LOGGER_AVAILABLE else '⚠️ заглушка'}")
|
959 |
+
print("🔍 Логи зберігаються у: mai_dx_logs/")
|
960 |
|
961 |
+
demo = create_enhanced_gradio_interface()
|
|
|
|
|
962 |
|
963 |
demo.launch(
|
964 |
server_name="0.0.0.0",
|
965 |
server_port=7860,
|
966 |
share=True,
|
967 |
+
debug=False, # Відключаємо debug для стабільності
|
968 |
+
show_error=True,
|
969 |
+
favicon_path=None,
|
970 |
+
ssl_verify=False
|
971 |
)
|
mai_dx_logs/case_20250710_173828_5d4912.json
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
}
|
requirements.txt
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
loguru
|
2 |
swarms
|
3 |
pydantic
|
|
|
|
1 |
loguru
|
2 |
swarms
|
3 |
pydantic
|
4 |
+
plotly
|