vnai 2.1.2__py3-none-any.whl → 2.1.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,105 +1,333 @@
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
+ import time
2
+ import functools
3
+ import threading
9
4
  from collections import defaultdict
10
5
  from datetime import datetime
6
+
11
7
  class RateLimitExceeded(Exception):
12
- def __init__(self,resource_type,limit_type=_A,current_usage=_D,limit_value=_D,retry_after=_D):
13
- self.resource_type=resource_type;self.limit_type=limit_type;self.current_usage=current_usage;self.limit_value=limit_value;self.retry_after=retry_after;message=f"Bạn đã gửi quá nhiều request tới {resource_type}. "
14
- if retry_after:message+=f"Vui lòng thử lại sau {round(retry_after)} giây."
15
- else:message+='Vui lòng thêm thời gian chờ giữa các lần gửi request.'
16
- super().__init__(message)
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
+
17
21
  class Guardian:
18
- _instance=_D;_lock=threading.Lock()
19
- def __new__(cls):
20
- with cls._lock:
21
- if cls._instance is _D:cls._instance=super(Guardian,cls).__new__(cls);cls._instance._initialize()
22
- return cls._instance
23
- def _initialize(self):self.resource_limits=defaultdict(lambda:defaultdict(int));self.usage_counters=defaultdict(lambda:defaultdict(list));self.resource_limits[_C]={_A:60,_B:3000};self.resource_limits['TCBS']={_A:60,_B:3000};self.resource_limits['VCI']={_A:60,_B:3000};self.resource_limits['MBK']={_A:600,_B:36000};self.resource_limits['MAS.ext']={_A:600,_B:36000};self.resource_limits['VCI.ext']={_A:600,_B:36000};self.resource_limits['FMK.ext']={_A:600,_B:36000};self.resource_limits['VND.ext']={_A:600,_B:36000};self.resource_limits['CAF.ext']={_A:600,_B:36000};self.resource_limits['SPL.ext']={_A:600,_B:36000};self.resource_limits['VDS.ext']={_A:600,_B:36000};self.resource_limits['FAD.ext']={_A:600,_B:36000}
24
- def verify(self,operation_id,resource_type=_C):
25
- E='is_exceeded';D='current_usage';C='limit_value';B='limit_type';A='rate_limit';current_time=time.time();limits=self.resource_limits.get(resource_type,self.resource_limits[_C]);minute_cutoff=current_time-60;self.usage_counters[resource_type][_A]=[t for t in self.usage_counters[resource_type][_A]if t>minute_cutoff];minute_usage=len(self.usage_counters[resource_type][_A]);minute_exceeded=minute_usage>=limits[_A]
26
- if minute_exceeded:from vnai.beam.metrics import collector;collector.record(A,{_G:resource_type,B:_A,C:limits[_A],D:minute_usage,E:_E},priority='high');raise RateLimitExceeded(resource_type=resource_type,limit_type=_A,current_usage=minute_usage,limit_value=limits[_A],retry_after=60-current_time%60)
27
- hour_cutoff=current_time-3600;self.usage_counters[resource_type][_B]=[t for t in self.usage_counters[resource_type][_B]if t>hour_cutoff];hour_usage=len(self.usage_counters[resource_type][_B]);hour_exceeded=hour_usage>=limits[_B];from vnai.beam.metrics import collector;collector.record(A,{_G:resource_type,B:_B if hour_exceeded else _A,C:limits[_B]if hour_exceeded else limits[_A],D:hour_usage if hour_exceeded else minute_usage,E:hour_exceeded})
28
- if hour_exceeded:raise RateLimitExceeded(resource_type=resource_type,limit_type=_B,current_usage=hour_usage,limit_value=limits[_B],retry_after=3600-current_time%3600)
29
- self.usage_counters[resource_type][_A].append(current_time);self.usage_counters[resource_type][_B].append(current_time);return _E
30
- def usage(self,resource_type=_C):current_time=time.time();limits=self.resource_limits.get(resource_type,self.resource_limits[_C]);minute_cutoff=current_time-60;hour_cutoff=current_time-3600;self.usage_counters[resource_type][_A]=[t for t in self.usage_counters[resource_type][_A]if t>minute_cutoff];self.usage_counters[resource_type][_B]=[t for t in self.usage_counters[resource_type][_B]if t>hour_cutoff];minute_usage=len(self.usage_counters[resource_type][_A]);hour_usage=len(self.usage_counters[resource_type][_B]);minute_percentage=minute_usage/limits[_A]*100 if limits[_A]>0 else 0;hour_percentage=hour_usage/limits[_B]*100 if limits[_B]>0 else 0;return max(minute_percentage,hour_percentage)
31
- def get_limit_status(self,resource_type=_C):E='reset_in_seconds';D='remaining';C='percentage';B='limit';A='usage';current_time=time.time();limits=self.resource_limits.get(resource_type,self.resource_limits[_C]);minute_cutoff=current_time-60;hour_cutoff=current_time-3600;minute_usage=len([t for t in self.usage_counters[resource_type][_A]if t>minute_cutoff]);hour_usage=len([t for t in self.usage_counters[resource_type][_B]if t>hour_cutoff]);return{_G:resource_type,'minute_limit':{A:minute_usage,B:limits[_A],C:minute_usage/limits[_A]*100 if limits[_A]>0 else 0,D:max(0,limits[_A]-minute_usage),E:60-current_time%60},'hour_limit':{A:hour_usage,B:limits[_B],C:hour_usage/limits[_B]*100 if limits[_B]>0 else 0,D:max(0,limits[_B]-hour_usage),E:3600-current_time%3600}}
32
- guardian=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
+
33
153
  class CleanErrorContext:
