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