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