34
- _last_message_time=0;_message_cooldown=5
35
- def __enter__(self):return self
36
- def __exit__(self,exc_type,exc_val,exc_tb):
37
- if exc_type is RateLimitExceeded:
38
- current_time=time.time()
39
- if current_time-CleanErrorContext._last_message_time>=CleanErrorContext._message_cooldown:print(f"\n⚠️ {str(exc_val)}\n");CleanErrorContext._last_message_time=current_time
40
- import sys;sys.exit(f"Rate limit exceeded. {str(exc_val)} Process terminated.");return _F
41
- return _F
42
- 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):
43
- if callable(resource_type):func=resource_type;return _create_wrapper(func,_C,loop_threshold,time_window,ad_cooldown,content_trigger_threshold,max_retries,backoff_factor,debug)
44
- if loop_threshold<2:raise ValueError(f"loop_threshold must be at least 2, got {loop_threshold}")
45
- if time_window<=0:raise ValueError(f"time_window must be positive, got {time_window}")
46
- if content_trigger_threshold<1:raise ValueError(f"content_trigger_threshold must be at least 1, got {content_trigger_threshold}")
47
- if max_retries<0:raise ValueError(f"max_retries must be non-negative, got {max_retries}")
48
- if backoff_factor<=0:raise ValueError(f"backoff_factor must be positive, got {backoff_factor}")
49
- def decorator(func):return _create_wrapper(func,resource_type,loop_threshold,time_window,ad_cooldown,content_trigger_threshold,max_retries,backoff_factor,debug)
50
- return decorator
51
- def _create_wrapper(func,resource_type,loop_threshold,time_window,ad_cooldown,content_trigger_threshold,max_retries,backoff_factor,debug):
52
- call_history=[];last_ad_time=0;consecutive_loop_detections=0;session_displayed=_F;session_start_time=time.time();session_timeout=1800
53
- @functools.wraps(func)
54
- def wrapper(*args,**kwargs):
55
- E='timestamp';D='environment';C='error';B='function';A='loop';nonlocal last_ad_time,consecutive_loop_detections,session_displayed,session_start_time;current_time=time.time();content_triggered=_F
56
- if current_time-session_start_time>session_timeout:session_displayed=_F;session_start_time=current_time
57
- retries=0
58
- while _E:
59
- call_history.append(current_time)
60
- while call_history and current_time-call_history[0]>time_window:call_history.pop(0)
61
- loop_detected=len(call_history)>=loop_threshold
62
- if debug and loop_detected: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")
63
- if loop_detected:
64
- consecutive_loop_detections+=1
65
- if debug:print(f"[OPTIMIZE] Số lần phát hiện vòng lặp liên tiếp: {consecutive_loop_detections}/{content_trigger_threshold}")
66
- else:consecutive_loop_detections=0
67
- should_show_content=consecutive_loop_detections>=content_trigger_threshold and current_time-last_ad_time>=ad_cooldown and not session_displayed
68
- if should_show_content:
69
- last_ad_time=current_time;consecutive_loop_detections=0;content_triggered=_E;session_displayed=_E
70
- if debug:print(f"[OPTIMIZE] Đã kích hoạt nội dung cho {func.__name__}")
71
- try:
72
- from vnai.scope.promo import manager
73
- try:from vnai.scope.profile import inspector;environment=inspector.examine().get(D,_D);manager.present_content(environment=environment,context=A)
74
- except ImportError:manager.present_content(context=A)
75
- except ImportError: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")
76
- except Exception as e:
77
- if debug:print(f"[OPTIMIZE] Lỗi khi hiển thị nội dung: {str(e)}")
78
- try:
79
- with CleanErrorContext():guardian.verify(func.__name__,resource_type)
80
- except RateLimitExceeded as e:
81
- from vnai.beam.metrics import collector;collector.record(C,{B:func.__name__,C:str(e),'context':'resource_verification',_G:resource_type,'retry_attempt':retries},priority='high')
82
- if not session_displayed:
83
- try:
84
- from vnai.scope.promo import manager
85
- try:from vnai.scope.profile import inspector;environment=inspector.examine().get(D,_D);manager.present_content(environment=environment,context=A);session_displayed=_E;last_ad_time=current_time
86
- except ImportError:manager.present_content(context=A);session_displayed=_E;last_ad_time=current_time
87
- except Exception:pass
88
- if retries<max_retries:
89
- wait_time=backoff_factor**retries;retries+=1
90
- if hasattr(e,'retry_after')and e.retry_after:wait_time=min(wait_time,e.retry_after)
91
- if debug: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})")
92
- time.sleep(wait_time);continue
93
- else:raise
94
- start_time=time.time();success=_F;error=_D
95
- try:result=func(*args,**kwargs);success=_E;return result
96
- except Exception as e:error=str(e);raise
97
- finally:
98
- execution_time=time.time()-start_time
99
- try:
100
- from vnai.beam.metrics import collector;collector.record(B,{B:func.__name__,_G:resource_type,'execution_time':execution_time,'success':success,C:error,'in_loop':loop_detected,'loop_depth':len(call_history),'content_triggered':content_triggered,E:datetime.now().isoformat(),'retry_count':retries if retries>0 else _D})
101
- if content_triggered:collector.record('ad_opportunity',{B:func.__name__,_G:resource_type,'call_frequency':len(call_history)/time_window,'consecutive_loops':consecutive_loop_detections,E:datetime.now().isoformat()})
102
- except ImportError:pass
103
- break
104
- return wrapper
105
- def rate_limit_status(resource_type=_C):return guardian.get_limit_status(resource_type)
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)
vnai/flow/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- from vnai.flow.relay import conduit,configure
1
+ from vnai.flow.relay import conduit, configure
2
2
  from vnai.flow.queue import buffer
