Utiric commited on
Commit
a2f40a7
·
verified ·
1 Parent(s): 3709277

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +4 -62
app.py CHANGED
@@ -10,43 +10,31 @@ from detoxify import Detoxify
10
 
11
  app = Flask(__name__, static_folder='static', template_folder='templates')
12
 
13
- # Load the detoxify model
14
  detoxify_model = Detoxify('multilingual')
15
 
16
- # API key from environment variable
17
  API_KEY = os.getenv('API_KEY', 'your-api-key-here')
18
 
19
- # Performance metrics tracking
20
- request_times = deque(maxlen=1000) # Store last 1000 request times
21
  daily_requests = defaultdict(int)
22
  daily_tokens = defaultdict(int)
23
  concurrent_requests = 0
24
  concurrent_requests_lock = threading.Lock()
25
 
26
- # Token encoding for counting tokens
27
  encoding = tiktoken.get_encoding("cl100k_base")
28
 
29
  def count_tokens(text):
30
- """Count tokens in the given text using tiktoken."""
31
  return len(encoding.encode(text))
32
 
33
  def transform_predictions(prediction_dict):
34
- """
35
- Transform predictions to OpenAI-style format.
36
- Returns: flagged, categories, category_scores, category_applied_input_types
37
- """
38
- # Define the categories we want to track
39
  category_keys = [
40
  "toxicity", "severe_toxicity", "obscene", "threat",
41
  "insult", "identity_attack", "sexual_explicit"
42
  ]
43
 
44
- # Map detoxify output to our categories
45
  scores = {}
46
  for key in category_keys:
47
  scores[key] = prediction_dict.get(key, 0.0)
48
 
49
- # Set threshold for flagging content
50
  threshold = 0.5
51
  bool_categories = {key: (scores[key] > threshold) for key in category_keys}
52
  cat_applied_input_types = {key: (["text"] if scores[key] > 0 else []) for key in category_keys}
@@ -55,36 +43,28 @@ def transform_predictions(prediction_dict):
55
  return flagged, bool_categories, scores, cat_applied_input_types
56
 
57
  def track_request_metrics(start_time, tokens_count):
58
- """Track performance metrics for requests."""
59
  end_time = time.time()
60
- request_time = end_time - start_time
61
- request_times.append(request_time)
62
 
63
  today = datetime.now().strftime("%Y-%m-%d")
64
  daily_requests[today] += 1
65
  daily_tokens[today] += tokens_count
66
 
67
  def get_performance_metrics():
68
- """Get current performance metrics."""
69
  global concurrent_requests
70
  with concurrent_requests_lock:
71
  current_concurrent = concurrent_requests
72
 
73
- # Calculate average request time
74
  avg_request_time = sum(request_times) / len(request_times) if request_times else 0
75
 
76
- # Get today's date
77
  today = datetime.now().strftime("%Y-%m-%d")
78
 
79
- # Calculate requests per second (based on last 100 requests)
80
  recent_requests = list(request_times)[-100:] if len(request_times) >= 100 else list(request_times)
81
  requests_per_second = len(recent_requests) / sum(recent_requests) if recent_requests and sum(recent_requests) > 0 else 0
82
 
83
- # Get daily stats
84
  today_requests = daily_requests.get(today, 0)
85
  today_tokens = daily_tokens.get(today, 0)
86
 
87
- # Get last 7 days stats
88
  last_7_days = []
89
  for i in range(7):
90
  date = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
@@ -111,7 +91,6 @@ def home():
111
  def moderations():
112
  global concurrent_requests
113
 
114
- # Track concurrent requests
115
  with concurrent_requests_lock:
116
  concurrent_requests += 1
117
 
@@ -119,7 +98,6 @@ def moderations():
119
  total_tokens = 0
120
 
121
  try:
122
- # Check authorization
123
  auth_header = request.headers.get('Authorization')
124
  if not auth_header or not auth_header.startswith("Bearer "):
125
  return jsonify({"error": "Unauthorized"}), 401
@@ -128,14 +106,12 @@ def moderations():
128
  if provided_api_key != API_KEY:
129
  return jsonify({"error": "Unauthorized"}), 401
130
 
131
- # Get input data
132
  data = request.get_json()
133
  raw_input = data.get('input') or data.get('texts')
134
 
135
  if raw_input is None:
136
  return jsonify({"error": "Invalid input, expected 'input' or 'texts' field"}), 400
137
 
138
- # Handle both string and list inputs
139
  if isinstance(raw_input, str):
140
  texts = [raw_input]
141
  elif isinstance(raw_input, list):
@@ -143,7 +119,6 @@ def moderations():
143
  else:
144
  return jsonify({"error": "Invalid input format, expected string or list of strings"}), 400
145
 
146
- # Validate input size
147
  if len(texts) > 10:
148
  return jsonify({"error": "Too many input items. Maximum 10 allowed."}), 400
149
 
@@ -152,7 +127,6 @@ def moderations():
152
  return jsonify({"error": "Each input item must be a string with a maximum of 100k characters."}), 400
153
  total_tokens += count_tokens(text)
154
 
155
- # Process each text
156
  results = []
157
  for text in texts:
158
  pred = detoxify_model.predict([text])
@@ -166,10 +140,8 @@ def moderations():
166
  "category_applied_input_types": cat_applied_input_types
167
  })
168
 
169
- # Track metrics
170
  track_request_metrics(start_time, total_tokens)
171
 
172
- # Prepare response
173
  response_data = {
174
  "id": "modr-" + uuid.uuid4().hex[:24],
175
  "model": "unitaryai/detoxify-multilingual",
@@ -183,13 +155,11 @@ def moderations():
183
  return jsonify(response_data)
184
 
185
  finally:
186
- # Decrement concurrent requests counter
187
  with concurrent_requests_lock:
188
  concurrent_requests -= 1
189
 
190
  @app.route('/v1/metrics', methods=['GET'])
191
  def metrics():
192
- """Endpoint to get performance metrics."""
193
  auth_header = request.headers.get('Authorization')
194
  if not auth_header or not auth_header.startswith("Bearer "):
195
  return jsonify({"error": "Unauthorized"}), 401
@@ -200,13 +170,10 @@ def metrics():
200
 
201
  return jsonify(get_performance_metrics())
202
 
203
- # Create a simple file-based approach for the HTML template
204
  def create_directories_and_files():
205
- """Create necessary directories and files if they don't exist."""
206
  os.makedirs('templates', exist_ok=True)
207
  os.makedirs('static', exist_ok=True)
208
 
209
- # Create index.html if it doesn't exist
210
  index_path = os.path.join('templates', 'index.html')
211
  if not os.path.exists(index_path):
212
  with open(index_path, 'w') as f:
@@ -275,7 +242,6 @@ def create_directories_and_files():
275
  </style>
276
  </head>
277
  <body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
278
- <!-- Header -->
279
  <header class="gradient-bg text-white shadow-lg">
280
  <div class="container mx-auto px-4 py-6 flex justify-between items-center">
281
  <div class="flex items-center space-x-3">
@@ -297,7 +263,6 @@ def create_directories_and_files():
297
  </header>
298
 
299
  <main class="container mx-auto px-4 py-8">
300
- <!-- Performance Metrics Section -->
301
  <section class="mb-12">
302
  <h2 class="text-2xl font-bold mb-6 flex items-center">
303
  <i class="fas fa-chart-line mr-3 text-primary-600"></i>
@@ -362,7 +327,6 @@ def create_directories_and_files():
362
  </div>
363
  </section>
364
 
365
- <!-- API Testing Section -->
366
  <section class="mb-12">
367
  <h2 class="text-2xl font-bold mb-6 flex items-center">
368
  <i class="fas fa-code mr-3 text-primary-600"></i>
@@ -417,7 +381,6 @@ def create_directories_and_files():
417
  </div>
418
  </section>
419
 
420
- <!-- Results Section -->
421
  <section id="resultsSection" class="hidden">
422
  <h2 class="text-2xl font-bold mb-6 flex items-center">
423
  <i class="fas fa-clipboard-check mr-3 text-primary-600"></i>
@@ -434,12 +397,10 @@ def create_directories_and_files():
434
  </div>
435
 
436
  <div id="resultsContainer" class="space-y-6">
437
- <!-- Results will be dynamically inserted here -->
438
  </div>
439
  </div>
440
  </section>
441
 
442
- <!-- API Documentation Section -->
443
  <section>
444
  <h2 class="text-2xl font-bold mb-6 flex items-center">
445
  <i class="fas fa-book mr-3 text-primary-600"></i>
@@ -507,7 +468,6 @@ def create_directories_and_files():
507
  </section>
508
  </main>
509
 
510
- <!-- Footer -->
511
  <footer class="bg-gray-100 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
512
  <div class="container mx-auto px-4 py-6">
513
  <div class="flex flex-col md:flex-row justify-between items-center">
@@ -530,11 +490,9 @@ def create_directories_and_files():
530
  </footer>
531
 
532
  <script>
533
- // Dark mode toggle
534
  const darkModeToggle = document.getElementById('darkModeToggle');
535
  const html = document.documentElement;
536
 
537
- // Check for saved dark mode preference or default to light mode
538
  const currentTheme = localStorage.getItem('theme') || 'light';
539
  if (currentTheme === 'dark') {
540
  html.classList.add('dark');
@@ -546,7 +504,6 @@ def create_directories_and_files():
546
  localStorage.setItem('theme', theme);
547
  });
