vectorvein 0.1.87__py3-none-any.whl → 0.1.89__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.
@@ -0,0 +1,312 @@
1
+ import time
2
+ import asyncio
3
+ from typing import Tuple
4
+ from collections import defaultdict
5
+ from abc import ABC, abstractmethod
6
+
7
+
8
+ class AsyncRateLimiterBackend(ABC):
9
+ """Rate Limiter Backend Abstract Base Class"""
10
+
11
+ @abstractmethod
12
+ async def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1) -> Tuple[bool, float]:
13
+ """Returns (allowed, wait_time)"""
14
+ pass
15
+
16
+
17
+ class SyncRateLimiterBackend(ABC):
18
+ """Rate Limiter Backend Abstract Base Class"""
19
+
20
+ @abstractmethod
21
+ def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1) -> Tuple[bool, float]:
22
+ """Returns (allowed, wait_time)"""
23
+ pass
24
+
25
+
26
+ class AsyncMemoryRateLimiter(AsyncRateLimiterBackend):
27
+ """Async Memory Rate Limiter"""
28
+
29
+ def __init__(self):
30
+ self.windows = defaultdict(list)
31
+ self.tokens = defaultdict(int)
32
+ self.lock = asyncio.Lock()
33
+
34
+ def _get_last_reset(self, key):
35
+ return self.windows[key][0] if self.windows[key] else time.time()
36
+
37
+ async def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1):
38
+ async with self.lock:
39
+ now = time.time()
40
+
41
+ # RPM 检查
42
+ window = self.windows[key]
43
+ window = [t for t in window if t > now - 60]
44
+ if len(window) >= rpm:
45
+ return False, 60 - (now - window[0])
46
+
47
+ # TPM 检查
48
+ if self.tokens[key] + request_cost > tpm:
49
+ return False, 60 - (now - self._get_last_reset(key))
50
+
51
+ window.append(now)
52
+ self.tokens[key] += request_cost
53
+ self.windows[key] = window[-rpm:]
54
+ return True, 0
55
+
56
+
57
+ class SyncMemoryRateLimiter(SyncRateLimiterBackend):
58
+ """Sync Memory Rate Limiter"""
59
+
60
+ def __init__(self):
61
+ self.windows = defaultdict(list)
62
+ self.tokens = defaultdict(int)
63
+ self.lock = asyncio.Lock()
64
+
65
+ def _get_last_reset(self, key):
66
+ return self.windows[key][0] if self.windows[key] else time.time()
67
+
68
+ def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1) -> Tuple[bool, float]:
69
+ """Sync Rate Limiter Check
70
+
71
+ Args:
72
+ key: Rate Limiter Key
73
+ rpm: Requests per minute limit
74
+ tpm: Tokens per minute limit
75
+ request_cost: The number of tokens consumed by this request
76
+
77
+
78
+
79
+ Returns:
80
+ Tuple[bool, float]: (allowed, wait_time)
81
+ """
82
+ now = time.time()
83
+
84
+ # RPM 检查
85
+ window = self.windows[key]
86
+ window = [t for t in window if t > now - 60]
87
+ if len(window) >= rpm:
88
+ return False, 60 - (now - window[0])
89
+
90
+ # TPM 检查
91
+ if self.tokens[key] + request_cost > tpm:
92
+ return False, 60 - (now - self._get_last_reset(key))
93
+
94
+ window.append(now)
95
+ self.tokens[key] += request_cost
96
+ self.windows[key] = window[-rpm:]
97
+ return True, 0
98
+
99
+
100
+ REDIS_SCRIPT = """
101
+ local key = KEYS[1]
102
+ local rpm = tonumber(ARGV[1])
103
+ local tpm = tonumber(ARGV[2])
104
+ local cost = tonumber(ARGV[3])
105
+
106
+ -- 使用Redis服务器时间(精确到微秒)
107
+ local server_time = redis.call('TIME')
108
+ local now = tonumber(server_time[1]) + tonumber(server_time[2])/1000000
109
+
110
+ -- RPM限制检查
111
+ local rpm_key = key..'_rpm'
112
+ local elements = redis.call('LRANGE', rpm_key, 0, -1)
113
+ local valid_elements = {}
114
+ local min_valid_time = now - 60
115
+
116
+ -- 过滤过期时间戳
117
+ for _, ts in ipairs(elements) do
118
+ local timestamp = tonumber(ts)
119
+ if timestamp > min_valid_time then
120
+ table.insert(valid_elements, timestamp)
121
+ end
122
+ end
123
+ local valid_count = #valid_elements
124
+
125
+ -- 新增:自动清理过期时间戳
126
+ if valid_count > 0 then
127
+ redis.call('DEL', rpm_key)
128
+ for _, ts in ipairs(valid_elements) do
129
+ redis.call('RPUSH', rpm_key, ts)
130
+ end
131
+ redis.call('EXPIRE', rpm_key, 60)
132
+ end
133
+
134
+ if valid_count >= rpm then
135
+ local oldest = valid_elements[valid_count] -- 最旧的有效时间戳
136
+ local remaining = math.max(0.001, 60 - (now - oldest)) -- 保证最小等待时间
137
+ return {0, math.ceil(remaining * 1000)/1000} -- 保留3位小数
138
+ end
139
+
140
+ -- TPM限制检查(保持不变)
141
+ local tpm_key = key..'_tpm'
142
+ local current_tokens = tonumber(redis.call('GET', tpm_key) or 0)
143
+ if current_tokens + cost > tpm then
144
+ local ttl = redis.call('TTL', tpm_key)
145
+ if ttl < 0 then
146
+ redis.call('SETEX', tpm_key, 60, current_tokens)
147
+ ttl = 60
148
+ end
149
+ return {0, ttl}
150
+ end
151
+
152
+ -- 更新计数(增加时间戳覆盖写入)
153
+ redis.call('LPUSH', rpm_key, now)
154
+ redis.call('LTRIM', rpm_key, 0, rpm-1)
155
+ redis.call('EXPIRE', rpm_key, 60)
156
+
157
+ redis.call('INCRBY', tpm_key, cost)
158
+ redis.call('EXPIRE', tpm_key, 60)
159
+
160
+ return {1, 0}
161
+ """
162
+
163
+
164
+ class AsyncRedisRateLimiter(AsyncRateLimiterBackend):
165
+ """Async Redis Rate Limiter"""
166
+
167
+ def __init__(self, host: str = "localhost", port: int = 6379, db: int = 0):
168
+ import redis.asyncio as redis
169
+
170
+ self.redis = redis.Redis(host=host, port=port, db=db)
171
+ self.script = self.redis.register_script(REDIS_SCRIPT)
172
+
173
+ async def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1):
174
+ result = await self.script(keys=[key], args=[rpm, tpm, request_cost])
175
+ allowed, wait_time = result
176
+ return bool(allowed), max(1, float(wait_time))
177
+
178
+
179
+ class SyncRedisRateLimiter(SyncRateLimiterBackend):
180
+ """Sync Redis Rate Limiter"""
181
+
182
+ def __init__(self, host: str = "localhost", port: int = 6379, db: int = 0):
183
+ import redis
184
+
185
+ self.redis = redis.Redis(host=host, port=port, db=db)
186
+ self.script = self.redis.register_script(REDIS_SCRIPT)
187
+
188
+ def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1):
189
+ result = self.script(keys=[key], args=[rpm, tpm, request_cost])
190
+ allowed, wait_time = result
191
+ return bool(allowed), max(1, float(wait_time))
192
+
193
+
194
+ class AsyncDiskCacheRateLimiter(AsyncRateLimiterBackend):
195
+ """基于 diskcache 的异步限流器实现"""
196
+
197
+ def __init__(self, cache_dir: str = ".rate_limit_cache"):
198
+ """初始化 diskcache 限流器
199
+
200
+ Args:
201
+ cache_dir: 缓存目录路径
202
+ """
203
+ from diskcache import Cache
204
+
205
+ self.cache = Cache(cache_dir)
206
+ self._lock = asyncio.Lock()
207
+
208
+ def _get_rpm_key(self, key: str) -> str:
209
+ return f"{key}_rpm"
210
+
211
+ def _get_tpm_key(self, key: str) -> str:
212
+ return f"{key}_tpm"
213
+
214
+ async def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1) -> Tuple[bool, float]:
215
+ """检查是否超出限流阈值
216
+
217
+ Args:
218
+ key: 限流键
219
+ rpm: 每分钟请求数限制
220
+ tpm: 每分钟令牌数限制
221
+ request_cost: 本次请求消耗的令牌数
222
+
223
+ Returns:
224
+ Tuple[bool, float]: (是否允许请求, 需要等待的时间)
225
+ """
226
+ async with self._lock:
227
+ now = time.time()
228
+ rpm_key = self._get_rpm_key(key)
229
+ tpm_key = self._get_tpm_key(key)
230
+
231
+ # RPM 检查
232
+ window = self.cache.get(rpm_key, []) or []
233
+ window = [t for t in window if t > now - 60] # type: ignore 清理过期时间戳
234
+
235
+ if len(window) >= rpm:
236
+ return False, 60 - (now - window[0]) # type: ignore
237
+
238
+ # TPM 检查
239
+ current_tokens = self.cache.get(tpm_key, 0)
240
+ if current_tokens + request_cost > tpm: # type: ignore
241
+ # 获取最早的请求时间
242
+ oldest_time = window[0] if window else now
243
+ return False, 60 - (now - oldest_time) # type: ignore
244
+
245
+ # 更新状态
246
+ window.append(now) # type: ignore
247
+ window = window[-rpm:] # type: ignore # 只保留最近的 rpm 个时间戳
248
+ self.cache.set(rpm_key, window, expire=60)
249
+ self.cache.set(tpm_key, current_tokens + request_cost, expire=60) # type: ignore
250
+
251
+ return True, 0
252
+
253
+
254
+ class SyncDiskCacheRateLimiter(SyncRateLimiterBackend):
255
+ """基于 diskcache 的同步限流器实现"""
256
+
257
+ def __init__(self, cache_dir: str = ".rate_limit_cache"):
258
+ """初始化 diskcache 限流器
259
+
260
+ Args:
261
+ cache_dir: 缓存目录路径
262
+ """
263
+ from diskcache import Cache
264
+ import threading
265
+
266
+ self.cache = Cache(cache_dir)
267
+ self._lock = threading.Lock()
268
+
269
+ def _get_rpm_key(self, key: str) -> str:
270
+ return f"{key}_rpm"
271
+
272
+ def _get_tpm_key(self, key: str) -> str:
273
+ return f"{key}_tpm"
274
+
275
+ def check_limit(self, key: str, rpm: int, tpm: int, request_cost: int = 1) -> Tuple[bool, float]:
276
+ """检查是否超出限流阈值
277
+
278
+ Args:
279
+ key: 限流键
280
+ rpm: 每分钟请求数限制
281
+ tpm: 每分钟令牌数限制
282
+ request_cost: 本次请求消耗的令牌数
283
+
284
+ Returns:
285
+ Tuple[bool, float]: (是否允许请求, 需要等待的时间)
286
+ """
287
+ with self._lock:
288
+ now = time.time()
289
+ rpm_key = self._get_rpm_key(key)
290
+ tpm_key = self._get_tpm_key(key)
291
+
292
+ # RPM 检查
293
+ window = self.cache.get(rpm_key, []) or []
294
+ window = [t for t in window if t > now - 60] # type: ignore 清理过期时间戳
295
+
296
+ if len(window) >= rpm:
297
+ return False, 60 - (now - window[0]) # type: ignore
298
+
299
+ # TPM 检查
300
+ current_tokens = self.cache.get(tpm_key, 0)
301
+ if current_tokens + request_cost > tpm: # type: ignore
302
+ # 获取最早的请求时间
303
+ oldest_time = window[0] if window else now
304
+ return False, 60 - (now - oldest_time) # type: ignore
305
+
306
+ # 更新状态
307
+ window.append(now)
308
+ window = window[-rpm:] # 只保留最近的 rpm 个时间戳
309
+ self.cache.set(rpm_key, window, expire=60)
310
+ self.cache.set(tpm_key, current_tokens + request_cost, expire=60) # type: ignore
311
+
312
+ return True, 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vectorvein
3
- Version: 0.1.87
3
+ Version: 0.1.89
4
4
  Summary: VectorVein python SDK