vnai/flow/queue.py CHANGED
@@ -1,55 +1,100 @@
1
- _C='category'
2
- _B=True
3
- _A=None
4
- import time,threading,json
1
+ import time
2
+ import threading
3
+ import json
5
4
  from datetime import datetime
6
5
  from pathlib import Path
6
+
7
7
  class Buffer:
8
- _instance=_A;_lock=threading.Lock()
9
- def __new__(cls):
10
- with cls._lock:
11
- if cls._instance is _A:cls._instance=super(Buffer,cls).__new__(cls);cls._instance._initialize()
12
- return cls._instance
13
- def _initialize(self):self.data=[];self.lock=threading.Lock();self.max_size=1000;self.backup_interval=300;self.home_dir=Path.home();self.project_dir=self.home_dir/'.vnstock';self.project_dir.mkdir(exist_ok=_B);self.data_dir=self.project_dir/'data';self.data_dir.mkdir(exist_ok=_B);self.backup_path=self.data_dir/'buffer_backup.json';self._load_from_backup();self._start_backup_thread()
14
- def _load_from_backup(self):
15
- if self.backup_path.exists():
16
- try:
17
- with open(self.backup_path,'r')as f:backup_data=json.load(f)
18
- with self.lock:self.data=backup_data
19
- except:pass
20
- def _save_to_backup(self):
21
- with self.lock:
22
- if not self.data:return
23
- try:
24
- with open(self.backup_path,'w')as f:json.dump(self.data,f)
25
- except:pass
26
- def _start_backup_thread(self):
27
- def backup_task():
28
- while _B:time.sleep(self.backup_interval);self._save_to_backup()
29
- backup_thread=threading.Thread(target=backup_task,daemon=_B);backup_thread.start()
30
- def add(self,item,category=_A):
31
- A='timestamp'
32
- with self.lock:
33
- if isinstance(item,dict):
34
- if A not in item:item[A]=datetime.now().isoformat()
35
- if category:item[_C]=category
36
- self.data.append(item)
37
- if len(self.data)>self.max_size:self.data=self.data[-self.max_size:]
38
- if len(self.data)%100==0:self._save_to_backup()
39
- return len(self.data)
40
- def get(self,count=_A,category=_A):
41
- with self.lock:
42
- if category:filtered_data=[item for item in self.data if item.get(_C)==category]
43
- else:filtered_data=self.data.copy()
44
- if count:return filtered_data[:count]
45
- else:return filtered_data
46
- def clear(self,category=_A):
47
- with self.lock:
48
- if category:self.data=[item for item in self.data if item.get(_C)!=category]
49
- else:self.data=[]
50
- self._save_to_backup();return len(self.data)
51
- def size(self,category=_A):
52
- with self.lock:
53
- if category:return len([item for item in self.data if item.get(_C)==category])
54
- else:return len(self.data)
55
- buffer=Buffer()
8
+ _instance = None
9
+ _lock = threading.Lock()
10
+
11
+ def __new__(cls):
12
+ with cls._lock:
13
+ if cls._instance is None:
14
+ cls._instance = super(Buffer, cls).__new__(cls)
15
+ cls._instance._initialize()
16
+ return cls._instance
17
+
18
+ def _initialize(self):
19
+ self.data = []
20
+ self.lock = threading.Lock()
21
+ self.max_size = 1000
22
+ self.backup_interval = 300
23
+ self.home_dir = Path.home()
24
+ self.project_dir = self.home_dir /".vnstock"
25
+ self.project_dir.mkdir(exist_ok=True)
26
+ self.data_dir = self.project_dir /'data'
27
+ self.data_dir.mkdir(exist_ok=True)
28
+ self.backup_path = self.data_dir /"buffer_backup.json"
29
+ self._load_from_backup()
30
+ self._start_backup_thread()
31
+
32
+ def _load_from_backup(self):
33
+ if self.backup_path.exists():
34
+ try:
35
+ with open(self.backup_path,'r') as f:
36
+ backup_data = json.load(f)
37
+ with self.lock:
38
+ self.data = backup_data
39
+ except:
40
+ pass
41
+
42
+ def _save_to_backup(self):
43
+ with self.lock:
44
+ if not self.data:
45
+ return
46
+ try:
47
+ with open(self.backup_path,'w') as f:
48
+ json.dump(self.data, f)
49
+ except:
50
+ pass
51
+
52
+ def _start_backup_thread(self):
53
+ def backup_task():
54
+ while True:
55
+ time.sleep(self.backup_interval)
56
+ self._save_to_backup()
57
+ backup_thread = threading.Thread(target=backup_task, daemon=True)
58
+ backup_thread.start()
59
+
60
+ def add(self, item, category=None):
61
+ with self.lock:
62
+ if isinstance(item, dict):
63
+ if"timestamp" not in item:
64
+ item["timestamp"] = datetime.now().isoformat()
65
+ if category:
66
+ item["category"] = category
67
+ self.data.append(item)
68
+ if len(self.data) > self.max_size:
69
+ self.data = self.data[-self.max_size:]
70
+ if len(self.data) % 100 == 0:
71
+ self._save_to_backup()
72
+ return len(self.data)
73
+
74
+ def get(self, count=None, category=None):
75
+ with self.lock:
76
+ if category:
77
+ filtered_data = [item for item in self.data if item.get("category") == category]
78
+ else:
79
+ filtered_data = self.data.copy()
80
+ if count:
81
+ return filtered_data[:count]
82
+ else:
83
+ return filtered_data
84
+
85
+ def clear(self, category=None):
86
+ with self.lock:
87
+ if category:
88
+ self.data = [item for item in self.data if item.get("category") != category]
89
+ else:
90
+ self.data = []
91
+ self._save_to_backup()
92
+ return len(self.data)
93
+
94
+ def size(self, category=None):
95
+ with self.lock:
96
+ if category:
97
+ return len([item for item in self.data if item.get("category") == category])
98
+ else:
99
+ return len(self.data)
100
+ buffer = Buffer()