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