5
5
  Author-Email: Anderson <andersonby@163.com>
6
6
  License: MIT
@@ -14,9 +14,14 @@ Requires-Dist: Pillow>=10.4.0
14
14
  Requires-Dist: deepseek-tokenizer>=0.1.0
15
15
  Requires-Dist: qwen-tokenizer>=0.2.0
16
16
  Requires-Dist: google-auth>=2.35.0
17
+ Requires-Dist: diskcache>=5.0.0
17
18
  Provides-Extra: server
18
19
  Requires-Dist: fastapi; extra == "server"
19
20
  Requires-Dist: uvicorn; extra == "server"
21
+ Provides-Extra: redis
22
+ Requires-Dist: redis; extra == "redis"
23
+ Provides-Extra: diskcache
24
+ Requires-Dist: diskcache; extra == "diskcache"
20
25
  Description-Content-Type: text/markdown
21
26
 
22
27
  # vectorvein
@@ -1,37 +1,38 @@
1
- vectorvein-0.1.87.dist-info/METADATA,sha256=qaCzwLhxB8o0buhZ-_94iqCMM8tITVVbZQMGt_xi_Lk,641
2
- vectorvein-0.1.87.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- vectorvein-0.1.87.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1
+ vectorvein-0.1.89.dist-info/METADATA,sha256=qO2cLUOWAPGVGMH_ufs-2-fosZiJNkEM8fIo9npYEaY,807
2
+ vectorvein-0.1.89.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ vectorvein-0.1.89.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
4
  vectorvein/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  vectorvein/chat_clients/__init__.py,sha256=omQuG4PRRPNflSAgtdU--rwsWG6vMpwMEyIGZyFVHVQ,18596
