ubden commited on
Commit
9313c24
·
verified ·
1 Parent(s): b48bc31

Upload 15 files

Browse files
Files changed (7) hide show
  1. README.md +5 -17
  2. base_request.md +45 -45
  3. deployment_guide.md +133 -133
  4. handler.py +2 -82
  5. requirements.txt +1 -5
  6. test_requests.json +220 -220
  7. 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 auto email processing
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 Environment Variables
 
 
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
- health = create_health_check()
291
  else:
292
- health = {
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()