vnai 2.1.8__py3-none-any.whl → 2.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vnai/__init__.py +37 -110
- vnai/beam/__init__.py +0 -2
- vnai/beam/metrics.py +48 -99
- vnai/beam/pulse.py +24 -53
- vnai/beam/quota.py +94 -247
- vnai/flow/__init__.py +1 -4
- vnai/flow/queue.py +17 -50
- vnai/flow/relay.py +98 -204
- vnai/scope/__init__.py +1 -4
- vnai/scope/profile.py +231 -417
- vnai/scope/promo.py +41 -123
- vnai/scope/state.py +52 -119
- {vnai-2.1.8.dist-info → vnai-2.1.9.dist-info}/METADATA +1 -1
- vnai-2.1.9.dist-info/RECORD +16 -0
- vnai-2.1.8.dist-info/RECORD +0 -16
- {vnai-2.1.8.dist-info → vnai-2.1.9.dist-info}/WHEEL +0 -0
- {vnai-2.1.8.dist-info → vnai-2.1.9.dist-info}/top_level.txt +0 -0
vnai/beam/quota.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# vnai/beam/quota.py
|
2
|
-
|
3
1
|
import time
|
4
2
|
import functools
|
5
3
|
import threading
|
@@ -7,266 +5,175 @@ from collections import defaultdict
|
|
7
5
|
from datetime import datetime
|
8
6
|
|
9
7
|
class RateLimitExceeded(Exception):
|
10
|
-
"""Custom exception for rate limit violations."""
|
11
8
|
def __init__(self, resource_type, limit_type="min", current_usage=None, limit_value=None, retry_after=None):
|
12
9
|
self.resource_type = resource_type
|
13
10
|
self.limit_type = limit_type
|
14
11
|
self.current_usage = current_usage
|
15
12
|
self.limit_value = limit_value
|
16
13
|
self.retry_after = retry_after
|
17
|
-
|
18
|
-
# Create a user-friendly message
|
19
|
-
message = f"Bạn đã gửi quá nhiều request tới {resource_type}. "
|
14
|
+
message =f"Bạn đã gửi quá nhiều request tới {resource_type}. "
|
20
15
|
if retry_after:
|
21
|
-
message +=
|
16
|
+
message +=f"Vui lòng thử lại sau {round(retry_after)} giây."
|
22
17
|
else:
|
23
|
-
message +=
|
24
|
-
|
18
|
+
message +="Vui lòng thêm thời gian chờ giữa các lần gửi request."
|
25
19
|
super().__init__(message)
|
26
20
|
|
27
21
|
class Guardian:
|
28
|
-
"""Ensures optimal resource allocation"""
|
29
|
-
|
30
22
|
_instance = None
|
31
23
|
_lock = threading.Lock()
|
32
|
-
|
24
|
+
|
33
25
|
def __new__(cls):
|
34
26
|
with cls._lock:
|
35
27
|
if cls._instance is None:
|
36
28
|
cls._instance = super(Guardian, cls).__new__(cls)
|
37
29
|
cls._instance._initialize()
|
38
30
|
return cls._instance
|
39
|
-
|
31
|
+
|
40
32
|
def _initialize(self):
|
41
|
-
"""Initialize guardian"""
|
42
33
|
self.resource_limits = defaultdict(lambda: defaultdict(int))
|
43
34
|
self.usage_counters = defaultdict(lambda: defaultdict(list))
|
44
|
-
|
45
|
-
|
46
|
-
self.resource_limits["
|
47
|
-
self.resource_limits["
|
48
|
-
self.resource_limits["
|
49
|
-
self.resource_limits["
|
50
|
-
self.resource_limits["
|
51
|
-
self.resource_limits["
|
52
|
-
self.resource_limits["
|
53
|
-
self.resource_limits["
|
54
|
-
self.resource_limits["
|
55
|
-
self.resource_limits["
|
56
|
-
|
57
|
-
self.resource_limits["FAD.ext"] = {"min": 600, "hour": 36000}
|
58
|
-
|
35
|
+
self.resource_limits["default"] = {"min": 60,"hour": 3000}
|
36
|
+
self.resource_limits["TCBS"] = {"min": 60,"hour": 3000}
|
37
|
+
self.resource_limits["VCI"] = {"min": 60,"hour": 3000}
|
38
|
+
self.resource_limits["MBK"] = {"min": 600,"hour": 36000}
|
39
|
+
self.resource_limits["MAS.ext"] = {"min": 600,"hour": 36000}
|
40
|
+
self.resource_limits["VCI.ext"] = {"min": 600,"hour": 36000}
|
41
|
+
self.resource_limits["FMK.ext"] = {"min": 600,"hour": 36000}
|
42
|
+
self.resource_limits["VND.ext"] = {"min": 600,"hour": 36000}
|
43
|
+
self.resource_limits["CAF.ext"] = {"min": 600,"hour": 36000}
|
44
|
+
self.resource_limits["SPL.ext"] = {"min": 600,"hour": 36000}
|
45
|
+
self.resource_limits["VDS.ext"] = {"min": 600,"hour": 36000}
|
46
|
+
self.resource_limits["FAD.ext"] = {"min": 600,"hour": 36000}
|
47
|
+
|
59
48
|
def verify(self, operation_id, resource_type="default"):
|
60
|
-
"""Verify resource availability before operation"""
|
61
49
|
current_time = time.time()
|
62
|
-
|
63
|
-
# Get limits for this resource type (or use default)
|
64
50
|
limits = self.resource_limits.get(resource_type, self.resource_limits["default"])
|
65
|
-
|
66
|
-
# Check minute limit
|
67
51
|
minute_cutoff = current_time - 60
|
68
52
|
self.usage_counters[resource_type]["min"] = [
|
69
|
-
t for t in self.usage_counters[resource_type]["min"]
|
53
|
+
t for t in self.usage_counters[resource_type]["min"]
|
70
54
|
if t > minute_cutoff
|
71
55
|
]
|
72
|
-
|
73
56
|
minute_usage = len(self.usage_counters[resource_type]["min"])
|
74
57
|
minute_exceeded = minute_usage >= limits["min"]
|
75
|
-
|
76
58
|
if minute_exceeded:
|
77
|
-
# Track limit check through metrics module
|
78
59
|
from vnai.beam.metrics import collector
|
79
60
|
collector.record(
|
80
|
-
|
61
|
+
"rate_limit",
|
81
62
|
{
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
63
|
+
"resource_type": resource_type,
|
64
|
+
"limit_type":"min",
|
65
|
+
"limit_value": limits["min"],
|
66
|
+
"current_usage": minute_usage,
|
67
|
+
"is_exceeded": True
|
87
68
|
},
|
88
69
|
priority="high"
|
89
70
|
)
|
90
|
-
# Raise custom exception with retry information
|
91
71
|
raise RateLimitExceeded(
|
92
|
-
resource_type=resource_type,
|
72
|
+
resource_type=resource_type,
|
93
73
|
limit_type="min",
|
94
74
|
current_usage=minute_usage,
|
95
75
|
limit_value=limits["min"],
|
96
|
-
retry_after=60 - (current_time % 60)
|
76
|
+
retry_after=60 - (current_time % 60)
|
97
77
|
)
|
98
|
-
|
99
|
-
# Check hour limit
|
100
78
|
hour_cutoff = current_time - 3600
|
101
79
|
self.usage_counters[resource_type]["hour"] = [
|
102
|
-
t for t in self.usage_counters[resource_type]["hour"]
|
80
|
+
t for t in self.usage_counters[resource_type]["hour"]
|
103
81
|
if t > hour_cutoff
|
104
82
|
]
|
105
|
-
|
106
83
|
hour_usage = len(self.usage_counters[resource_type]["hour"])
|
107
84
|
hour_exceeded = hour_usage >= limits["hour"]
|
108
|
-
|
109
|
-
# Track rate limit check
|
110
85
|
from vnai.beam.metrics import collector
|
111
86
|
collector.record(
|
112
|
-
|
87
|
+
"rate_limit",
|
113
88
|
{
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
89
|
+
"resource_type": resource_type,
|
90
|
+
"limit_type":"hour" if hour_exceeded else"min",
|
91
|
+
"limit_value": limits["hour"] if hour_exceeded else limits["min"],
|
92
|
+
"current_usage": hour_usage if hour_exceeded else minute_usage,
|
93
|
+
"is_exceeded": hour_exceeded
|
119
94
|
}
|
120
95
|
)
|
121
|
-
|
122
96
|
if hour_exceeded:
|
123
|
-
# Raise custom exception with retry information
|
124
97
|
raise RateLimitExceeded(
|
125
|
-
resource_type=resource_type,
|
98
|
+
resource_type=resource_type,
|
126
99
|
limit_type="hour",
|
127
100
|
current_usage=hour_usage,
|
128
101
|
limit_value=limits["hour"],
|
129
|
-
retry_after=3600 - (current_time % 3600)
|
102
|
+
retry_after=3600 - (current_time % 3600)
|
130
103
|
)
|
131
|
-
|
132
|
-
# Record this request
|
133
104
|
self.usage_counters[resource_type]["min"].append(current_time)
|
134
105
|
self.usage_counters[resource_type]["hour"].append(current_time)
|
135
106
|
return True
|
136
|
-
|
107
|
+
|
137
108
|
def usage(self, resource_type="default"):
|
138
|
-
"""Get current usage percentage for resource limits"""
|
139
109
|
current_time = time.time()
|
140
110
|
limits = self.resource_limits.get(resource_type, self.resource_limits["default"])
|
141
|
-
|
142
|
-
# Clean old timestamps
|
143
111
|
minute_cutoff = current_time - 60
|
144
112
|
hour_cutoff = current_time - 3600
|
145
|
-
|
146
113
|
self.usage_counters[resource_type]["min"] = [
|
147
|
-
t for t in self.usage_counters[resource_type]["min"]
|
114
|
+
t for t in self.usage_counters[resource_type]["min"]
|
148
115
|
if t > minute_cutoff
|
149
116
|
]
|
150
|
-
|
151
117
|
self.usage_counters[resource_type]["hour"] = [
|
152
|
-
t for t in self.usage_counters[resource_type]["hour"]
|
118
|
+
t for t in self.usage_counters[resource_type]["hour"]
|
153
119
|
if t > hour_cutoff
|
154
120
|
]
|
155
|
-
|
156
|
-
# Calculate percentages
|
157
121
|
minute_usage = len(self.usage_counters[resource_type]["min"])
|
158
122
|
hour_usage = len(self.usage_counters[resource_type]["hour"])
|
159
|
-
|
160
123
|
minute_percentage = (minute_usage / limits["min"]) * 100 if limits["min"] > 0 else 0
|
161
124
|
hour_percentage = (hour_usage / limits["hour"]) * 100 if limits["hour"] > 0 else 0
|
162
|
-
|
163
|
-
# Return the higher percentage
|
164
125
|
return max(minute_percentage, hour_percentage)
|
165
|
-
|
126
|
+
|
166
127
|
def get_limit_status(self, resource_type="default"):
|
167
|
-
"""Get detailed information about current limit status"""
|
168
128
|
current_time = time.time()
|
169
129
|
limits = self.resource_limits.get(resource_type, self.resource_limits["default"])
|
170
|
-
|
171
|
-
# Clean old timestamps
|
172
130
|
minute_cutoff = current_time - 60
|
173
131
|
hour_cutoff = current_time - 3600
|
174
|
-
|
175
132
|
minute_usage = len([t for t in self.usage_counters[resource_type]["min"] if t > minute_cutoff])
|
176
133
|
hour_usage = len([t for t in self.usage_counters[resource_type]["hour"] if t > hour_cutoff])
|
177
|
-
|
178
134
|
return {
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
135
|
+
"resource_type": resource_type,
|
136
|
+
"minute_limit": {
|
137
|
+
"usage": minute_usage,
|
138
|
+
"limit": limits["min"],
|
139
|
+
"percentage": (minute_usage / limits["min"]) * 100 if limits["min"] > 0 else 0,
|
140
|
+
"remaining": max(0, limits["min"] - minute_usage),
|
141
|
+
"reset_in_seconds": 60 - (current_time % 60)
|
186
142
|
},
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
143
|
+
"hour_limit": {
|
144
|
+
"usage": hour_usage,
|
145
|
+
"limit": limits["hour"],
|
146
|
+
"percentage": (hour_usage / limits["hour"]) * 100 if limits["hour"] > 0 else 0,
|
147
|
+
"remaining": max(0, limits["hour"] - hour_usage),
|
148
|
+
"reset_in_seconds": 3600 - (current_time % 3600)
|
193
149
|
}
|
194
150
|
}
|
195
|
-
|
196
|
-
# Create singleton instance
|
197
151
|
guardian = Guardian()
|
198
152
|
|
199
153
|
class CleanErrorContext:
|
200
|
-
"""Context manager to clean up tracebacks for rate limits"""
|
201
|
-
# Class variable to track if a message has been displayed recently
|
202
154
|
_last_message_time = 0
|
203
|
-
_message_cooldown = 5
|
204
|
-
|
155
|
+
_message_cooldown = 5
|
156
|
+
|
205
157
|
def __enter__(self):
|
206
158
|
return self
|
207
|
-
|
159
|
+
|
208
160
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
209
161
|
if exc_type is RateLimitExceeded:
|
210
162
|
current_time = time.time()
|
211
|
-
|
212
|
-
# Only print the message if enough time has passed since the last one
|
213
163
|
if current_time - CleanErrorContext._last_message_time >= CleanErrorContext._message_cooldown:
|
214
164
|
print(f"\n⚠️ {str(exc_val)}\n")
|
215
165
|
CleanErrorContext._last_message_time = current_time
|
216
|
-
|
217
|
-
# Re-raise the exception more forcefully to ensure it propagates
|
218
|
-
# This will bypass any try/except blocks that might be catching RateLimitExceeded
|
219
166
|
import sys
|
220
167
|
sys.exit(f"Rate limit exceeded. {str(exc_val)} Process terminated.")
|
221
|
-
|
222
|
-
# The line below won't be reached, but we keep it for clarity
|
223
168
|
return False
|
224
169
|
return False
|
225
170
|
|
226
|
-
|
227
|
-
def optimize(resource_type='default', loop_threshold=10, time_window=5, ad_cooldown=150, content_trigger_threshold=3,
|
171
|
+
def optimize(resource_type='default', loop_threshold=10, time_window=5, ad_cooldown=150, content_trigger_threshold=3,
|
228
172
|
max_retries=2, backoff_factor=2, debug=False):
|
229
|
-
"""
|
230
|
-
Decorator that optimizes function execution, tracks metrics, and detects loop patterns for ad opportunities.
|
231
|
-
|
232
|
-
Features:
|
233
|
-
- Resource verification
|
234
|
-
- Performance metrics collection
|
235
|
-
- Loop detection for ad/content opportunities
|
236
|
-
- Automatic retry with exponential backoff for rate limit errors
|
237
|
-
|
238
|
-
Args:
|
239
|
-
resource_type: Type of resource used by function ("network", "database", "cpu", "memory", "io", "default")
|
240
|
-
loop_threshold: Number of calls within time_window to consider as a loop (min: 2)
|
241
|
-
time_window: Time period in seconds to consider for loop detection
|
242
|
-
ad_cooldown: Minimum seconds between showing ads for the same function
|
243
|
-
content_trigger_threshold: Number of consecutive loop detections before triggering content (min: 1)
|
244
|
-
max_retries: Maximum number of times to retry when rate limits are hit
|
245
|
-
backoff_factor: Base factor for exponential backoff (wait time = backoff_factor^retry_count)
|
246
|
-
debug: When True, prints diagnostic information about loop detection
|
247
|
-
|
248
|
-
Examples:
|
249
|
-
@optimize
|
250
|
-
def simple_function():
|
251
|
-
return "result"
|
252
|
-
|
253
|
-
@optimize("network")
|
254
|
-
def fetch_stock_data(symbol):
|
255
|
-
# Makes network calls
|
256
|
-
return data
|
257
|
-
|
258
|
-
@optimize("database", loop_threshold=4, time_window=10)
|
259
|
-
def query_financial_data(params):
|
260
|
-
# Database queries
|
261
|
-
return results
|
262
|
-
"""
|
263
|
-
# Handle case where decorator is used without arguments: @optimize
|
264
173
|
if callable(resource_type):
|
265
174
|
func = resource_type
|
266
|
-
return _create_wrapper(func,
|
175
|
+
return _create_wrapper(func,'default', loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
|
267
176
|
max_retries, backoff_factor, debug)
|
268
|
-
|
269
|
-
# Basic validation
|
270
177
|
if loop_threshold < 2:
|
271
178
|
raise ValueError(f"loop_threshold must be at least 2, got {loop_threshold}")
|
272
179
|
if time_window <= 0:
|
@@ -277,117 +184,80 @@ def optimize(resource_type='default', loop_threshold=10, time_window=5, ad_coold
|
|
277
184
|
raise ValueError(f"max_retries must be non-negative, got {max_retries}")
|
278
185
|
if backoff_factor <= 0:
|
279
186
|
raise ValueError(f"backoff_factor must be positive, got {backoff_factor}")
|
280
|
-
|
281
|
-
# Return the actual decorator
|
187
|
+
|
282
188
|
def decorator(func):
|
283
|
-
return _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
|
189
|
+
return _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
|
284
190
|
max_retries, backoff_factor, debug)
|
285
191
|
return decorator
|
286
192
|
|
287
|
-
def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
|
193
|
+
def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
|
288
194
|
max_retries, backoff_factor, debug):
|
289
|
-
"""Creates the function wrapper with call tracking for loop detection"""
|
290
|
-
# Static storage for each decorated function instance
|
291
195
|
call_history = []
|
292
196
|
last_ad_time = 0
|
293
197
|
consecutive_loop_detections = 0
|
294
|
-
session_displayed = False
|
198
|
+
session_displayed = False
|
295
199
|
session_start_time = time.time()
|
296
|
-
session_timeout = 1800
|
297
|
-
|
200
|
+
session_timeout = 1800
|
298
201
|
@functools.wraps(func)
|
202
|
+
|
299
203
|
def wrapper(*args, **kwargs):
|
300
204
|
nonlocal last_ad_time, consecutive_loop_detections, session_displayed, session_start_time
|
301
205
|
current_time = time.time()
|
302
206
|
content_triggered = False
|
303
|
-
|
304
|
-
# Reset session if it has expired
|
305
207
|
if current_time - session_start_time > session_timeout:
|
306
208
|
session_displayed = False
|
307
209
|
session_start_time = current_time
|
308
|
-
|
309
|
-
# For automatic retries with rate limits
|
310
210
|
retries = 0
|
311
211
|
while True:
|
312
|
-
# ===== LOOP DETECTION LOGIC =====
|
313
|
-
# Add current call to history
|
314
212
|
call_history.append(current_time)
|
315
|
-
|
316
|
-
# Prune old calls outside the time window
|
317
213
|
while call_history and current_time - call_history[0] > time_window:
|
318
214
|
call_history.pop(0)
|
319
|
-
|
320
|
-
# Check if we're in a loop pattern
|
321
215
|
loop_detected = len(call_history) >= loop_threshold
|
322
|
-
|
323
216
|
if debug and loop_detected:
|
324
217
|
print(f"[OPTIMIZE] Đã phát hiện vòng lặp cho {func.__name__}: {len(call_history)} lần gọi trong {time_window}s")
|
325
|
-
|
326
|
-
# Handle loop detection
|
327
218
|
if loop_detected:
|
328
219
|
consecutive_loop_detections += 1
|
329
220
|
if debug:
|
330
221
|
print(f"[OPTIMIZE] Số lần phát hiện vòng lặp liên tiếp: {consecutive_loop_detections}/{content_trigger_threshold}")
|
331
222
|
else:
|
332
223
|
consecutive_loop_detections = 0
|
333
|
-
|
334
|
-
# Determine if we should show content - add session_displayed check
|
335
|
-
should_show_content = (consecutive_loop_detections >= content_trigger_threshold) and \
|
336
|
-
(current_time - last_ad_time >= ad_cooldown) and \
|
337
|
-
not session_displayed
|
338
|
-
|
339
|
-
# Handle content opportunity
|
224
|
+
should_show_content = (consecutive_loop_detections >= content_trigger_threshold) and (current_time - last_ad_time >= ad_cooldown) and not session_displayed
|
340
225
|
if should_show_content:
|
341
226
|
last_ad_time = current_time
|
342
227
|
consecutive_loop_detections = 0
|
343
228
|
content_triggered = True
|
344
|
-
session_displayed = True
|
345
|
-
|
229
|
+
session_displayed = True
|
346
230
|
if debug:
|
347
231
|
print(f"[OPTIMIZE] Đã kích hoạt nội dung cho {func.__name__}")
|
348
|
-
|
349
|
-
# Trigger content display using promo manager with "loop" context
|
350
232
|
try:
|
351
233
|
from vnai.scope.promo import manager
|
352
|
-
|
353
|
-
# Get environment if available
|
354
234
|
try:
|
355
235
|
from vnai.scope.profile import inspector
|
356
236
|
environment = inspector.examine().get("environment", None)
|
357
237
|
manager.present_content(environment=environment, context="loop")
|
358
238
|
except ImportError:
|
359
239
|
manager.present_content(context="loop")
|
360
|
-
|
361
240
|
except ImportError:
|
362
|
-
# Fallback if content manager is not available
|
363
241
|
print(f"Phát hiện vòng lặp: Hàm '{func.__name__}' đang được gọi trong một vòng lặp")
|
364
242
|
except Exception as e:
|
365
|
-
# Don't let content errors affect the main function
|
366
243
|
if debug:
|
367
244
|
print(f"[OPTIMIZE] Lỗi khi hiển thị nội dung: {str(e)}")
|
368
|
-
|
369
|
-
# ===== RESOURCE VERIFICATION =====
|
370
245
|
try:
|
371
|
-
# Use a context manager to clean up the traceback
|
372
246
|
with CleanErrorContext():
|
373
247
|
guardian.verify(func.__name__, resource_type)
|
374
|
-
|
375
248
|
except RateLimitExceeded as e:
|
376
|
-
# Record the rate limit error
|
377
249
|
from vnai.beam.metrics import collector
|
378
250
|
collector.record(
|
379
|
-
|
251
|
+
"error",
|
380
252
|
{
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
253
|
+
"function": func.__name__,
|
254
|
+
"error": str(e),
|
255
|
+
"context":"resource_verification",
|
256
|
+
"resource_type": resource_type,
|
257
|
+
"retry_attempt": retries
|
386
258
|
},
|
387
259
|
priority="high"
|
388
260
|
)
|
389
|
-
|
390
|
-
# Display rate limit content ONLY if we haven't shown any content this session
|
391
261
|
if not session_displayed:
|
392
262
|
try:
|
393
263
|
from vnai.scope.promo import manager
|
@@ -395,40 +265,29 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
|
|
395
265
|
from vnai.scope.profile import inspector
|
396
266
|
environment = inspector.examine().get("environment", None)
|
397
267
|
manager.present_content(environment=environment, context="loop")
|
398
|
-
session_displayed = True
|
268
|
+
session_displayed = True
|
399
269
|
last_ad_time = current_time
|
400
270
|
except ImportError:
|
401
271
|
manager.present_content(context="loop")
|
402
272
|
session_displayed = True
|
403
273
|
last_ad_time = current_time
|
404
274
|
except Exception:
|
405
|
-
pass
|
406
|
-
|
407
|
-
# Continue with retry logic
|
275
|
+
pass
|
408
276
|
if retries < max_retries:
|
409
277
|
wait_time = backoff_factor ** retries
|
410
278
|
retries += 1
|
411
|
-
|
412
|
-
# If the exception has a retry_after value, use that instead
|
413
|
-
if hasattr(e, "retry_after") and e.retry_after:
|
279
|
+
if hasattr(e,"retry_after") and e.retry_after:
|
414
280
|
wait_time = min(wait_time, e.retry_after)
|
415
|
-
|
416
281
|
if debug:
|
417
282
|
print(f"[OPTIMIZE] Đã đạt giới hạn tốc độ cho {func.__name__}, thử lại sau {wait_time} giây (lần thử {retries}/{max_retries})")
|
418
|
-
|
419
283
|
time.sleep(wait_time)
|
420
|
-
continue
|
284
|
+
continue
|
421
285
|
else:
|
422
|
-
# No more retries, re-raise the exception
|
423
286
|
raise
|
424
|
-
|
425
|
-
# ===== FUNCTION EXECUTION & METRICS =====
|
426
287
|
start_time = time.time()
|
427
288
|
success = False
|
428
289
|
error = None
|
429
|
-
|
430
290
|
try:
|
431
|
-
# Execute the original function
|
432
291
|
result = func(*args, **kwargs)
|
433
292
|
success = True
|
434
293
|
return result
|
@@ -436,51 +295,39 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
|
|
436
295
|
error = str(e)
|
437
296
|
raise
|
438
297
|
finally:
|
439
|
-
# Calculate execution metrics
|
440
298
|
execution_time = time.time() - start_time
|
441
|
-
|
442
|
-
# Record metrics
|
443
299
|
try:
|
444
300
|
from vnai.beam.metrics import collector
|
445
301
|
collector.record(
|
446
|
-
|
302
|
+
"function",
|
447
303
|
{
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
304
|
+
"function": func.__name__,
|
305
|
+
"resource_type": resource_type,
|
306
|
+
"execution_time": execution_time,
|
307
|
+
"success": success,
|
308
|
+
"error": error,
|
309
|
+
"in_loop": loop_detected,
|
310
|
+
"loop_depth": len(call_history),
|
311
|
+
"content_triggered": content_triggered,
|
312
|
+
"timestamp": datetime.now().isoformat(),
|
313
|
+
"retry_count": retries if retries > 0 else None
|
458
314
|
}
|
459
315
|
)
|
460
|
-
|
461
|
-
# Record content opportunity metrics if detected
|
462
316
|
if content_triggered:
|
463
317
|
collector.record(
|
464
|
-
|
318
|
+
"ad_opportunity",
|
465
319
|
{
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
320
|
+
"function": func.__name__,
|
321
|
+
"resource_type": resource_type,
|
322
|
+
"call_frequency": len(call_history) / time_window,
|
323
|
+
"consecutive_loops": consecutive_loop_detections,
|
324
|
+
"timestamp": datetime.now().isoformat()
|
471
325
|
}
|
472
326
|
)
|
473
327
|
except ImportError:
|
474
|
-
# Metrics module not available, just continue
|
475
328
|
pass
|
476
|
-
|
477
|
-
# If we got here, the function executed successfully, so break the retry loop
|
478
329
|
break
|
479
|
-
|
480
330
|
return wrapper
|
481
331
|
|
482
|
-
|
483
|
-
# Helper function for getting the current rate limit status
|
484
332
|
def rate_limit_status(resource_type="default"):
|
485
|
-
|
486
|
-
return guardian.get_limit_status(resource_type)
|
333
|
+
return guardian.get_limit_status(resource_type)
|
vnai/flow/__init__.py
CHANGED