6
- vectorvein/chat_clients/anthropic_client.py,sha256=PGIKldH4FnGrqozoY_FZ6LqhDHC-jY7NF5J1F1zT2Ok,38257
6
+ vectorvein/chat_clients/anthropic_client.py,sha256=Zk6X1feIvv7Az5dgyipJXbm9TkgWgpFghSTxLiXKKA8,38405
7
7
  vectorvein/chat_clients/baichuan_client.py,sha256=CVMvpgjdrZGv0BWnTOBD-f2ufZ3wq3496wqukumsAr4,526
8
- vectorvein/chat_clients/base_client.py,sha256=Rw-BYFxy86ohZQH8KABiXP6xuNQBjWC3JkeN6WsXfLw,13638
8
+ vectorvein/chat_clients/base_client.py,sha256=QLvcGhjravPbvha6-spU-w6ugHU1LrsbdFUcs6NwMgE,18842
9
9
  vectorvein/chat_clients/deepseek_client.py,sha256=3qWu01NlJAP2N-Ff62d5-CZXZitlizE1fzb20LNetig,526
10
- vectorvein/chat_clients/gemini_client.py,sha256=qqqjQ9X8sIgJaT8xgvtG_cY-lmNGzA_f9V4tUcGRcBo,20853
10
+ vectorvein/chat_clients/gemini_client.py,sha256=ufovIZrmAE3RLEe8h5WXombf7bARAZxnkj6ydNK2FQM,475
11
11
  vectorvein/chat_clients/groq_client.py,sha256=Uow4pgdmFi93ZQSoOol2-0PhhqkW-S0XuSldvppz5U4,498
