Upload 15 files
Browse files- README.md +5 -17
- base_request.md +45 -45
- deployment_guide.md +133 -133
- handler.py +2 -82
- requirements.txt +1 -5
- test_requests.json +220 -220
- utils.py +360 -360
README.md
CHANGED
@@ -23,8 +23,6 @@ This repository provides a custom handler for deploying the **PULSE-7B** ECG ana
|
|
23 |
|
24 |
**🚀 Enhanced with DeepSeek Integration**: This handler automatically translates PULSE-7B's English medical analysis into patient-friendly Turkish commentary using DeepSeek AI, providing bilingual ECG interpretation for Turkish healthcare professionals and patients.
|
25 |
|
26 |
-
**📧 Automatic Email Processing**: When email configuration is provided, the system automatically processes ECG images sent via email and responds with analysis results, making it a complete email-to-analysis solution.
|
27 |
-
|
28 |
## 🚀 Quick Start
|
29 |
|
30 |
### Prerequisites
|
@@ -36,9 +34,8 @@ This repository provides a custom handler for deploying the **PULSE-7B** ECG ana
|
|
36 |
|
37 |
```
|
38 |
pulse-hf/
|
39 |
-
├── handler.py # Custom inference handler with
|
40 |
├── utils.py # Performance monitoring and DeepSeek client
|
41 |
-
├── email_ecg_processor.py # Email processing module (auto-loaded by handler)
|
42 |
├── requirements.txt # Python dependencies
|
43 |
├── generation_config.json # Model generation configuration
|
44 |
├── test_requests.json # Example request templates
|
@@ -71,26 +68,17 @@ pulse-hf/
|
|
71 |
4. Click **"Create Endpoint"**
|
72 |
5. Wait for the status to change from `Building` → `Initializing` → `Running`
|
73 |
|
74 |
-
### Step 3: Configure
|
|
|
|
|
75 |
|
76 |
-
#### 3.1 DeepSeek API Key (Optional - for Turkish commentary)
|
77 |
1. Go to your endpoint's **"Environment"** tab
|
78 |
2. In **"Secret Env"** section, add:
|
79 |
- **Key**: `deep_key`
|
80 |
- **Value**: Your DeepSeek API key
|
81 |
-
|
82 |
-
#### 3.2 Email Processing (Optional - for automatic email processing)
|
83 |
-
Add these additional environment variables:
|
84 |
-
- **Key**: `mail_host` | **Value**: `imap.gmail.com` (or your email provider)
|
85 |
-
- **Key**: `mail_username` | **Value**: Your email address
|
86 |
-
- **Key**: `mail_pw` | **Value**: Your email app password
|
87 |
-
- **Key**: `hf_key` | **Value**: Your HuggingFace token
|
88 |
-
|
89 |
3. Click **"Update Endpoint"**
|
90 |
|
91 |
-
**Note**:
|
92 |
-
- Without DeepSeek key: Works but no Turkish commentary
|
93 |
-
- Without email config: Works but no automatic email processing
|
94 |
|
95 |
### Step 4: Get Your Endpoint URL
|
96 |
|
|
|
23 |
|
24 |
**🚀 Enhanced with DeepSeek Integration**: This handler automatically translates PULSE-7B's English medical analysis into patient-friendly Turkish commentary using DeepSeek AI, providing bilingual ECG interpretation for Turkish healthcare professionals and patients.
|
25 |
|
|
|
|
|
26 |
## 🚀 Quick Start
|
27 |
|
28 |
### Prerequisites
|
|
|
34 |
|
35 |
```
|
36 |
pulse-hf/
|
37 |
+
├── handler.py # Custom inference handler with DeepSeek integration
|
38 |
├── utils.py # Performance monitoring and DeepSeek client
|
|
|
39 |
├── requirements.txt # Python dependencies
|
40 |
├── generation_config.json # Model generation configuration
|
41 |
├── test_requests.json # Example request templates
|
|
|
68 |
4. Click **"Create Endpoint"**
|
69 |
5. Wait for the status to change from `Building` → `Initializing` → `Running`
|
70 |
|
71 |
+
### Step 3: Configure DeepSeek API Key (Optional)
|
72 |
+
|
73 |
+
To enable Turkish commentary feature:
|
74 |
|
|
|
75 |
1. Go to your endpoint's **"Environment"** tab
|
76 |
2. In **"Secret Env"** section, add:
|
77 |
- **Key**: `deep_key`
|
78 |
- **Value**: Your DeepSeek API key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
3. Click **"Update Endpoint"**
|
80 |
|
81 |
+
**Note**: Without this configuration, the endpoint will work but without Turkish commentary.
|
|
|
|
|
82 |
|
83 |
### Step 4: Get Your Endpoint URL
|
84 |
|
base_request.md
CHANGED
@@ -1,46 +1,46 @@
|
|
1 |
-
https://www.base64-image.de/ # image to base64 for test
|
2 |
-
|
3 |
-
{
|
4 |
-
"inputs": {
|
5 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
6 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
7 |
-
},
|
8 |
-
"parameters": {
|
9 |
-
"max_new_tokens": 512,
|
10 |
-
"temperature": 0.2,
|
11 |
-
"top_p": 0.9,
|
12 |
-
"repetition_penalty": 1.05,
|
13 |
-
"stop": ["</s>"],
|
14 |
-
"return_full_text": false
|
15 |
-
}
|
16 |
-
}
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
{
|
21 |
-
"inputs": {
|
22 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
23 |
-
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2..."
|
24 |
-
},
|
25 |
-
"parameters": {
|
26 |
-
"max_new_tokens": 512,
|
27 |
-
"temperature": 0.2,
|
28 |
-
"top_p": 0.9,
|
29 |
-
"repetition_penalty": 1.05,
|
30 |
-
"stop": ["</s>"],
|
31 |
-
"return_full_text": false
|
32 |
-
}
|
33 |
-
}
|
34 |
-
|
35 |
-
|
36 |
-
{
|
37 |
-
"inputs": {
|
38 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
39 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
40 |
-
},
|
41 |
-
"parameters": {
|
42 |
-
"temperature": 0.2,
|
43 |
-
"enable_turkish_commentary": true,
|
44 |
-
"deepseek_timeout": 30
|
45 |
-
}
|
46 |
}
|
|
|
1 |
+
https://www.base64-image.de/ # image to base64 for test
|
2 |
+
|
3 |
+
{
|
4 |
+
"inputs": {
|
5 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
6 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
7 |
+
},
|
8 |
+
"parameters": {
|
9 |
+
"max_new_tokens": 512,
|
10 |
+
"temperature": 0.2,
|
11 |
+
"top_p": 0.9,
|
12 |
+
"repetition_penalty": 1.05,
|
13 |
+
"stop": ["</s>"],
|
14 |
+
"return_full_text": false
|
15 |
+
}
|
16 |
+
}
|
17 |
+
|
18 |
+
|
19 |
+
|
20 |
+
{
|
21 |
+
"inputs": {
|
22 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
23 |
+
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2..."
|
24 |
+
},
|
25 |
+
"parameters": {
|
26 |
+
"max_new_tokens": 512,
|
27 |
+
"temperature": 0.2,
|
28 |
+
"top_p": 0.9,
|
29 |
+
"repetition_penalty": 1.05,
|
30 |
+
"stop": ["</s>"],
|
31 |
+
"return_full_text": false
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
|
36 |
+
{
|
37 |
+
"inputs": {
|
38 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
39 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
40 |
+
},
|
41 |
+
"parameters": {
|
42 |
+
"temperature": 0.2,
|
43 |
+
"enable_turkish_commentary": true,
|
44 |
+
"deepseek_timeout": 30
|
45 |
+
}
|
46 |
}
|
deployment_guide.md
CHANGED
@@ -1,133 +1,133 @@
|
|
1 |
-
# PULSE-7B Handler Deployment Guide
|
2 |
-
|
3 |
-
## 🚀 Deployment Rehberi
|
4 |
-
|
5 |
-
### Gereksinimler
|
6 |
-
- Python 3.8+
|
7 |
-
- CUDA 11.8+ (GPU kullanımı için)
|
8 |
-
- Minimum 16GB RAM (CPU), 8GB VRAM (GPU)
|
9 |
-
|
10 |
-
### Kurulum
|
11 |
-
|
12 |
-
1. **Bağımlılıkları yükleyin:**
|
13 |
-
```bash
|
14 |
-
pip install -r requirements.txt
|
15 |
-
```
|
16 |
-
|
17 |
-
2. **Flash Attention (isteğe bağlı, performans için):**
|
18 |
-
```bash
|
19 |
-
pip install flash-attn --no-build-isolation
|
20 |
-
```
|
21 |
-
|
22 |
-
### HuggingFace Inference Deployment
|
23 |
-
|
24 |
-
#### 1. Model Repository Yapısı
|
25 |
-
```
|
26 |
-
your-model-repo/
|
27 |
-
├── handler.py
|
28 |
-
├── config.json
|
29 |
-
├── generation_config.json
|
30 |
-
├── requirements.txt
|
31 |
-
├── model.safetensors.index.json
|
32 |
-
├── tokenizer_config.json
|
33 |
-
├── special_tokens_map.json
|
34 |
-
└── tokenizer.model
|
35 |
-
```
|
36 |
-
|
37 |
-
#### 2. Endpoint Oluşturma
|
38 |
-
```bash
|
39 |
-
# HuggingFace CLI ile deploy
|
40 |
-
huggingface-cli login
|
41 |
-
huggingface-cli repo create your-pulse-endpoint --type=space
|
42 |
-
```
|
43 |
-
|
44 |
-
#### 3. Test Requests
|
45 |
-
|
46 |
-
**Image URL ile test:**
|
47 |
-
```bash
|
48 |
-
curl -X POST "YOUR_ENDPOINT_URL" \
|
49 |
-
-H "Content-Type: application/json" \
|
50 |
-
-d '{
|
51 |
-
"inputs": {
|
52 |
-
"query": "Analyze this ECG image",
|
53 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
54 |
-
},
|
55 |
-
"parameters": {
|
56 |
-
"temperature": 0.2,
|
57 |
-
"max_new_tokens": 512
|
58 |
-
}
|
59 |
-
}'
|
60 |
-
```
|
61 |
-
|
62 |
-
**Base64 ile test:**
|
63 |
-
```bash
|
64 |
-
curl -X POST "YOUR_ENDPOINT_URL" \
|
65 |
-
-H "Content-Type: application/json" \
|
66 |
-
-d '{
|
67 |
-
"inputs": {
|
68 |
-
"query": "What do you see in this ECG?",
|
69 |
-
"image": "data:image/jpeg;base64,/9j/4AAQ..."
|
70 |
-
},
|
71 |
-
"parameters": {
|
72 |
-
"temperature": 0.2
|
73 |
-
}
|
74 |
-
}'
|
75 |
-
```
|
76 |
-
|
77 |
-
### Performans Optimizasyonları
|
78 |
-
|
79 |
-
#### GPU Memory Optimizasyonu
|
80 |
-
- `torch_dtype=torch.bfloat16` kullanın
|
81 |
-
- `low_cpu_mem_usage=True` ayarlayın
|
82 |
-
- `device_map="auto"` ile otomatik dağıtım
|
83 |
-
|
84 |
-
#### CPU Optimizasyonu
|
85 |
-
- `torch_dtype=torch.float32` kullanın
|
86 |
-
- Thread sayısını ayarlayın: `torch.set_num_threads(4)`
|
87 |
-
|
88 |
-
### Monitoring ve Debugging
|
89 |
-
|
90 |
-
#### Log Seviyeleri
|
91 |
-
```python
|
92 |
-
import logging
|
93 |
-
logging.basicConfig(level=logging.INFO)
|
94 |
-
```
|
95 |
-
|
96 |
-
#### Memory Usage
|
97 |
-
```python
|
98 |
-
import torch
|
99 |
-
print(f"GPU Memory: {torch.cuda.memory_allocated()/1024**3:.2f}GB")
|
100 |
-
```
|
101 |
-
|
102 |
-
### Troubleshooting
|
103 |
-
|
104 |
-
#### Common Issues:
|
105 |
-
|
106 |
-
1. **CUDA Out of Memory**
|
107 |
-
- Batch size'ı azaltın
|
108 |
-
- `max_new_tokens` değerini düşürün
|
109 |
-
- Gradient checkpointing kullanın
|
110 |
-
|
111 |
-
2. **Slow Image Processing**
|
112 |
-
- Image timeout değerini artırın
|
113 |
-
- Image resize threshold ayarlayın
|
114 |
-
|
115 |
-
3. **Model Loading Issues**
|
116 |
-
- HuggingFace token'ını kontrol edin
|
117 |
-
- Network bağlantısını doğrulayın
|
118 |
-
- Cache dizinini temizleyin
|
119 |
-
|
120 |
-
### Security Best Practices
|
121 |
-
|
122 |
-
- Image URL'leri validate edin
|
123 |
-
- Base64 boyut limitlerini ayarlayın
|
124 |
-
- Rate limiting uygulayın
|
125 |
-
- Input sanitization yapın
|
126 |
-
|
127 |
-
### Monitoring Metrics
|
128 |
-
|
129 |
-
- Response time
|
130 |
-
- Memory usage
|
131 |
-
- Error rates
|
132 |
-
- Image processing success rate
|
133 |
-
- Token generation speed
|
|
|
1 |
+
# PULSE-7B Handler Deployment Guide
|
2 |
+
|
3 |
+
## 🚀 Deployment Rehberi
|
4 |
+
|
5 |
+
### Gereksinimler
|
6 |
+
- Python 3.8+
|
7 |
+
- CUDA 11.8+ (GPU kullanımı için)
|
8 |
+
- Minimum 16GB RAM (CPU), 8GB VRAM (GPU)
|
9 |
+
|
10 |
+
### Kurulum
|
11 |
+
|
12 |
+
1. **Bağımlılıkları yükleyin:**
|
13 |
+
```bash
|
14 |
+
pip install -r requirements.txt
|
15 |
+
```
|
16 |
+
|
17 |
+
2. **Flash Attention (isteğe bağlı, performans için):**
|
18 |
+
```bash
|
19 |
+
pip install flash-attn --no-build-isolation
|
20 |
+
```
|
21 |
+
|
22 |
+
### HuggingFace Inference Deployment
|
23 |
+
|
24 |
+
#### 1. Model Repository Yapısı
|
25 |
+
```
|
26 |
+
your-model-repo/
|
27 |
+
├── handler.py
|
28 |
+
├── config.json
|
29 |
+
├── generation_config.json
|
30 |
+
├── requirements.txt
|
31 |
+
├── model.safetensors.index.json
|
32 |
+
├── tokenizer_config.json
|
33 |
+
├── special_tokens_map.json
|
34 |
+
└── tokenizer.model
|
35 |
+
```
|
36 |
+
|
37 |
+
#### 2. Endpoint Oluşturma
|
38 |
+
```bash
|
39 |
+
# HuggingFace CLI ile deploy
|
40 |
+
huggingface-cli login
|
41 |
+
huggingface-cli repo create your-pulse-endpoint --type=space
|
42 |
+
```
|
43 |
+
|
44 |
+
#### 3. Test Requests
|
45 |
+
|
46 |
+
**Image URL ile test:**
|
47 |
+
```bash
|
48 |
+
curl -X POST "YOUR_ENDPOINT_URL" \
|
49 |
+
-H "Content-Type: application/json" \
|
50 |
+
-d '{
|
51 |
+
"inputs": {
|
52 |
+
"query": "Analyze this ECG image",
|
53 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
54 |
+
},
|
55 |
+
"parameters": {
|
56 |
+
"temperature": 0.2,
|
57 |
+
"max_new_tokens": 512
|
58 |
+
}
|
59 |
+
}'
|
60 |
+
```
|
61 |
+
|
62 |
+
**Base64 ile test:**
|
63 |
+
```bash
|
64 |
+
curl -X POST "YOUR_ENDPOINT_URL" \
|
65 |
+
-H "Content-Type: application/json" \
|
66 |
+
-d '{
|
67 |
+
"inputs": {
|
68 |
+
"query": "What do you see in this ECG?",
|
69 |
+
"image": "data:image/jpeg;base64,/9j/4AAQ..."
|
70 |
+
},
|
71 |
+
"parameters": {
|
72 |
+
"temperature": 0.2
|
73 |
+
}
|
74 |
+
}'
|
75 |
+
```
|
76 |
+
|
77 |
+
### Performans Optimizasyonları
|
78 |
+
|
79 |
+
#### GPU Memory Optimizasyonu
|
80 |
+
- `torch_dtype=torch.bfloat16` kullanın
|
81 |
+
- `low_cpu_mem_usage=True` ayarlayın
|
82 |
+
- `device_map="auto"` ile otomatik dağıtım
|
83 |
+
|
84 |
+
#### CPU Optimizasyonu
|
85 |
+
- `torch_dtype=torch.float32` kullanın
|
86 |
+
- Thread sayısını ayarlayın: `torch.set_num_threads(4)`
|
87 |
+
|
88 |
+
### Monitoring ve Debugging
|
89 |
+
|
90 |
+
#### Log Seviyeleri
|
91 |
+
```python
|
92 |
+
import logging
|
93 |
+
logging.basicConfig(level=logging.INFO)
|
94 |
+
```
|
95 |
+
|
96 |
+
#### Memory Usage
|
97 |
+
```python
|
98 |
+
import torch
|
99 |
+
print(f"GPU Memory: {torch.cuda.memory_allocated()/1024**3:.2f}GB")
|
100 |
+
```
|
101 |
+
|
102 |
+
### Troubleshooting
|
103 |
+
|
104 |
+
#### Common Issues:
|
105 |
+
|
106 |
+
1. **CUDA Out of Memory**
|
107 |
+
- Batch size'ı azaltın
|
108 |
+
- `max_new_tokens` değerini düşürün
|
109 |
+
- Gradient checkpointing kullanın
|
110 |
+
|
111 |
+
2. **Slow Image Processing**
|
112 |
+
- Image timeout değerini artırın
|
113 |
+
- Image resize threshold ayarlayın
|
114 |
+
|
115 |
+
3. **Model Loading Issues**
|
116 |
+
- HuggingFace token'ını kontrol edin
|
117 |
+
- Network bağlantısını doğrulayın
|
118 |
+
- Cache dizinini temizleyin
|
119 |
+
|
120 |
+
### Security Best Practices
|
121 |
+
|
122 |
+
- Image URL'leri validate edin
|
123 |
+
- Base64 boyut limitlerini ayarlayın
|
124 |
+
- Rate limiting uygulayın
|
125 |
+
- Input sanitization yapın
|
126 |
+
|
127 |
+
### Monitoring Metrics
|
128 |
+
|
129 |
+
- Response time
|
130 |
+
- Memory usage
|
131 |
+
- Error rates
|
132 |
+
- Image processing success rate
|
133 |
+
- Token generation speed
|
handler.py
CHANGED
@@ -11,8 +11,6 @@ from io import BytesIO
|
|
11 |
from PIL import Image
|
12 |
import requests
|
13 |
import time
|
14 |
-
import threading
|
15 |
-
import os
|
16 |
|
17 |
# Import utilities if available
|
18 |
try:
|
@@ -30,16 +28,6 @@ except ImportError:
|
|
30 |
deepseek_client = None
|
31 |
print("⚠️ Utils module not found - performance monitoring and DeepSeek integration disabled")
|
32 |
|
33 |
-
# Import email processor if available
|
34 |
-
try:
|
35 |
-
from email_ecg_processor import EmailECGProcessor
|
36 |
-
EMAIL_PROCESSOR_AVAILABLE = True
|
37 |
-
print("📧 Email ECG Processor module found - email processing will be enabled")
|
38 |
-
except ImportError:
|
39 |
-
EMAIL_PROCESSOR_AVAILABLE = False
|
40 |
-
EmailECGProcessor = None
|
41 |
-
print("📭 Email ECG Processor module not found - email processing disabled")
|
42 |
-
|
43 |
|
44 |
class EndpointHandler:
|
45 |
def __init__(self, path=""):
|
@@ -57,11 +45,6 @@ class EndpointHandler:
|
|
57 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
58 |
print(f"🖥️ Running on: {self.device}")
|
59 |
|
60 |
-
# Initialize email processor
|
61 |
-
self.email_processor = None
|
62 |
-
self.email_thread = None
|
63 |
-
self._init_email_processor()
|
64 |
-
|
65 |
try:
|
66 |
# First attempt - using pipeline (easiest and most stable way)
|
67 |
from transformers import pipeline
|
@@ -124,47 +107,6 @@ class EndpointHandler:
|
|
124 |
else:
|
125 |
self.use_pipeline = True
|
126 |
|
127 |
-
def _init_email_processor(self):
|
128 |
-
"""Initialize email processor in background thread"""
|
129 |
-
if not EMAIL_PROCESSOR_AVAILABLE:
|
130 |
-
print("📭 Email processor not available - skipping email initialization")
|
131 |
-
return
|
132 |
-
|
133 |
-
# Check if email configuration is available
|
134 |
-
required_email_vars = ['mail_username', 'mail_pw', 'hf_key']
|
135 |
-
missing_vars = [var for var in required_email_vars if not os.getenv(var)]
|
136 |
-
|
137 |
-
if missing_vars:
|
138 |
-
print(f"📭 Email processor disabled - missing environment variables: {', '.join(missing_vars)}")
|
139 |
-
return
|
140 |
-
|
141 |
-
try:
|
142 |
-
print("📧 Initializing email processor...")
|
143 |
-
self.email_processor = EmailECGProcessor()
|
144 |
-
|
145 |
-
# Start email processor in background thread
|
146 |
-
self.email_thread = threading.Thread(
|
147 |
-
target=self._run_email_processor,
|
148 |
-
daemon=True,
|
149 |
-
name="EmailProcessor"
|
150 |
-
)
|
151 |
-
self.email_thread.start()
|
152 |
-
print("✅ Email processor started successfully in background thread")
|
153 |
-
|
154 |
-
except Exception as e:
|
155 |
-
print(f"❌ Failed to initialize email processor: {e}")
|
156 |
-
self.email_processor = None
|
157 |
-
self.email_thread = None
|
158 |
-
|
159 |
-
def _run_email_processor(self):
|
160 |
-
"""Run email processor in background thread"""
|
161 |
-
try:
|
162 |
-
if self.email_processor:
|
163 |
-
print("📧 Email processor thread started - checking emails every 5 minutes")
|
164 |
-
self.email_processor.run_email_processor(check_interval=300)
|
165 |
-
except Exception as e:
|
166 |
-
print(f"❌ Email processor thread error: {e}")
|
167 |
-
|
168 |
def process_image_input(self, image_input):
|
169 |
"""
|
170 |
Handle both URL and base64 image inputs like a champ!
|
@@ -287,36 +229,14 @@ class EndpointHandler:
|
|
287 |
def health_check(self) -> Dict[str, Any]:
|
288 |
"""Health check endpoint"""
|
289 |
if UTILS_AVAILABLE:
|
290 |
-
|
291 |
else:
|
292 |
-
|
293 |
'status': 'healthy',
|
294 |
'model': 'PULSE-7B',
|
295 |
'timestamp': time.time(),
|
296 |
'handler_version': '2.0.0'
|
297 |
}
|
298 |
-
|
299 |
-
# Add email processor status
|
300 |
-
if EMAIL_PROCESSOR_AVAILABLE and self.email_processor:
|
301 |
-
health['email_processor'] = {
|
302 |
-
'status': 'running',
|
303 |
-
'thread_alive': self.email_thread.is_alive() if self.email_thread else False,
|
304 |
-
'configuration': 'configured'
|
305 |
-
}
|
306 |
-
elif EMAIL_PROCESSOR_AVAILABLE:
|
307 |
-
health['email_processor'] = {
|
308 |
-
'status': 'available_but_not_configured',
|
309 |
-
'thread_alive': False,
|
310 |
-
'configuration': 'missing_environment_variables'
|
311 |
-
}
|
312 |
-
else:
|
313 |
-
health['email_processor'] = {
|
314 |
-
'status': 'not_available',
|
315 |
-
'thread_alive': False,
|
316 |
-
'configuration': 'module_not_found'
|
317 |
-
}
|
318 |
-
|
319 |
-
return health
|
320 |
|
321 |
def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
322 |
"""
|
|
|
11 |
from PIL import Image
|
12 |
import requests
|
13 |
import time
|
|
|
|
|
14 |
|
15 |
# Import utilities if available
|
16 |
try:
|
|
|
28 |
deepseek_client = None
|
29 |
print("⚠️ Utils module not found - performance monitoring and DeepSeek integration disabled")
|
30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
class EndpointHandler:
|
33 |
def __init__(self, path=""):
|
|
|
45 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
46 |
print(f"🖥️ Running on: {self.device}")
|
47 |
|
|
|
|
|
|
|
|
|
|
|
48 |
try:
|
49 |
# First attempt - using pipeline (easiest and most stable way)
|
50 |
from transformers import pipeline
|
|
|
107 |
else:
|
108 |
self.use_pipeline = True
|
109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
def process_image_input(self, image_input):
|
111 |
"""
|
112 |
Handle both URL and base64 image inputs like a champ!
|
|
|
229 |
def health_check(self) -> Dict[str, Any]:
|
230 |
"""Health check endpoint"""
|
231 |
if UTILS_AVAILABLE:
|
232 |
+
return create_health_check()
|
233 |
else:
|
234 |
+
return {
|
235 |
'status': 'healthy',
|
236 |
'model': 'PULSE-7B',
|
237 |
'timestamp': time.time(),
|
238 |
'handler_version': '2.0.0'
|
239 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
|
241 |
def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
242 |
"""
|
requirements.txt
CHANGED
@@ -19,8 +19,4 @@ typing-extensions>=4.0.0
|
|
19 |
psutil>=5.8.0
|
20 |
|
21 |
# HuggingFace Inference specific
|
22 |
-
huggingface-hub>=0.16.0
|
23 |
-
|
24 |
-
# Email processing dependencies
|
25 |
-
email-validator>=2.0.0
|
26 |
-
chardet>=5.0.0
|
|
|
19 |
psutil>=5.8.0
|
20 |
|
21 |
# HuggingFace Inference specific
|
22 |
+
huggingface-hub>=0.16.0
|
|
|
|
|
|
|
|
test_requests.json
CHANGED
@@ -1,220 +1,220 @@
|
|
1 |
-
{
|
2 |
-
"test_requests": {
|
3 |
-
"image_url_request": {
|
4 |
-
"description": "ECG analizi için image URL ile istek",
|
5 |
-
"request": {
|
6 |
-
"inputs": {
|
7 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
8 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
9 |
-
},
|
10 |
-
"parameters": {
|
11 |
-
"max_new_tokens": 512,
|
12 |
-
"temperature": 0.2,
|
13 |
-
"top_p": 0.9,
|
14 |
-
"repetition_penalty": 1.05,
|
15 |
-
"stop": ["</s>"],
|
16 |
-
"return_full_text": false
|
17 |
-
}
|
18 |
-
}
|
19 |
-
},
|
20 |
-
|
21 |
-
"base64_request": {
|
22 |
-
"description": "ECG analizi için base64 encoded image ile istek",
|
23 |
-
"request": {
|
24 |
-
"inputs": {
|
25 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
26 |
-
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
|
27 |
-
},
|
28 |
-
"parameters": {
|
29 |
-
"max_new_tokens": 512,
|
30 |
-
"temperature": 0.2,
|
31 |
-
"top_p": 0.9,
|
32 |
-
"repetition_penalty": 1.05,
|
33 |
-
"stop": ["</s>"],
|
34 |
-
"return_full_text": false
|
35 |
-
}
|
36 |
-
}
|
37 |
-
},
|
38 |
-
|
39 |
-
"minimal_url_request": {
|
40 |
-
"description": "Minimal parametrelerle image URL isteği",
|
41 |
-
"request": {
|
42 |
-
"inputs": {
|
43 |
-
"query": "Analyze this ECG image.",
|
44 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
45 |
-
},
|
46 |
-
"parameters": {
|
47 |
-
"temperature": 0.2
|
48 |
-
}
|
49 |
-
}
|
50 |
-
},
|
51 |
-
|
52 |
-
"minimal_base64_request": {
|
53 |
-
"description": "Minimal parametrelerle base64 isteği",
|
54 |
-
"request": {
|
55 |
-
"inputs": {
|
56 |
-
"query": "Analyze this ECG image.",
|
57 |
-
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
|
58 |
-
},
|
59 |
-
"parameters": {
|
60 |
-
"temperature": 0.2
|
61 |
-
}
|
62 |
-
}
|
63 |
-
},
|
64 |
-
|
65 |
-
"text_only_request": {
|
66 |
-
"description": "Sadece text ile istek (image olmadan)",
|
67 |
-
"request": {
|
68 |
-
"inputs": {
|
69 |
-
"query": "What are the common causes of atrial fibrillation?"
|
70 |
-
},
|
71 |
-
"parameters": {
|
72 |
-
"max_new_tokens": 256,
|
73 |
-
"temperature": 0.3,
|
74 |
-
"top_p": 0.9
|
75 |
-
}
|
76 |
-
}
|
77 |
-
},
|
78 |
-
|
79 |
-
"advanced_parameters_request": {
|
80 |
-
"description": "Gelişmiş parametrelerle image URL isteği",
|
81 |
-
"request": {
|
82 |
-
"inputs": {
|
83 |
-
"query": "Provide detailed ECG analysis including rhythm, rate, intervals, and potential abnormalities.",
|
84 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
85 |
-
},
|
86 |
-
"parameters": {
|
87 |
-
"max_new_tokens": 1024,
|
88 |
-
"temperature": 0.1,
|
89 |
-
"top_p": 0.95,
|
90 |
-
"repetition_penalty": 1.1,
|
91 |
-
"stop": ["</s>", "\n\n"],
|
92 |
-
"return_full_text": false,
|
93 |
-
"do_sample": true
|
94 |
-
}
|
95 |
-
}
|
96 |
-
},
|
97 |
-
|
98 |
-
"deepseek_enabled_request": {
|
99 |
-
"description": "DeepSeek Türkçe yorum özelliği aktif - Image URL",
|
100 |
-
"request": {
|
101 |
-
"inputs": {
|
102 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
103 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
104 |
-
},
|
105 |
-
"parameters": {
|
106 |
-
"max_new_tokens": 512,
|
107 |
-
"temperature": 0.2,
|
108 |
-
"top_p": 0.9,
|
109 |
-
"repetition_penalty": 1.05,
|
110 |
-
"enable_turkish_commentary": true,
|
111 |
-
"deepseek_timeout": 30
|
112 |
-
}
|
113 |
-
}
|
114 |
-
},
|
115 |
-
|
116 |
-
"deepseek_enabled_base64_request": {
|
117 |
-
"description": "DeepSeek Türkçe yorum özelliği aktif - Base64",
|
118 |
-
"request": {
|
119 |
-
"inputs": {
|
120 |
-
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
121 |
-
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
|
122 |
-
},
|
123 |
-
"parameters": {
|
124 |
-
"max_new_tokens": 512,
|
125 |
-
"temperature": 0.2,
|
126 |
-
"enable_turkish_commentary": true,
|
127 |
-
"deepseek_timeout": 25
|
128 |
-
}
|
129 |
-
}
|
130 |
-
},
|
131 |
-
|
132 |
-
"deepseek_disabled_request": {
|
133 |
-
"description": "DeepSeek Türkçe yorum özelliği deaktif",
|
134 |
-
"request": {
|
135 |
-
"inputs": {
|
136 |
-
"query": "Analyze this ECG image briefly.",
|
137 |
-
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
138 |
-
},
|
139 |
-
"parameters": {
|
140 |
-
"temperature": 0.2,
|
141 |
-
"enable_turkish_commentary": false
|
142 |
-
}
|
143 |
-
}
|
144 |
-
}
|
145 |
-
},
|
146 |
-
|
147 |
-
"expected_response_format": {
|
148 |
-
"description": "Beklenen response formatı (DeepSeek aktif)",
|
149 |
-
"format": [
|
150 |
-
{
|
151 |
-
"generated_text": "Answer: This ECG image shows a sinus rhythm with a normal heart rate...",
|
152 |
-
"model": "PULSE-7B",
|
153 |
-
"processing_method": "pipeline",
|
154 |
-
"comment_text": "Bu EKG sonucu normal sinüs ritmi göstermektedir. Kalp atış hızı normaldir ve düzenli bir ritim görülmektedir...",
|
155 |
-
"commentary_model": "deepseek-chat",
|
156 |
-
"commentary_tokens": 85,
|
157 |
-
"commentary_status": "success"
|
158 |
-
}
|
159 |
-
]
|
160 |
-
},
|
161 |
-
|
162 |
-
"expected_response_format_no_commentary": {
|
163 |
-
"description": "Beklenen response formatı (DeepSeek deaktif)",
|
164 |
-
"format": [
|
165 |
-
{
|
166 |
-
"generated_text": "Answer: This ECG image shows a sinus rhythm with a normal heart rate...",
|
167 |
-
"model": "PULSE-7B",
|
168 |
-
"processing_method": "pipeline"
|
169 |
-
}
|
170 |
-
]
|
171 |
-
},
|
172 |
-
|
173 |
-
"error_response_format": {
|
174 |
-
"description": "Hata durumunda response formatı",
|
175 |
-
"format": [
|
176 |
-
{
|
177 |
-
"generated_text": "",
|
178 |
-
"error": "Error message here...",
|
179 |
-
"model": "PULSE-7B",
|
180 |
-
"handler": "Ubden® Team Enhanced Handler",
|
181 |
-
"success": false
|
182 |
-
}
|
183 |
-
]
|
184 |
-
},
|
185 |
-
|
186 |
-
"usage_notes": {
|
187 |
-
"supported_image_formats": [
|
188 |
-
"JPEG", "PNG", "GIF", "BMP", "WebP"
|
189 |
-
],
|
190 |
-
"supported_input_methods": [
|
191 |
-
"HTTP/HTTPS URL",
|
192 |
-
"Base64 encoded with data URI prefix",
|
193 |
-
"Raw base64 string"
|
194 |
-
],
|
195 |
-
"parameter_limits": {
|
196 |
-
"max_new_tokens": "1-2048",
|
197 |
-
"temperature": "0.01-2.0",
|
198 |
-
"top_p": "0.01-1.0",
|
199 |
-
"repetition_penalty": "1.0-2.0"
|
200 |
-
},
|
201 |
-
"query_field_alternatives": [
|
202 |
-
"query", "text", "prompt"
|
203 |
-
],
|
204 |
-
"deepseek_integration": {
|
205 |
-
"enable_turkish_commentary": "true/false (default: true)",
|
206 |
-
"deepseek_timeout": "10-60 seconds (default: 30)",
|
207 |
-
"environment_variable": "deep_key (DeepSeek API key)",
|
208 |
-
"commentary_status_values": [
|
209 |
-
"success", "failed", "unavailable", "api_key_missing", "no_text"
|
210 |
-
]
|
211 |
-
},
|
212 |
-
"response_fields_with_commentary": [
|
213 |
-
"generated_text", "model", "processing_method",
|
214 |
-
"comment_text", "commentary_model", "commentary_tokens", "commentary_status"
|
215 |
-
],
|
216 |
-
"response_fields_without_commentary": [
|
217 |
-
"generated_text", "model", "processing_method"
|
218 |
-
]
|
219 |
-
}
|
220 |
-
}
|
|
|
1 |
+
{
|
2 |
+
"test_requests": {
|
3 |
+
"image_url_request": {
|
4 |
+
"description": "ECG analizi için image URL ile istek",
|
5 |
+
"request": {
|
6 |
+
"inputs": {
|
7 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
8 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
9 |
+
},
|
10 |
+
"parameters": {
|
11 |
+
"max_new_tokens": 512,
|
12 |
+
"temperature": 0.2,
|
13 |
+
"top_p": 0.9,
|
14 |
+
"repetition_penalty": 1.05,
|
15 |
+
"stop": ["</s>"],
|
16 |
+
"return_full_text": false
|
17 |
+
}
|
18 |
+
}
|
19 |
+
},
|
20 |
+
|
21 |
+
"base64_request": {
|
22 |
+
"description": "ECG analizi için base64 encoded image ile istek",
|
23 |
+
"request": {
|
24 |
+
"inputs": {
|
25 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
26 |
+
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
|
27 |
+
},
|
28 |
+
"parameters": {
|
29 |
+
"max_new_tokens": 512,
|
30 |
+
"temperature": 0.2,
|
31 |
+
"top_p": 0.9,
|
32 |
+
"repetition_penalty": 1.05,
|
33 |
+
"stop": ["</s>"],
|
34 |
+
"return_full_text": false
|
35 |
+
}
|
36 |
+
}
|
37 |
+
},
|
38 |
+
|
39 |
+
"minimal_url_request": {
|
40 |
+
"description": "Minimal parametrelerle image URL isteği",
|
41 |
+
"request": {
|
42 |
+
"inputs": {
|
43 |
+
"query": "Analyze this ECG image.",
|
44 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
45 |
+
},
|
46 |
+
"parameters": {
|
47 |
+
"temperature": 0.2
|
48 |
+
}
|
49 |
+
}
|
50 |
+
},
|
51 |
+
|
52 |
+
"minimal_base64_request": {
|
53 |
+
"description": "Minimal parametrelerle base64 isteği",
|
54 |
+
"request": {
|
55 |
+
"inputs": {
|
56 |
+
"query": "Analyze this ECG image.",
|
57 |
+
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
|
58 |
+
},
|
59 |
+
"parameters": {
|
60 |
+
"temperature": 0.2
|
61 |
+
}
|
62 |
+
}
|
63 |
+
},
|
64 |
+
|
65 |
+
"text_only_request": {
|
66 |
+
"description": "Sadece text ile istek (image olmadan)",
|
67 |
+
"request": {
|
68 |
+
"inputs": {
|
69 |
+
"query": "What are the common causes of atrial fibrillation?"
|
70 |
+
},
|
71 |
+
"parameters": {
|
72 |
+
"max_new_tokens": 256,
|
73 |
+
"temperature": 0.3,
|
74 |
+
"top_p": 0.9
|
75 |
+
}
|
76 |
+
}
|
77 |
+
},
|
78 |
+
|
79 |
+
"advanced_parameters_request": {
|
80 |
+
"description": "Gelişmiş parametrelerle image URL isteği",
|
81 |
+
"request": {
|
82 |
+
"inputs": {
|
83 |
+
"query": "Provide detailed ECG analysis including rhythm, rate, intervals, and potential abnormalities.",
|
84 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
85 |
+
},
|
86 |
+
"parameters": {
|
87 |
+
"max_new_tokens": 1024,
|
88 |
+
"temperature": 0.1,
|
89 |
+
"top_p": 0.95,
|
90 |
+
"repetition_penalty": 1.1,
|
91 |
+
"stop": ["</s>", "\n\n"],
|
92 |
+
"return_full_text": false,
|
93 |
+
"do_sample": true
|
94 |
+
}
|
95 |
+
}
|
96 |
+
},
|
97 |
+
|
98 |
+
"deepseek_enabled_request": {
|
99 |
+
"description": "DeepSeek Türkçe yorum özelliği aktif - Image URL",
|
100 |
+
"request": {
|
101 |
+
"inputs": {
|
102 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
103 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
104 |
+
},
|
105 |
+
"parameters": {
|
106 |
+
"max_new_tokens": 512,
|
107 |
+
"temperature": 0.2,
|
108 |
+
"top_p": 0.9,
|
109 |
+
"repetition_penalty": 1.05,
|
110 |
+
"enable_turkish_commentary": true,
|
111 |
+
"deepseek_timeout": 30
|
112 |
+
}
|
113 |
+
}
|
114 |
+
},
|
115 |
+
|
116 |
+
"deepseek_enabled_base64_request": {
|
117 |
+
"description": "DeepSeek Türkçe yorum özelliği aktif - Base64",
|
118 |
+
"request": {
|
119 |
+
"inputs": {
|
120 |
+
"query": "What are the main features and diagnosis in this ECG image? Provide a concise, clinical answer.",
|
121 |
+
"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
|
122 |
+
},
|
123 |
+
"parameters": {
|
124 |
+
"max_new_tokens": 512,
|
125 |
+
"temperature": 0.2,
|
126 |
+
"enable_turkish_commentary": true,
|
127 |
+
"deepseek_timeout": 25
|
128 |
+
}
|
129 |
+
}
|
130 |
+
},
|
131 |
+
|
132 |
+
"deepseek_disabled_request": {
|
133 |
+
"description": "DeepSeek Türkçe yorum özelliği deaktif",
|
134 |
+
"request": {
|
135 |
+
"inputs": {
|
136 |
+
"query": "Analyze this ECG image briefly.",
|
137 |
+
"image": "https://i.imgur.com/7uuejqO.jpeg"
|
138 |
+
},
|
139 |
+
"parameters": {
|
140 |
+
"temperature": 0.2,
|
141 |
+
"enable_turkish_commentary": false
|
142 |
+
}
|
143 |
+
}
|
144 |
+
}
|
145 |
+
},
|
146 |
+
|
147 |
+
"expected_response_format": {
|
148 |
+
"description": "Beklenen response formatı (DeepSeek aktif)",
|
149 |
+
"format": [
|
150 |
+
{
|
151 |
+
"generated_text": "Answer: This ECG image shows a sinus rhythm with a normal heart rate...",
|
152 |
+
"model": "PULSE-7B",
|
153 |
+
"processing_method": "pipeline",
|
154 |
+
"comment_text": "Bu EKG sonucu normal sinüs ritmi göstermektedir. Kalp atış hızı normaldir ve düzenli bir ritim görülmektedir...",
|
155 |
+
"commentary_model": "deepseek-chat",
|
156 |
+
"commentary_tokens": 85,
|
157 |
+
"commentary_status": "success"
|
158 |
+
}
|
159 |
+
]
|
160 |
+
},
|
161 |
+
|
162 |
+
"expected_response_format_no_commentary": {
|
163 |
+
"description": "Beklenen response formatı (DeepSeek deaktif)",
|
164 |
+
"format": [
|
165 |
+
{
|
166 |
+
"generated_text": "Answer: This ECG image shows a sinus rhythm with a normal heart rate...",
|
167 |
+
"model": "PULSE-7B",
|
168 |
+
"processing_method": "pipeline"
|
169 |
+
}
|
170 |
+
]
|
171 |
+
},
|
172 |
+
|
173 |
+
"error_response_format": {
|
174 |
+
"description": "Hata durumunda response formatı",
|
175 |
+
"format": [
|
176 |
+
{
|
177 |
+
"generated_text": "",
|
178 |
+
"error": "Error message here...",
|
179 |
+
"model": "PULSE-7B",
|
180 |
+
"handler": "Ubden® Team Enhanced Handler",
|
181 |
+
"success": false
|
182 |
+
}
|
183 |
+
]
|
184 |
+
},
|
185 |
+
|
186 |
+
"usage_notes": {
|
187 |
+
"supported_image_formats": [
|
188 |
+
"JPEG", "PNG", "GIF", "BMP", "WebP"
|
189 |
+
],
|
190 |
+
"supported_input_methods": [
|
191 |
+
"HTTP/HTTPS URL",
|
192 |
+
"Base64 encoded with data URI prefix",
|
193 |
+
"Raw base64 string"
|
194 |
+
],
|
195 |
+
"parameter_limits": {
|
196 |
+
"max_new_tokens": "1-2048",
|
197 |
+
"temperature": "0.01-2.0",
|
198 |
+
"top_p": "0.01-1.0",
|
199 |
+
"repetition_penalty": "1.0-2.0"
|
200 |
+
},
|
201 |
+
"query_field_alternatives": [
|
202 |
+
"query", "text", "prompt"
|
203 |
+
],
|
204 |
+
"deepseek_integration": {
|
205 |
+
"enable_turkish_commentary": "true/false (default: true)",
|
206 |
+
"deepseek_timeout": "10-60 seconds (default: 30)",
|
207 |
+
"environment_variable": "deep_key (DeepSeek API key)",
|
208 |
+
"commentary_status_values": [
|
209 |
+
"success", "failed", "unavailable", "api_key_missing", "no_text"
|
210 |
+
]
|
211 |
+
},
|
212 |
+
"response_fields_with_commentary": [
|
213 |
+
"generated_text", "model", "processing_method",
|
214 |
+
"comment_text", "commentary_model", "commentary_tokens", "commentary_status"
|
215 |
+
],
|
216 |
+
"response_fields_without_commentary": [
|
217 |
+
"generated_text", "model", "processing_method"
|
218 |
+
]
|
219 |
+
}
|
220 |
+
}
|
utils.py
CHANGED
@@ -1,360 +1,360 @@
|
|
1 |
-
"""
|
2 |
-
PULSE-7B Handler Utilities
|
3 |
-
Ubden® Team - Performance monitoring and helper functions
|
4 |
-
"""
|
5 |
-
|
6 |
-
import time
|
7 |
-
import torch
|
8 |
-
import psutil
|
9 |
-
import logging
|
10 |
-
import os
|
11 |
-
import json
|
12 |
-
import requests
|
13 |
-
from typing import Dict, Any, Optional
|
14 |
-
from functools import wraps
|
15 |
-
|
16 |
-
# Configure logging
|
17 |
-
logging.basicConfig(level=logging.INFO)
|
18 |
-
logger = logging.getLogger(__name__)
|
19 |
-
|
20 |
-
class PerformanceMonitor:
|
21 |
-
"""Performance monitoring utilities for PULSE-7B handler"""
|
22 |
-
|
23 |
-
def __init__(self):
|
24 |
-
self.metrics = {
|
25 |
-
'total_requests': 0,
|
26 |
-
'successful_requests': 0,
|
27 |
-
'failed_requests': 0,
|
28 |
-
'image_url_requests': 0,
|
29 |
-
'base64_requests': 0,
|
30 |
-
'text_only_requests': 0,
|
31 |
-
'total_generation_time': 0.0,
|
32 |
-
'total_image_processing_time': 0.0
|
33 |
-
}
|
34 |
-
|
35 |
-
def log_request(self, request_type: str, success: bool,
|
36 |
-
generation_time: float = 0.0,
|
37 |
-
image_processing_time: float = 0.0):
|
38 |
-
"""Log request metrics"""
|
39 |
-
self.metrics['total_requests'] += 1
|
40 |
-
|
41 |
-
if success:
|
42 |
-
self.metrics['successful_requests'] += 1
|
43 |
-
else:
|
44 |
-
self.metrics['failed_requests'] += 1
|
45 |
-
|
46 |
-
if request_type == 'image_url':
|
47 |
-
self.metrics['image_url_requests'] += 1
|
48 |
-
elif request_type == 'base64':
|
49 |
-
self.metrics['base64_requests'] += 1
|
50 |
-
else:
|
51 |
-
self.metrics['text_only_requests'] += 1
|
52 |
-
|
53 |
-
self.metrics['total_generation_time'] += generation_time
|
54 |
-
self.metrics['total_image_processing_time'] += image_processing_time
|
55 |
-
|
56 |
-
def get_stats(self) -> Dict[str, Any]:
|
57 |
-
"""Get current performance statistics"""
|
58 |
-
total_requests = self.metrics['total_requests']
|
59 |
-
if total_requests == 0:
|
60 |
-
return self.metrics
|
61 |
-
|
62 |
-
success_rate = (self.metrics['successful_requests'] / total_requests) * 100
|
63 |
-
avg_generation_time = self.metrics['total_generation_time'] / total_requests
|
64 |
-
avg_image_processing_time = self.metrics['total_image_processing_time'] / max(
|
65 |
-
self.metrics['image_url_requests'] + self.metrics['base64_requests'], 1
|
66 |
-
)
|
67 |
-
|
68 |
-
return {
|
69 |
-
**self.metrics,
|
70 |
-
'success_rate_percent': round(success_rate, 2),
|
71 |
-
'avg_generation_time_seconds': round(avg_generation_time, 3),
|
72 |
-
'avg_image_processing_time_seconds': round(avg_image_processing_time, 3)
|
73 |
-
}
|
74 |
-
|
75 |
-
def reset_stats(self):
|
76 |
-
"""Reset all metrics"""
|
77 |
-
for key in self.metrics:
|
78 |
-
self.metrics[key] = 0 if isinstance(self.metrics[key], int) else 0.0
|
79 |
-
|
80 |
-
def timing_decorator(func):
|
81 |
-
"""Decorator to measure function execution time"""
|
82 |
-
@wraps(func)
|
83 |
-
def wrapper(*args, **kwargs):
|
84 |
-
start_time = time.time()
|
85 |
-
try:
|
86 |
-
result = func(*args, **kwargs)
|
87 |
-
execution_time = time.time() - start_time
|
88 |
-
logger.info(f"{func.__name__} completed in {execution_time:.3f}s")
|
89 |
-
return result, execution_time
|
90 |
-
except Exception as e:
|
91 |
-
execution_time = time.time() - start_time
|
92 |
-
logger.error(f"{func.__name__} failed in {execution_time:.3f}s: {e}")
|
93 |
-
raise e
|
94 |
-
return wrapper
|
95 |
-
|
96 |
-
def get_system_info() -> Dict[str, Any]:
|
97 |
-
"""Get current system resource information"""
|
98 |
-
cpu_percent = psutil.cpu_percent(interval=1)
|
99 |
-
memory = psutil.virtual_memory()
|
100 |
-
|
101 |
-
system_info = {
|
102 |
-
'cpu_usage_percent': cpu_percent,
|
103 |
-
'memory_total_gb': round(memory.total / (1024**3), 2),
|
104 |
-
'memory_used_gb': round(memory.used / (1024**3), 2),
|
105 |
-
'memory_available_gb': round(memory.available / (1024**3), 2),
|
106 |
-
'memory_usage_percent': memory.percent
|
107 |
-
}
|
108 |
-
|
109 |
-
# Add GPU info if available
|
110 |
-
if torch.cuda.is_available():
|
111 |
-
gpu_memory = torch.cuda.memory_stats()
|
112 |
-
system_info.update({
|
113 |
-
'gpu_available': True,
|
114 |
-
'gpu_memory_allocated_gb': round(
|
115 |
-
torch.cuda.memory_allocated() / (1024**3), 2
|
116 |
-
),
|
117 |
-
'gpu_memory_reserved_gb': round(
|
118 |
-
torch.cuda.memory_reserved() / (1024**3), 2
|
119 |
-
),
|
120 |
-
'gpu_device_name': torch.cuda.get_device_name(0)
|
121 |
-
})
|
122 |
-
else:
|
123 |
-
system_info['gpu_available'] = False
|
124 |
-
|
125 |
-
return system_info
|
126 |
-
|
127 |
-
def validate_image_input(image_input: str) -> Dict[str, Any]:
|
128 |
-
"""Validate image input and return metadata"""
|
129 |
-
if not image_input or not isinstance(image_input, str):
|
130 |
-
return {'valid': False, 'type': None, 'error': 'Invalid input type'}
|
131 |
-
|
132 |
-
# Check if URL
|
133 |
-
if image_input.startswith(('http://', 'https://')):
|
134 |
-
return {
|
135 |
-
'valid': True,
|
136 |
-
'type': 'url',
|
137 |
-
'length': len(image_input),
|
138 |
-
'domain': image_input.split('/')[2] if '/' in image_input else 'unknown'
|
139 |
-
}
|
140 |
-
|
141 |
-
# Check if base64
|
142 |
-
elif image_input.startswith('data:image/') or len(image_input) > 100:
|
143 |
-
is_data_url = image_input.startswith('data:')
|
144 |
-
base64_data = image_input
|
145 |
-
|
146 |
-
if is_data_url:
|
147 |
-
if 'base64,' in image_input:
|
148 |
-
base64_data = image_input.split('base64,')[1]
|
149 |
-
else:
|
150 |
-
return {'valid': False, 'type': 'base64', 'error': 'Invalid data URL format'}
|
151 |
-
|
152 |
-
# Estimate decoded size
|
153 |
-
estimated_size = len(base64_data) * 3 // 4
|
154 |
-
|
155 |
-
return {
|
156 |
-
'valid': True,
|
157 |
-
'type': 'base64',
|
158 |
-
'is_data_url': is_data_url,
|
159 |
-
'base64_length': len(base64_data),
|
160 |
-
'estimated_size_bytes': estimated_size,
|
161 |
-
'estimated_size_kb': round(estimated_size / 1024, 2)
|
162 |
-
}
|
163 |
-
|
164 |
-
return {'valid': False, 'type': None, 'error': 'Unrecognized format'}
|
165 |
-
|
166 |
-
def sanitize_parameters(parameters: Dict[str, Any]) -> Dict[str, Any]:
|
167 |
-
"""Sanitize and validate generation parameters"""
|
168 |
-
sanitized = {}
|
169 |
-
|
170 |
-
# Max new tokens
|
171 |
-
max_new_tokens = parameters.get('max_new_tokens', 512)
|
172 |
-
sanitized['max_new_tokens'] = max(1, min(max_new_tokens, 2048))
|
173 |
-
|
174 |
-
# Temperature
|
175 |
-
temperature = parameters.get('temperature', 0.2)
|
176 |
-
sanitized['temperature'] = max(0.01, min(temperature, 2.0))
|
177 |
-
|
178 |
-
# Top-p
|
179 |
-
top_p = parameters.get('top_p', 0.9)
|
180 |
-
sanitized['top_p'] = max(0.01, min(top_p, 1.0))
|
181 |
-
|
182 |
-
# Repetition penalty
|
183 |
-
repetition_penalty = parameters.get('repetition_penalty', 1.05)
|
184 |
-
sanitized['repetition_penalty'] = max(1.0, min(repetition_penalty, 2.0))
|
185 |
-
|
186 |
-
# Stop sequences
|
187 |
-
stop = parameters.get('stop', ['</s>'])
|
188 |
-
if isinstance(stop, str):
|
189 |
-
stop = [stop]
|
190 |
-
sanitized['stop'] = stop[:5] # Limit to 5 stop sequences
|
191 |
-
|
192 |
-
# Return full text
|
193 |
-
sanitized['return_full_text'] = bool(parameters.get('return_full_text', False))
|
194 |
-
|
195 |
-
# Do sample
|
196 |
-
sanitized['do_sample'] = bool(parameters.get('do_sample', sanitized['temperature'] > 0.01))
|
197 |
-
|
198 |
-
return sanitized
|
199 |
-
|
200 |
-
def create_health_check() -> Dict[str, Any]:
|
201 |
-
"""Create a health check response"""
|
202 |
-
try:
|
203 |
-
system_info = get_system_info()
|
204 |
-
|
205 |
-
health_status = {
|
206 |
-
'status': 'healthy',
|
207 |
-
'timestamp': time.time(),
|
208 |
-
'system': system_info,
|
209 |
-
'model': 'PULSE-7B',
|
210 |
-
'handler_version': '2.0.0',
|
211 |
-
'features': [
|
212 |
-
'image_url_support',
|
213 |
-
'base64_image_support',
|
214 |
-
'stop_sequences',
|
215 |
-
'parameter_validation',
|
216 |
-
'performance_monitoring'
|
217 |
-
]
|
218 |
-
}
|
219 |
-
|
220 |
-
# Check if system is under stress
|
221 |
-
if system_info['memory_usage_percent'] > 90:
|
222 |
-
health_status['warnings'] = ['High memory usage']
|
223 |
-
|
224 |
-
if system_info['cpu_usage_percent'] > 90:
|
225 |
-
health_status.setdefault('warnings', []).append('High CPU usage')
|
226 |
-
|
227 |
-
return health_status
|
228 |
-
|
229 |
-
except Exception as e:
|
230 |
-
return {
|
231 |
-
'status': 'unhealthy',
|
232 |
-
'timestamp': time.time(),
|
233 |
-
'error': str(e)
|
234 |
-
}
|
235 |
-
|
236 |
-
class DeepSeekClient:
|
237 |
-
"""DeepSeek API client for Turkish commentary"""
|
238 |
-
|
239 |
-
def __init__(self, api_key: Optional[str] = None):
|
240 |
-
self.api_key = api_key or os.getenv('deep_key') or os.getenv('DEEPSEEK_API_KEY')
|
241 |
-
self.base_url = "https://api.deepseek.com/v1/chat/completions"
|
242 |
-
self.headers = {
|
243 |
-
"Content-Type": "application/json",
|
244 |
-
"Authorization": f"Bearer {self.api_key}" if self.api_key else ""
|
245 |
-
}
|
246 |
-
|
247 |
-
def is_available(self) -> bool:
|
248 |
-
"""Check if DeepSeek API is available"""
|
249 |
-
return bool(self.api_key)
|
250 |
-
|
251 |
-
def get_turkish_commentary(self, english_analysis: str, timeout: int = 30) -> Dict[str, Any]:
|
252 |
-
"""
|
253 |
-
Get Turkish commentary for English medical analysis
|
254 |
-
|
255 |
-
Args:
|
256 |
-
english_analysis: English medical analysis text
|
257 |
-
timeout: Request timeout in seconds
|
258 |
-
|
259 |
-
Returns:
|
260 |
-
Dict with success status and commentary
|
261 |
-
"""
|
262 |
-
if not self.is_available():
|
263 |
-
return {
|
264 |
-
"success": False,
|
265 |
-
"error": "DeepSeek API key not configured",
|
266 |
-
"comment_text": ""
|
267 |
-
}
|
268 |
-
|
269 |
-
try:
|
270 |
-
# Prepare the prompt for Turkish medical commentary
|
271 |
-
prompt = f"""Bu bir EKG sonucu klinik incelemesi. Aşağıdaki İngilizce medikal analizi Türkçe olarak yorumla ve hasta için anlaşılır bir dilde açıkla:
|
272 |
-
|
273 |
-
"{english_analysis}"
|
274 |
-
|
275 |
-
Lütfen:
|
276 |
-
1. Medikal terimleri Türkçe karşılıklarıyla açıkla
|
277 |
-
2. Hastanın anlayabileceği basit bir dille yaz
|
278 |
-
3. Gerekirse aciliyet durumu hakkında bilgi ver
|
279 |
-
4. Kısa ve net ol
|
280 |
-
|
281 |
-
Türkçe Yorum:"""
|
282 |
-
|
283 |
-
payload = {
|
284 |
-
"model": "deepseek-chat",
|
285 |
-
"messages": [
|
286 |
-
{
|
287 |
-
"role": "system",
|
288 |
-
"content": "Sen deneyimli bir kardiyolog doktorsun. EKG sonuçlarını Türkçe olarak hastalar için anlaşılır şekilde açıklıyorsun."
|
289 |
-
},
|
290 |
-
{
|
291 |
-
"role": "user",
|
292 |
-
"content": prompt
|
293 |
-
}
|
294 |
-
],
|
295 |
-
"temperature": 0.3,
|
296 |
-
"max_tokens": 500,
|
297 |
-
"stream": False
|
298 |
-
}
|
299 |
-
|
300 |
-
logger.info("🔄 DeepSeek API'ye Türkçe yorum için istek gönderiliyor...")
|
301 |
-
|
302 |
-
response = requests.post(
|
303 |
-
self.base_url,
|
304 |
-
headers=self.headers,
|
305 |
-
json=payload,
|
306 |
-
timeout=timeout
|
307 |
-
)
|
308 |
-
|
309 |
-
response.raise_for_status()
|
310 |
-
result = response.json()
|
311 |
-
|
312 |
-
if 'choices' in result and len(result['choices']) > 0:
|
313 |
-
comment_text = result['choices'][0]['message']['content'].strip()
|
314 |
-
|
315 |
-
# Clean up the response - remove "Türkçe Yorum:" prefix if present
|
316 |
-
if comment_text.startswith("Türkçe Yorum:"):
|
317 |
-
comment_text = comment_text[13:].strip()
|
318 |
-
|
319 |
-
logger.info("✅ DeepSeek'ten Türkçe yorum başarıyla alındı")
|
320 |
-
|
321 |
-
return {
|
322 |
-
"success": True,
|
323 |
-
"comment_text": comment_text,
|
324 |
-
"model": "deepseek-chat",
|
325 |
-
"tokens_used": result.get('usage', {}).get('total_tokens', 0)
|
326 |
-
}
|
327 |
-
else:
|
328 |
-
return {
|
329 |
-
"success": False,
|
330 |
-
"error": "DeepSeek API'den geçersiz yanıt",
|
331 |
-
"comment_text": ""
|
332 |
-
}
|
333 |
-
|
334 |
-
except requests.exceptions.Timeout:
|
335 |
-
logger.error("❌ DeepSeek API timeout")
|
336 |
-
return {
|
337 |
-
"success": False,
|
338 |
-
"error": "DeepSeek API timeout - istek zaman aşımına uğradı",
|
339 |
-
"comment_text": ""
|
340 |
-
}
|
341 |
-
|
342 |
-
except requests.exceptions.RequestException as e:
|
343 |
-
logger.error(f"❌ DeepSeek API request error: {e}")
|
344 |
-
return {
|
345 |
-
"success": False,
|
346 |
-
"error": f"DeepSeek API bağlantı hatası: {str(e)}",
|
347 |
-
"comment_text": ""
|
348 |
-
}
|
349 |
-
|
350 |
-
except Exception as e:
|
351 |
-
logger.error(f"❌ DeepSeek API unexpected error: {e}")
|
352 |
-
return {
|
353 |
-
"success": False,
|
354 |
-
"error": f"DeepSeek API beklenmeyen hata: {str(e)}",
|
355 |
-
"comment_text": ""
|
356 |
-
}
|
357 |
-
|
358 |
-
# Global instances
|
359 |
-
performance_monitor = PerformanceMonitor()
|
360 |
-
deepseek_client = DeepSeekClient()
|
|
|
1 |
+
"""
|
2 |
+
PULSE-7B Handler Utilities
|
3 |
+
Ubden® Team - Performance monitoring and helper functions
|
4 |
+
"""
|
5 |
+
|
6 |
+
import time
|
7 |
+
import torch
|
8 |
+
import psutil
|
9 |
+
import logging
|
10 |
+
import os
|
11 |
+
import json
|
12 |
+
import requests
|
13 |
+
from typing import Dict, Any, Optional
|
14 |
+
from functools import wraps
|
15 |
+
|
16 |
+
# Configure logging
|
17 |
+
logging.basicConfig(level=logging.INFO)
|
18 |
+
logger = logging.getLogger(__name__)
|
19 |
+
|
20 |
+
class PerformanceMonitor:
|
21 |
+
"""Performance monitoring utilities for PULSE-7B handler"""
|
22 |
+
|
23 |
+
def __init__(self):
|
24 |
+
self.metrics = {
|
25 |
+
'total_requests': 0,
|
26 |
+
'successful_requests': 0,
|
27 |
+
'failed_requests': 0,
|
28 |
+
'image_url_requests': 0,
|
29 |
+
'base64_requests': 0,
|
30 |
+
'text_only_requests': 0,
|
31 |
+
'total_generation_time': 0.0,
|
32 |
+
'total_image_processing_time': 0.0
|
33 |
+
}
|
34 |
+
|
35 |
+
def log_request(self, request_type: str, success: bool,
|
36 |
+
generation_time: float = 0.0,
|
37 |
+
image_processing_time: float = 0.0):
|
38 |
+
"""Log request metrics"""
|
39 |
+
self.metrics['total_requests'] += 1
|
40 |
+
|
41 |
+
if success:
|
42 |
+
self.metrics['successful_requests'] += 1
|
43 |
+
else:
|
44 |
+
self.metrics['failed_requests'] += 1
|
45 |
+
|
46 |
+
if request_type == 'image_url':
|
47 |
+
self.metrics['image_url_requests'] += 1
|
48 |
+
elif request_type == 'base64':
|
49 |
+
self.metrics['base64_requests'] += 1
|
50 |
+
else:
|
51 |
+
self.metrics['text_only_requests'] += 1
|
52 |
+
|
53 |
+
self.metrics['total_generation_time'] += generation_time
|
54 |
+
self.metrics['total_image_processing_time'] += image_processing_time
|
55 |
+
|
56 |
+
def get_stats(self) -> Dict[str, Any]:
|
57 |
+
"""Get current performance statistics"""
|
58 |
+
total_requests = self.metrics['total_requests']
|
59 |
+
if total_requests == 0:
|
60 |
+
return self.metrics
|
61 |
+
|
62 |
+
success_rate = (self.metrics['successful_requests'] / total_requests) * 100
|
63 |
+
avg_generation_time = self.metrics['total_generation_time'] / total_requests
|
64 |
+
avg_image_processing_time = self.metrics['total_image_processing_time'] / max(
|
65 |
+
self.metrics['image_url_requests'] + self.metrics['base64_requests'], 1
|
66 |
+
)
|
67 |
+
|
68 |
+
return {
|
69 |
+
**self.metrics,
|
70 |
+
'success_rate_percent': round(success_rate, 2),
|
71 |
+
'avg_generation_time_seconds': round(avg_generation_time, 3),
|
72 |
+
'avg_image_processing_time_seconds': round(avg_image_processing_time, 3)
|
73 |
+
}
|
74 |
+
|
75 |
+
def reset_stats(self):
|
76 |
+
"""Reset all metrics"""
|
77 |
+
for key in self.metrics:
|
78 |
+
self.metrics[key] = 0 if isinstance(self.metrics[key], int) else 0.0
|
79 |
+
|
80 |
+
def timing_decorator(func):
|
81 |
+
"""Decorator to measure function execution time"""
|
82 |
+
@wraps(func)
|
83 |
+
def wrapper(*args, **kwargs):
|
84 |
+
start_time = time.time()
|
85 |
+
try:
|
86 |
+
result = func(*args, **kwargs)
|
87 |
+
execution_time = time.time() - start_time
|
88 |
+
logger.info(f"{func.__name__} completed in {execution_time:.3f}s")
|
89 |
+
return result, execution_time
|
90 |
+
except Exception as e:
|
91 |
+
execution_time = time.time() - start_time
|
92 |
+
logger.error(f"{func.__name__} failed in {execution_time:.3f}s: {e}")
|
93 |
+
raise e
|
94 |
+
return wrapper
|
95 |
+
|
96 |
+
def get_system_info() -> Dict[str, Any]:
|
97 |
+
"""Get current system resource information"""
|
98 |
+
cpu_percent = psutil.cpu_percent(interval=1)
|
99 |
+
memory = psutil.virtual_memory()
|
100 |
+
|
101 |
+
system_info = {
|
102 |
+
'cpu_usage_percent': cpu_percent,
|
103 |
+
'memory_total_gb': round(memory.total / (1024**3), 2),
|
104 |
+
'memory_used_gb': round(memory.used / (1024**3), 2),
|
105 |
+
'memory_available_gb': round(memory.available / (1024**3), 2),
|
106 |
+
'memory_usage_percent': memory.percent
|
107 |
+
}
|
108 |
+
|
109 |
+
# Add GPU info if available
|
110 |
+
if torch.cuda.is_available():
|
111 |
+
gpu_memory = torch.cuda.memory_stats()
|
112 |
+
system_info.update({
|
113 |
+
'gpu_available': True,
|
114 |
+
'gpu_memory_allocated_gb': round(
|
115 |
+
torch.cuda.memory_allocated() / (1024**3), 2
|
116 |
+
),
|
117 |
+
'gpu_memory_reserved_gb': round(
|
118 |
+
torch.cuda.memory_reserved() / (1024**3), 2
|
119 |
+
),
|
120 |
+
'gpu_device_name': torch.cuda.get_device_name(0)
|
121 |
+
})
|
122 |
+
else:
|
123 |
+
system_info['gpu_available'] = False
|
124 |
+
|
125 |
+
return system_info
|
126 |
+
|
127 |
+
def validate_image_input(image_input: str) -> Dict[str, Any]:
|
128 |
+
"""Validate image input and return metadata"""
|
129 |
+
if not image_input or not isinstance(image_input, str):
|
130 |
+
return {'valid': False, 'type': None, 'error': 'Invalid input type'}
|
131 |
+
|
132 |
+
# Check if URL
|
133 |
+
if image_input.startswith(('http://', 'https://')):
|
134 |
+
return {
|
135 |
+
'valid': True,
|
136 |
+
'type': 'url',
|
137 |
+
'length': len(image_input),
|
138 |
+
'domain': image_input.split('/')[2] if '/' in image_input else 'unknown'
|
139 |
+
}
|
140 |
+
|
141 |
+
# Check if base64
|
142 |
+
elif image_input.startswith('data:image/') or len(image_input) > 100:
|
143 |
+
is_data_url = image_input.startswith('data:')
|
144 |
+
base64_data = image_input
|
145 |
+
|
146 |
+
if is_data_url:
|
147 |
+
if 'base64,' in image_input:
|
148 |
+
base64_data = image_input.split('base64,')[1]
|
149 |
+
else:
|
150 |
+
return {'valid': False, 'type': 'base64', 'error': 'Invalid data URL format'}
|
151 |
+
|
152 |
+
# Estimate decoded size
|
153 |
+
estimated_size = len(base64_data) * 3 // 4
|
154 |
+
|
155 |
+
return {
|
156 |
+
'valid': True,
|
157 |
+
'type': 'base64',
|
158 |
+
'is_data_url': is_data_url,
|
159 |
+
'base64_length': len(base64_data),
|
160 |
+
'estimated_size_bytes': estimated_size,
|
161 |
+
'estimated_size_kb': round(estimated_size / 1024, 2)
|
162 |
+
}
|
163 |
+
|
164 |
+
return {'valid': False, 'type': None, 'error': 'Unrecognized format'}
|
165 |
+
|
166 |
+
def sanitize_parameters(parameters: Dict[str, Any]) -> Dict[str, Any]:
|
167 |
+
"""Sanitize and validate generation parameters"""
|
168 |
+
sanitized = {}
|
169 |
+
|
170 |
+
# Max new tokens
|
171 |
+
max_new_tokens = parameters.get('max_new_tokens', 512)
|
172 |
+
sanitized['max_new_tokens'] = max(1, min(max_new_tokens, 2048))
|
173 |
+
|
174 |
+
# Temperature
|
175 |
+
temperature = parameters.get('temperature', 0.2)
|
176 |
+
sanitized['temperature'] = max(0.01, min(temperature, 2.0))
|
177 |
+
|
178 |
+
# Top-p
|
179 |
+
top_p = parameters.get('top_p', 0.9)
|
180 |
+
sanitized['top_p'] = max(0.01, min(top_p, 1.0))
|
181 |
+
|
182 |
+
# Repetition penalty
|
183 |
+
repetition_penalty = parameters.get('repetition_penalty', 1.05)
|
184 |
+
sanitized['repetition_penalty'] = max(1.0, min(repetition_penalty, 2.0))
|
185 |
+
|
186 |
+
# Stop sequences
|
187 |
+
stop = parameters.get('stop', ['</s>'])
|
188 |
+
if isinstance(stop, str):
|
189 |
+
stop = [stop]
|
190 |
+
sanitized['stop'] = stop[:5] # Limit to 5 stop sequences
|
191 |
+
|
192 |
+
# Return full text
|
193 |
+
sanitized['return_full_text'] = bool(parameters.get('return_full_text', False))
|
194 |
+
|
195 |
+
# Do sample
|
196 |
+
sanitized['do_sample'] = bool(parameters.get('do_sample', sanitized['temperature'] > 0.01))
|
197 |
+
|
198 |
+
return sanitized
|
199 |
+
|
200 |
+
def create_health_check() -> Dict[str, Any]:
|
201 |
+
"""Create a health check response"""
|
202 |
+
try:
|
203 |
+
system_info = get_system_info()
|
204 |
+
|
205 |
+
health_status = {
|
206 |
+
'status': 'healthy',
|
207 |
+
'timestamp': time.time(),
|
208 |
+
'system': system_info,
|
209 |
+
'model': 'PULSE-7B',
|
210 |
+
'handler_version': '2.0.0',
|
211 |
+
'features': [
|
212 |
+
'image_url_support',
|
213 |
+
'base64_image_support',
|
214 |
+
'stop_sequences',
|
215 |
+
'parameter_validation',
|
216 |
+
'performance_monitoring'
|
217 |
+
]
|
218 |
+
}
|
219 |
+
|
220 |
+
# Check if system is under stress
|
221 |
+
if system_info['memory_usage_percent'] > 90:
|
222 |
+
health_status['warnings'] = ['High memory usage']
|
223 |
+
|
224 |
+
if system_info['cpu_usage_percent'] > 90:
|
225 |
+
health_status.setdefault('warnings', []).append('High CPU usage')
|
226 |
+
|
227 |
+
return health_status
|
228 |
+
|
229 |
+
except Exception as e:
|
230 |
+
return {
|
231 |
+
'status': 'unhealthy',
|
232 |
+
'timestamp': time.time(),
|
233 |
+
'error': str(e)
|
234 |
+
}
|
235 |
+
|
236 |
+
class DeepSeekClient:
|
237 |
+
"""DeepSeek API client for Turkish commentary"""
|
238 |
+
|
239 |
+
def __init__(self, api_key: Optional[str] = None):
|
240 |
+
self.api_key = api_key or os.getenv('deep_key') or os.getenv('DEEPSEEK_API_KEY')
|
241 |
+
self.base_url = "https://api.deepseek.com/v1/chat/completions"
|
242 |
+
self.headers = {
|
243 |
+
"Content-Type": "application/json",
|
244 |
+
"Authorization": f"Bearer {self.api_key}" if self.api_key else ""
|
245 |
+
}
|
246 |
+
|
247 |
+
def is_available(self) -> bool:
|
248 |
+
"""Check if DeepSeek API is available"""
|
249 |
+
return bool(self.api_key)
|
250 |
+
|
251 |
+
def get_turkish_commentary(self, english_analysis: str, timeout: int = 30) -> Dict[str, Any]:
|
252 |
+
"""
|
253 |
+
Get Turkish commentary for English medical analysis
|
254 |
+
|
255 |
+
Args:
|
256 |
+
english_analysis: English medical analysis text
|
257 |
+
timeout: Request timeout in seconds
|
258 |
+
|
259 |
+
Returns:
|
260 |
+
Dict with success status and commentary
|
261 |
+
"""
|
262 |
+
if not self.is_available():
|
263 |
+
return {
|
264 |
+
"success": False,
|
265 |
+
"error": "DeepSeek API key not configured",
|
266 |
+
"comment_text": ""
|
267 |
+
}
|
268 |
+
|
269 |
+
try:
|
270 |
+
# Prepare the prompt for Turkish medical commentary
|
271 |
+
prompt = f"""Bu bir EKG sonucu klinik incelemesi. Aşağıdaki İngilizce medikal analizi Türkçe olarak yorumla ve hasta için anlaşılır bir dilde açıkla:
|
272 |
+
|
273 |
+
"{english_analysis}"
|
274 |
+
|
275 |
+
Lütfen:
|
276 |
+
1. Medikal terimleri Türkçe karşılıklarıyla açıkla
|
277 |
+
2. Hastanın anlayabileceği basit bir dille yaz
|
278 |
+
3. Gerekirse aciliyet durumu hakkında bilgi ver
|
279 |
+
4. Kısa ve net ol
|
280 |
+
|
281 |
+
Türkçe Yorum:"""
|
282 |
+
|
283 |
+
payload = {
|
284 |
+
"model": "deepseek-chat",
|
285 |
+
"messages": [
|
286 |
+
{
|
287 |
+
"role": "system",
|
288 |
+
"content": "Sen deneyimli bir kardiyolog doktorsun. EKG sonuçlarını Türkçe olarak hastalar için anlaşılır şekilde açıklıyorsun."
|
289 |
+
},
|
290 |
+
{
|
291 |
+
"role": "user",
|
292 |
+
"content": prompt
|
293 |
+
}
|
294 |
+
],
|
295 |
+
"temperature": 0.3,
|
296 |
+
"max_tokens": 500,
|
297 |
+
"stream": False
|
298 |
+
}
|
299 |
+
|
300 |
+
logger.info("🔄 DeepSeek API'ye Türkçe yorum için istek gönderiliyor...")
|
301 |
+
|
302 |
+
response = requests.post(
|
303 |
+
self.base_url,
|
304 |
+
headers=self.headers,
|
305 |
+
json=payload,
|
306 |
+
timeout=timeout
|
307 |
+
)
|
308 |
+
|
309 |
+
response.raise_for_status()
|
310 |
+
result = response.json()
|
311 |
+
|
312 |
+
if 'choices' in result and len(result['choices']) > 0:
|
313 |
+
comment_text = result['choices'][0]['message']['content'].strip()
|
314 |
+
|
315 |
+
# Clean up the response - remove "Türkçe Yorum:" prefix if present
|
316 |
+
if comment_text.startswith("Türkçe Yorum:"):
|
317 |
+
comment_text = comment_text[13:].strip()
|
318 |
+
|
319 |
+
logger.info("✅ DeepSeek'ten Türkçe yorum başarıyla alındı")
|
320 |
+
|
321 |
+
return {
|
322 |
+
"success": True,
|
323 |
+
"comment_text": comment_text,
|
324 |
+
"model": "deepseek-chat",
|
325 |
+
"tokens_used": result.get('usage', {}).get('total_tokens', 0)
|
326 |
+
}
|
327 |
+
else:
|
328 |
+
return {
|
329 |
+
"success": False,
|
330 |
+
"error": "DeepSeek API'den geçersiz yanıt",
|
331 |
+
"comment_text": ""
|
332 |
+
}
|
333 |
+
|
334 |
+
except requests.exceptions.Timeout:
|
335 |
+
logger.error("❌ DeepSeek API timeout")
|
336 |
+
return {
|
337 |
+
"success": False,
|
338 |
+
"error": "DeepSeek API timeout - istek zaman aşımına uğradı",
|
339 |
+
"comment_text": ""
|
340 |
+
}
|
341 |
+
|
342 |
+
except requests.exceptions.RequestException as e:
|
343 |
+
logger.error(f"❌ DeepSeek API request error: {e}")
|
344 |
+
return {
|
345 |
+
"success": False,
|
346 |
+
"error": f"DeepSeek API bağlantı hatası: {str(e)}",
|
347 |
+
"comment_text": ""
|
348 |
+
}
|
349 |
+
|
350 |
+
except Exception as e:
|
351 |
+
logger.error(f"❌ DeepSeek API unexpected error: {e}")
|
352 |
+
return {
|
353 |
+
"success": False,
|
354 |
+
"error": f"DeepSeek API beklenmeyen hata: {str(e)}",
|
355 |
+
"comment_text": ""
|
356 |
+
}
|
357 |
+
|
358 |
+
# Global instances
|
359 |
+
performance_monitor = PerformanceMonitor()
|
360 |
+
deepseek_client = DeepSeekClient()
|