548
 
549
- // Chart for activity
550
  let activityChart;
551
 
552
  function initActivityChart() {
@@ -601,7 +558,6 @@ def create_directories_and_files():
601
  });
602
  }
603
 
604
- // Fetch performance metrics
605
  async function fetchMetrics() {
606
  const apiKey = document.getElementById('apiKey').value;
607
  if (!apiKey) {
@@ -629,14 +585,12 @@ def create_directories_and_files():
629
  }
630
  }
631
 
632
- // Update metrics display
633
  function updateMetricsDisplay(data) {
634
  document.getElementById('avgResponseTime').textContent = (data.avg_request_time * 1000).toFixed(2) + 'ms';
635
  document.getElementById('requestsPerSecond').textContent = data.requests_per_second.toFixed(2);
636
  document.getElementById('concurrentRequests').textContent = data.concurrent_requests;
637
  document.getElementById('todayTokens').textContent = data.today_tokens.toLocaleString();
638
 
639
- // Update activity chart
640
  if (activityChart) {
641
  const labels = data.last_7_days.map(day => {
642
  const date = new Date(day.date);
@@ -653,7 +607,6 @@ def create_directories_and_files():
653
  }
654
  }
655
 
656
- // Add text input
657
  document.getElementById('addTextInput').addEventListener('click', () => {
658
  const container = document.getElementById('textInputsContainer');
659
  const inputGroups = container.querySelectorAll('.text-input-group');
@@ -674,7 +627,6 @@ def create_directories_and_files():
674
 
675
  container.appendChild(newInputGroup);
676
 
677
- // Add event listener to remove button
678
  newInputGroup.querySelector('.remove-input').addEventListener('click', function() {
679
  newInputGroup.remove();
680
  updateRemoveButtons();
@@ -683,7 +635,6 @@ def create_directories_and_files():
683
  updateRemoveButtons();
684
  });
685
 
686
- // Update remove buttons visibility
687
  function updateRemoveButtons() {
688
  const inputGroups = document.querySelectorAll('.text-input-group');
689
  inputGroups.forEach((group, index) => {
@@ -696,7 +647,6 @@ def create_directories_and_files():
696
  });
697
  }
698
 
699
- // Remove text input
700
  document.addEventListener('click', function(e) {
701
  if (e.target.closest('.remove-input')) {
702
  e.target.closest('.text-input-group').remove();
@@ -704,7 +654,6 @@ def create_directories_and_files():
704
  }
705
  });
706
 
707
- // Clear form
708
  document.getElementById('clearBtn').addEventListener('click', () => {
709
  document.getElementById('apiTestForm').reset();
710
  const container = document.getElementById('textInputsContainer');
@@ -719,7 +668,6 @@ def create_directories_and_files():
719
  document.getElementById('resultsSection').classList.add('hidden');
720
  });
721
 
722
- // Analyze text
723
  document.getElementById('apiTestForm').addEventListener('submit', async (e) => {
724
  e.preventDefault();
725
 
@@ -773,7 +721,7 @@ def create_directories_and_files():
773
  }
774
 
775
  const data = await response.json();
776
- displayResults(data, responseTime);
777
 
778
  } catch (error) {
779
  console.error('Error analyzing text:', error);
@@ -784,8 +732,7 @@ def create_directories_and_files():
784
  }
785
  });
786
 
787
- // Display results
788
- function displayResults(data, responseTime) {
789
  const resultsSection = document.getElementById('resultsSection');
790
  const resultsContainer = document.getElementById('resultsContainer');
791
 
@@ -842,17 +789,13 @@ def create_directories_and_files():
842
  resultsSection.scrollIntoView({ behavior: 'smooth' });
843
  }
844
 
845
- // Initialize chart on page load
846
  document.addEventListener('DOMContentLoaded', () => {
847
  initActivityChart();
848
 
849
- // Set up refresh metrics button
850
  document.getElementById('refreshMetrics').addEventListener('click', fetchMetrics);
851
 
852
- // Initial metrics fetch
853
  fetchMetrics();
854
 
855
- // Auto-refresh metrics every 30 seconds
856
  setInterval(fetchMetrics, 30000);
857
  });
858
  </script>
@@ -860,7 +803,6 @@ def create_directories_and_files():
860
  </html>''')
861
 
862
  if __name__ == '__main__':
863
- # Create directories and files
864
  create_directories_and_files()
865
 
866
  port = int(os.getenv('PORT', 7860))
 
10
 
11
  app = Flask(__name__, static_folder='static', template_folder='templates')
12
 
 
13
  detoxify_model = Detoxify('multilingual')
14
 
 
15
  API_KEY = os.getenv('API_KEY', 'your-api-key-here')
16
 
17
+ request_times = deque(maxlen=1000)
 
18
  daily_requests = defaultdict(int)
19
  daily_tokens = defaultdict(int)
20
  concurrent_requests = 0
21
  concurrent_requests_lock = threading.Lock()
22
 
 
23
  encoding = tiktoken.get_encoding("cl100k_base")
24
 
25
  def count_tokens(text):
 
26
  return len(encoding.encode(text))
27
 
28
  def transform_predictions(prediction_dict):
 
 
 
 
 
29
  category_keys = [
30
  "toxicity", "severe_toxicity", "obscene", "threat",
31
  "insult", "identity_attack", "sexual_explicit"
32
  ]
33
 
 
34
  scores = {}
35
  for key in category_keys:
36
  scores[key] = prediction_dict.get(key, 0.0)
37
 
 
38
  threshold = 0.5
39
  bool_categories = {key: (scores[key] > threshold) for key in category_keys}
40
  cat_applied_input_types = {key: (["text"] if scores[key] > 0 else []) for key in category_keys}
 
43
  return flagged, bool_categories, scores, cat_applied_input_types
44
 
45
  def track_request_metrics(start_time, tokens_count):
 
46
  end_time = time.time()
47
+ request_times.append(end_time - start_time)
 
48
 
49
  today = datetime.now().strftime("%Y-%m-%d")
50
  daily_requests[today] += 1
51
  daily_tokens[today] += tokens_count
52
 
53
  def get_performance_metrics():
 
54
  global concurrent_requests
55
  with concurrent_requests_lock:
56
  current_concurrent = concurrent_requests
57
 
 
58
  avg_request_time = sum(request_times) / len(request_times) if request_times else 0
59
 
 
60
  today = datetime.now().strftime("%Y-%m-%d")
61
 
 
62
  recent_requests = list(request_times)[-100:] if len(request_times) >= 100 else list(request_times)
63
  requests_per_second = len(recent_requests) / sum(recent_requests) if recent_requests and sum(recent_requests) > 0 else 0
64
 
 
65
  today_requests = daily_requests.get(today, 0)
66
  today_tokens = daily_tokens.get(today, 0)
67
 
 
68
  last_7_days = []
69
  for i in range(7):
70
  date = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
 
91
  def moderations():
92
  global concurrent_requests
93
 
 
94
  with concurrent_requests_lock:
95
  concurrent_requests += 1
96
 
 
98
  total_tokens = 0
99
 
100
  try:
 
101
  auth_header = request.headers.get('Authorization')
102
  if not auth_header or not auth_header.startswith("Bearer "):
103
  return jsonify({"error": "Unauthorized"}), 401
 
106
  if provided_api_key != API_KEY:
107
  return jsonify({"error": "Unauthorized"}), 401
108
 
 
109
  data = request.get_json()
110
  raw_input = data.get('input') or data.get('texts')
111
 
112
  if raw_input is None:
113
  return jsonify({"error": "Invalid input, expected 'input' or 'texts' field"}), 400
114
 
 
115
  if isinstance(raw_input, str):
116
  texts = [raw_input]
117
  elif isinstance(raw_input, list):
 
119
  else:
120
  return jsonify({"error": "Invalid input format, expected string or list of strings"}), 400
121
 
 
122
  if len(texts) > 10:
123
  return jsonify({"error": "Too many input items. Maximum 10 allowed."}), 400
124
 
 
127
  return jsonify({"error": "Each input item must be a string with a maximum of 100k characters."}), 400
128
  total_tokens += count_tokens(text)
129
 
 
130
  results = []
131
  for text in texts:
132
  pred = detoxify_model.predict([text])
 
140
  "category_applied_input_types": cat_applied_input_types
141
  })
142
 
 
143
  track_request_metrics(start_time, total_tokens)
144
 
 
145
  response_data = {
146
  "id": "modr-" + uuid.uuid4().hex[:24],
147
  "model": "unitaryai/detoxify-multilingual",
 
155
  return jsonify(response_data)
156
 
157
  finally:
 
158
  with concurrent_requests_lock:
159
  concurrent_requests -= 1
160
 
161
  @app.route('/v1/metrics', methods=['GET'])
162
  def metrics():
 
163
  auth_header = request.headers.get('Authorization')
164
  if not auth_header or not auth_header.startswith("Bearer "):
165
  return jsonify({"error": "Unauthorized"}), 401
 
170
 
171
  return jsonify(get_performance_metrics())
172
 
 
173
  def create_directories_and_files():
 
174
  os.makedirs('templates', exist_ok=True)
175
  os.makedirs('static', exist_ok=True)
176
 
 
177
  index_path = os.path.join('templates', 'index.html')
178
  if not os.path.exists(index_path):
179
  with open(index_path, 'w') as f:
 
242
  </style>
243
  </head>
244
  <body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
 
245
  <header class="gradient-bg text-white shadow-lg">
246
  <div class="container mx-auto px-4 py-6 flex justify-between items-center">
247
  <div class="flex items-center space-x-3">
 
263
  </header>
264
 
265
  <main class="container mx-auto px-4 py-8">
 
266
  <section class="mb-12">
267
  <h2 class="text-2xl font-bold mb-6 flex items-center">
268
  <i class="fas fa-chart-line mr-3 text-primary-600"></i>
 
327
  </div>
328
  </section>
329
 
 
330
  <section class="mb-12">
331
  <h2 class="text-2xl font-bold mb-6 flex items-center">
332
  <i class="fas fa-code mr-3 text-primary-600"></i>
 
381
  </div>
382
  </section>
383
 
 
384
  <section id="resultsSection" class="hidden">
385
  <h2 class="text-2xl font-bold mb-6 flex items-center">
386
  <i class="fas fa-clipboard-check mr-3 text-primary-600"></i>
 
397
  </div>
398
 
399
  <div id="resultsContainer" class="space-y-6">
 
400
  </div>
401
  </div>
402
  </section>
403
 
 
404
  <section>
405
  <h2 class="text-2xl font-bold mb-6 flex items-center">
406
  <i class="fas fa-book mr-3 text-primary-600"></i>
 
468
  </section>
469
  </main>
470
 
 
471
  <footer class="bg-gray-100 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
472
  <div class="container mx-auto px-4 py-6">
473
  <div class="flex flex-col md:flex-row justify-between items-center">
 
490
  </footer>
491
 
492
  <script>
 
493
  const darkModeToggle = document.getElementById('darkModeToggle');
494
  const html = document.documentElement;
495
 
 
496
  const currentTheme = localStorage.getItem('theme') || 'light';
497
  if (currentTheme === 'dark') {
498
  html.classList.add('dark');
 
504
  localStorage.setItem('theme', theme);
505
  });
506
 
 
507
  let activityChart;
508
 
509
  function initActivityChart() {
 
558
  });
559
  }
560
 
 
561
  async function fetchMetrics() {
562
  const apiKey = document.getElementById('apiKey').value;
563
  if (!apiKey) {
 
585
  }
586
  }
587
 
 
588
  function updateMetricsDisplay(data) {
589
  document.getElementById('avgResponseTime').textContent = (data.avg_request_time * 1000).toFixed(2) + 'ms';
590
  document.getElementById('requestsPerSecond').textContent = data.requests_per_second.toFixed(2);
591
  document.getElementById('concurrentRequests').textContent = data.concurrent_requests;
592
  document.getElementById('todayTokens').textContent = data.today_tokens.toLocaleString();
593
 
 
594
  if (activityChart) {
595
  const labels = data.last_7_days.map(day => {
596
  const date = new Date(day.date);
 
607
  }
608
  }
609
 
 
610
  document.getElementById('addTextInput').addEventListener('click', () => {
611
  const container = document.getElementById('textInputsContainer');
612
  const inputGroups = container.querySelectorAll('.text-input-group');
 
627
 
628
  container.appendChild(newInputGroup);
629
 
 
630
  newInputGroup.querySelector('.remove-input').addEventListener('click', function() {
631
  newInputGroup.remove();
632
  updateRemoveButtons();
 
635
  updateRemoveButtons();
636
  });
637
 
 
638
  function updateRemoveButtons() {
639
  const inputGroups = document.querySelectorAll('.text-input-group');
640
  inputGroups.forEach((group, index) => {
 
647
  });
648
  }
649
 
 
650
  document.addEventListener('click', function(e) {
651
  if (e.target.closest('.remove-input')) {
652
  e.target.closest('.text-input-group').remove();
 
654
  }
655
  });
656
 
 
657
  document.getElementById('clearBtn').addEventListener('click', () => {
658
  document.getElementById('apiTestForm').reset();
659
  const container = document.getElementById('textInputsContainer');
 
668
  document.getElementById('resultsSection').classList.add('hidden');
669
  });
670
 
 
671
  document.getElementById('apiTestForm').addEventListener('submit', async (e) => {
672
  e.preventDefault();
673
 
 
721
  }
722
 
723
  const data = await response.json();
724
+ displayResults(data, responseTime, texts);
725
 
726
  } catch (error) {
727
  console.error('Error analyzing text:', error);
 
732
  }
733
  });
734
 
735
+ function displayResults(data, responseTime, texts) {
 
736
  const resultsSection = document.getElementById('resultsSection');
737
  const resultsContainer = document.getElementById('resultsContainer');
738
 
 
789
  resultsSection.scrollIntoView({ behavior: 'smooth' });
790
  }
791
 
 
792
  document.addEventListener('DOMContentLoaded', () => {
793
  initActivityChart();
794
 
 
795
  document.getElementById('refreshMetrics').addEventListener('click', fetchMetrics);
796
 
 
797
  fetchMetrics();
798
 
 
799
  setInterval(fetchMetrics, 30000);
800
  });
801
  </script>
 
803
  </html>''')
804
 
805
  if __name__ == '__main__':
 
806
  create_directories_and_files()
807
 
808
  port = int(os.getenv('PORT', 7860))