12
12
  vectorvein/chat_clients/local_client.py,sha256=55nOsxzqUf79q3Y14MKROA71zxhsT7p7FsDZ89rts2M,422
13
13
  vectorvein/chat_clients/minimax_client.py,sha256=ooJU92UCACC4TVWKJ-uo8vqQ8qF3K14ziAuSFm8Wj3M,20025
14
14
  vectorvein/chat_clients/mistral_client.py,sha256=1aKSylzBDaLYcFnaBIL4-sXSzWmXfBeON9Q0rq-ziWw,534
15
15
  vectorvein/chat_clients/moonshot_client.py,sha256=gbu-6nGxx8uM_U2WlI4Wus881rFRotzHtMSoYOcruGU,526
16
16
  vectorvein/chat_clients/openai_client.py,sha256=Nz6tV45pWcsOupxjnsRsGTicbQNJWIZyxuJoJ5DGMpg,527
17
- vectorvein/chat_clients/openai_compatible_client.py,sha256=HqdECEcm0JzENjmqclwTXFlnoiguYLfAZ7Z8tqYIsNE,28894
17
+ vectorvein/chat_clients/openai_compatible_client.py,sha256=F_kHsoCtrqJ7jLsgyIZ2mJSNQ_YnDp9SRNW4ydFDtic,28950
18
18
  vectorvein/chat_clients/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  vectorvein/chat_clients/qwen_client.py,sha256=-ryh-m9PgsO0fc4ulcCmPTy1155J8YUy15uPoJQOHA0,513
20
20
  vectorvein/chat_clients/stepfun_client.py,sha256=zsD2W5ahmR4DD9cqQTXmJr3txrGuvxbRWhFlRdwNijI,519
21
- vectorvein/chat_clients/utils.py,sha256=FurSHSSpdPjeL8ktZ0AuRKtV4pcAJJYas0PHkw5WBw4,28070
21
+ vectorvein/chat_clients/utils.py,sha256=Nf7EKtKCuWkIi1zkoU-sSjsTT271OvWJsKzxo0WKJX4,24791
22
22
  vectorvein/chat_clients/xai_client.py,sha256=eLFJJrNRJ-ni3DpshODcr3S1EJQLbhVwxyO1E54LaqM,491
23
23
  vectorvein/chat_clients/yi_client.py,sha256=RNf4CRuPJfixrwLZ3-DEc3t25QDe1mvZeb9sku2f8Bc,484
24
24
  vectorvein/chat_clients/zhipuai_client.py,sha256=Ys5DSeLCuedaDXr3PfG1EW2zKXopt-awO2IylWSwY0s,519
25
25
  vectorvein/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  vectorvein/server/token_server.py,sha256=36F9PKSNOX8ZtYBXY_l-76GQTpUSmQ2Y8EMy1H7wtdQ,1353
27
- vectorvein/settings/__init__.py,sha256=g01y74x0k2JEAqNpRGG0PDs0NTULjOAZV6HRhydPX1c,3874
27
+ vectorvein/settings/__init__.py,sha256=ecGyrE_6YfX9z6Igb1rDCu1Q-qMTcVozWF3WEl_hiKA,4871
28
28
  vectorvein/settings/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- vectorvein/types/defaults.py,sha256=rM5qCUmaVx3-hBJ3x7ClHF4mT3qU7mrcsS2FrumLdUA,26530
29
+ vectorvein/types/defaults.py,sha256=MAoxFhtvzPWBl1Zroz6hhKl7AshyHcO90hcE3pXzePQ,27384
30
30
  vectorvein/types/enums.py,sha256=7KTJSVtQueImmbr1fSwv3rQVtc0RyMWXJmoE2tDOaso,1667
31
31
  vectorvein/types/exception.py,sha256=gnW4GnJ76jND6UGnodk9xmqkcbeS7Cz2rvncA2HpD5E,69
32
- vectorvein/types/llm_parameters.py,sha256=CLhDSp9KI_zzjIXUvjiTuGxfYXpubTNBCVcJ6RgH2iY,5879
32
+ vectorvein/types/llm_parameters.py,sha256=jXHGR9aORkBrUaG4oQef3zorFzvVX2oTn2lMu57IOQs,5989
33
33
  vectorvein/types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  vectorvein/utilities/media_processing.py,sha256=CTRq-lGlFkFgP_FSRhNwF_qUgmOrXPf2_1Ok9HY42_g,5887
35
+ vectorvein/utilities/rate_limiter.py,sha256=dwolIUVw2wP83Odqpx0AAaE77de1GzxkYDGH4tM_u_4,10300
35
36
  vectorvein/utilities/retry.py,sha256=6KFS9R2HdhqM3_9jkjD4F36ZSpEx2YNFGOVlpOsUetM,2208
36
37
  vectorvein/workflow/graph/edge.py,sha256=xLZEJmBjAfVB53cd7CuRcKhgE6QqXv9nz32wJn8cmyk,1064
37
38
  vectorvein/workflow/graph/node.py,sha256=A3M_GghrSju1D3xc_HtPdGyr-7XSkplGPKJveOUiIF4,3256
@@ -54,4 +55,4 @@ vectorvein/workflow/nodes/vector_db.py,sha256=t6I17q6iR3yQreiDHpRrksMdWDPIvgqJs0
54
55
  vectorvein/workflow/nodes/video_generation.py,sha256=qmdg-t_idpxq1veukd-jv_ChICMOoInKxprV9Z4Vi2w,4118
55
56
  vectorvein/workflow/nodes/web_crawlers.py,sha256=LsqomfXfqrXfHJDO1cl0Ox48f4St7X_SL12DSbAMSOw,5415
56
57
  vectorvein/workflow/utils/json_to_code.py,sha256=F7dhDy8kGc8ndOeihGLRLGFGlquoxVlb02ENtxnQ0C8,5914
57
- vectorvein-0.1.87.dist-info/RECORD,,
58
+ vectorvein-0.1.89.dist-info/RECORD,,