vectorvein 0.1.95__py3-none-any.whl → 0.2.1__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,36 @@
1
+ """向量脉络 API 包"""
2
+
3
+ from .client import VectorVeinClient, AsyncVectorVeinClient
4
+ from .models import (
5
+ VApp,
6
+ AccessKey,
7
+ WorkflowInputField,
8
+ WorkflowOutput,
9
+ WorkflowRunResult,
10
+ AccessKeyListResponse,
11
+ )
12
+ from .exceptions import (
13
+ VectorVeinAPIError,
14
+ APIKeyError,
15
+ WorkflowError,
16
+ AccessKeyError,
17
+ RequestError,
18
+ TimeoutError,
19
+ )
20
+
21
+ __all__ = [
22
+ "VectorVeinClient",
23
+ "AsyncVectorVeinClient",
24
+ "VApp",
25
+ "AccessKey",
26
+ "WorkflowInputField",
27
+ "WorkflowOutput",
28
+ "WorkflowRunResult",
29
+ "AccessKeyListResponse",
30
+ "VectorVeinAPIError",
31
+ "APIKeyError",
32
+ "WorkflowError",
33
+ "AccessKeyError",
34
+ "RequestError",
35
+ "TimeoutError",
36
+ ]
@@ -0,0 +1,827 @@
1
+ """向量脉络 API 客户端"""
2
+
3
+ import time
4
+ import base64
5
+ import asyncio
6
+ from urllib.parse import quote
7
+ from typing import List, Optional, Dict, Any, Union, Literal, overload
8
+
9
+ import httpx
10
+ from Crypto.Cipher import AES
11
+ from Crypto.Util.Padding import pad
12
+
13
+ from .exceptions import (
14
+ VectorVeinAPIError,
15
+ APIKeyError,
16
+ WorkflowError,
17
+ AccessKeyError,
18
+ RequestError,
19
+ TimeoutError,
20
+ )
21
+ from .models import (
22
+ AccessKey,
23
+ WorkflowInputField,
24
+ WorkflowOutput,
25
+ WorkflowRunResult,
26
+ AccessKeyListResponse,
27
+ )
28
+
29
+
30
+ class VectorVeinClient:
31
+ """向量脉络 API 客户端类"""
32
+
33
+ API_VERSION = "20240508"
34
+ BASE_URL = "https://vectorvein.com/api/v1/open-api"
35
+
36
+ def __init__(self, api_key: str, base_url: Optional[str] = None):
37
+ """初始化客户端
38
+
39
+ Args:
40
+ api_key: API密钥
41
+ base_url: API基础URL,默认为https://vectorvein.com/api/v1/open-api
42
+
43
+ Raises:
44
+ APIKeyError: API密钥为空或格式不正确
45
+ """
46
+ if not api_key or not isinstance(api_key, str):
47
+ raise APIKeyError("API密钥不能为空且必须是字符串类型")
48
+
49
+ self.api_key = api_key
50
+ self.base_url = base_url or self.BASE_URL
51
+ self._client = httpx.Client(
52
+ headers={
53
+ "VECTORVEIN-API-KEY": api_key,
54
+ "VECTORVEIN-API-VERSION": self.API_VERSION,
55
+ }
56
+ )
57
+
58
+ def __enter__(self):
59
+ return self
60
+
61
+ def __exit__(self, exc_type, exc_val, exc_tb):
62
+ self._client.close()
63
+
64
+ def _request(
65
+ self,
66
+ method: str,
67
+ endpoint: str,
68
+ params: Optional[Dict[str, Any]] = None,
69
+ json: Optional[Dict[str, Any]] = None,
70
+ **kwargs,
71
+ ) -> Dict[str, Any]:
72
+ """发送HTTP请求
73
+
74
+ Args:
75
+ method: HTTP方法
76
+ endpoint: API端点
77
+ params: URL参数
78
+ json: JSON请求体
79
+ **kwargs: 其他请求参数
80
+
81
+ Returns:
82
+ Dict[str, Any]: API响应
83
+
84
+ Raises:
85
+ RequestError: 请求错误
86
+ VectorVeinAPIError: API错误
87
+ APIKeyError: API密钥无效或已过期
88
+ """
89
+ url = f"{self.base_url}/{endpoint}"
90
+ try:
91
+ response = self._client.request(method=method, url=url, params=params, json=json, **kwargs)
92
+ response.raise_for_status()
93
+ result = response.json()
94
+
95
+ if result["status"] in [401, 403]:
96
+ raise APIKeyError("API密钥无效或已过期")
97
+ if result["status"] != 200 and result["status"] != 202:
98
+ raise VectorVeinAPIError(message=result.get("msg", "Unknown error"), status_code=result["status"])
99
+ return result
100
+ except httpx.HTTPError as e:
101
+ raise RequestError(f"Request failed: {str(e)}")
102
+
103
+ @overload
104
+ def run_workflow(
105
+ self,
106
+ wid: str,
107
+ input_fields: List[WorkflowInputField],
108
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
109
+ wait_for_completion: Literal[False] = False,
110
+ timeout: int = 30,
111
+ ) -> str: ...
112
+
113
+ @overload
114
+ def run_workflow(
115
+ self,
116
+ wid: str,
117
+ input_fields: List[WorkflowInputField],
118
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
119
+ wait_for_completion: Literal[True] = True,
120
+ timeout: int = 30,
121
+ ) -> WorkflowRunResult: ...
122
+
123
+ def run_workflow(
124
+ self,
125
+ wid: str,
126
+ input_fields: List[WorkflowInputField],
127
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
128
+ wait_for_completion: bool = False,
129
+ timeout: int = 30,
130
+ ) -> Union[str, WorkflowRunResult]:
131
+ """运行工作流
132
+
133
+ Args:
134
+ wid: 工作流ID
135
+ input_fields: 输入字段列表
136
+ output_scope: 输出范围,可选值:'all' 或 'output_fields_only'
137
+ wait_for_completion: 是否等待完成
138
+ timeout: 超时时间(秒)
139
+
140
+ Returns:
141
+ Union[str, WorkflowRunResult]: 工作流运行ID或运行结果
142
+
143
+ Raises:
144
+ WorkflowError: 工作流运行错误
145
+ TimeoutError: 超时错误
146
+ """
147
+ payload = {
148
+ "wid": wid,
149
+ "output_scope": output_scope,
150
+ "wait_for_completion": wait_for_completion,
151
+ "input_fields": [
152
+ {"node_id": field.node_id, "field_name": field.field_name, "value": field.value}
153
+ for field in input_fields
154
+ ],
155
+ }
156
+
157
+ result = self._request("POST", "workflow/run", json=payload)
158
+
159
+ if not wait_for_completion:
160
+ return result["data"]["rid"]
161
+
162
+ rid = result.get("rid") or (isinstance(result["data"], dict) and result["data"].get("rid")) or ""
163
+ start_time = time.time()
164
+
165
+ while True:
166
+ if time.time() - start_time > timeout:
167
+ raise TimeoutError(f"Workflow execution timed out after {timeout} seconds")
168
+
169
+ status = self.check_workflow_status(rid)
170
+ if status["status"] == 200:
171
+ return WorkflowRunResult(
172
+ rid=rid,
173
+ status=status["status"],
174
+ msg=status["msg"],
175
+ data=[WorkflowOutput(**output) for output in status["data"]],
176
+ )
177
+ elif status["status"] == 500:
178
+ raise WorkflowError(f"Workflow execution failed: {status['msg']}")
179
+
180
+ time.sleep(5)
181
+
182
+ def check_workflow_status(self, rid: str) -> Dict:
183
+ """检查工作流运行状态
184
+
185
+ Args:
186
+ rid: 工作流运行记录ID
187
+
188
+ Returns:
189
+ Dict: 工作流状态信息
190
+ """
191
+ payload = {"rid": rid}
192
+ return self._request("POST", "workflow/check-status", json=payload)
193
+
194
+ def get_access_keys(
195
+ self, access_keys: Optional[List[str]] = None, get_type: Literal["selected", "all"] = "selected"
196
+ ) -> List[AccessKey]:
197
+ """获取访问密钥信息
198
+
199
+ Args:
200
+ access_keys: 访问密钥列表
201
+ get_type: 获取类型,可选值:'selected' 或 'all'
202
+
203
+ Returns:
204
+ List[AccessKey]: 访问密钥信息列表
205
+
206
+ Raises:
207
+ AccessKeyError: 访问密钥不存在或已失效
208
+ """
209
+ params = {"get_type": get_type}
210
+ if access_keys:
211
+ params["access_keys"] = ",".join(access_keys)
212
+
213
+ try:
214
+ result = self._request("GET", "vapp/access-key/get", params=params)
215
+ return [AccessKey(**key) for key in result["data"]]
216
+ except VectorVeinAPIError as e:
217
+ if e.status_code == 404:
218
+ raise AccessKeyError("访问密钥不存在")
219
+ elif e.status_code == 403:
220
+ raise AccessKeyError("访问密钥已失效")
221
+ raise
222
+
223
+ def create_access_keys(
224
+ self,
225
+ access_key_type: Literal["O", "M", "L"],
226
+ app_id: Optional[str] = None,
227
+ app_ids: Optional[List[str]] = None,
228
+ count: int = 1,
229
+ expire_time: Optional[str] = None,
230
+ max_credits: Optional[int] = None,
231
+ max_use_count: Optional[int] = None,
232
+ description: Optional[str] = None,
233
+ ) -> List[AccessKey]:
234
+ """创建访问密钥
235
+
236
+ Args:
237
+ access_key_type: 密钥类型,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
238
+ app_id: 单个应用ID
239
+ app_ids: 多个应用ID列表
240
+ count: 创建数量
241
+ expire_time: 过期时间
242
+ max_credits: 最大积分限制
243
+ max_use_count: 最大使用次数
244
+ description: 描述信息
245
+
246
+ Returns:
247
+ List[AccessKey]: 创建的访问密钥列表
248
+
249
+ Raises:
250
+ AccessKeyError: 创建访问密钥失败,如类型无效、应用不存在等
251
+ """
252
+ if access_key_type not in ["O", "M", "L"]:
253
+ raise AccessKeyError("无效的访问密钥类型,必须是 'O'(一次性)、'M'(多次) 或 'L'(长期)")
254
+
255
+ if app_id and app_ids:
256
+ raise AccessKeyError("不能同时指定 app_id 和 app_ids")
257
+
258
+ payload = {"access_key_type": access_key_type, "count": count}
259
+
260
+ if app_id:
261
+ payload["app_id"] = app_id
262
+ if app_ids:
263
+ payload["app_ids"] = app_ids
264
+ if expire_time:
265
+ payload["expire_time"] = expire_time
266
+ if max_credits is not None:
267
+ payload["max_credits"] = max_credits
268
+ if max_use_count is not None:
269
+ payload["max_use_count"] = max_use_count
270
+ if description:
271
+ payload["description"] = description
272
+
273
+ try:
274
+ result = self._request("POST", "vapp/access-key/create", json=payload)
275
+ return [AccessKey(**key) for key in result["data"]]
276
+ except VectorVeinAPIError as e:
277
+ if e.status_code == 404:
278
+ raise AccessKeyError("指定的应用不存在")
279
+ elif e.status_code == 403:
280
+ raise AccessKeyError("没有权限创建访问密钥")
281
+ raise
282
+
283
+ def list_access_keys(
284
+ self,
285
+ page: int = 1,
286
+ page_size: int = 10,
287
+ sort_field: str = "create_time",
288
+ sort_order: str = "descend",
289
+ app_id: Optional[str] = None,
290
+ status: Optional[List[str]] = None,
291
+ access_key_type: Optional[Literal["O", "M", "L"]] = None,
292
+ ) -> AccessKeyListResponse:
293
+ """列出访问密钥
294
+
295
+ Args:
296
+ page: 页码
297
+ page_size: 每页数量
298
+ sort_field: 排序字段
299
+ sort_order: 排序顺序
300
+ app_id: 应用ID
301
+ status: 状态列表
302
+ access_key_type: 密钥类型列表,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
303
+
304
+ Returns:
305
+ AccessKeyListResponse: 访问密钥列表响应
306
+ """
307
+ payload = {"page": page, "page_size": page_size, "sort_field": sort_field, "sort_order": sort_order}
308
+
309
+ if app_id:
310
+ payload["app_id"] = app_id
311
+ if status:
312
+ payload["status"] = status
313
+ if access_key_type:
314
+ payload["access_key_type"] = access_key_type
315
+
316
+ result = self._request("POST", "vapp/access-key/list", json=payload)
317
+ return AccessKeyListResponse(**result["data"])
318
+
319
+ def delete_access_keys(self, app_id: str, access_keys: List[str]) -> None:
320
+ """删除访问密钥
321
+
322
+ Args:
323
+ app_id: 应用ID
324
+ access_keys: 要删除的访问密钥列表
325
+ """
326
+ payload = {"app_id": app_id, "access_keys": access_keys}
327
+ self._request("POST", "vapp/access-key/delete", json=payload)
328
+
329
+ def update_access_keys(
330
+ self,
331
+ access_key: Optional[str] = None,
332
+ access_keys: Optional[List[str]] = None,
333
+ app_id: Optional[str] = None,
334
+ app_ids: Optional[List[str]] = None,
335
+ expire_time: Optional[str] = None,
336
+ max_use_count: Optional[int] = None,
337
+ max_credits: Optional[int] = None,
338
+ description: Optional[str] = None,
339
+ access_key_type: Optional[Literal["O", "M", "L"]] = None,
340
+ ) -> None:
341
+ """更新访问密钥
342
+
343
+ Args:
344
+ access_key: 单个访问密钥
345
+ access_keys: 多个访问密钥列表
346
+ app_id: 单个应用ID
347
+ app_ids: 多个应用ID列表
348
+ expire_time: 过期时间
349
+ max_use_count: 最大使用次数
350
+ max_credits: 最大积分限制
351
+ description: 描述信息
352
+ access_key_type: 密钥类型,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
353
+ """
354
+ payload = {}
355
+ if access_key:
356
+ payload["access_key"] = access_key
357
+ if access_keys:
358
+ payload["access_keys"] = access_keys
359
+ if app_id:
360
+ payload["app_id"] = app_id
361
+ if app_ids:
362
+ payload["app_ids"] = app_ids
363
+ if expire_time:
364
+ payload["expire_time"] = expire_time
365
+ if max_use_count is not None:
366
+ payload["max_use_count"] = max_use_count
367
+ if max_credits is not None:
368
+ payload["max_credits"] = max_credits
369
+ if description:
370
+ payload["description"] = description
371
+ if access_key_type:
372
+ payload["access_key_type"] = access_key_type
373
+
374
+ self._request("POST", "vapp/access-key/update", json=payload)
375
+
376
+ def add_apps_to_access_keys(self, access_keys: List[str], app_ids: List[str]) -> None:
377
+ """向访问密钥添加应用
378
+
379
+ Args:
380
+ access_keys: 访问密钥列表
381
+ app_ids: 要添加的应用ID列表
382
+ """
383
+ payload = {"access_keys": access_keys, "app_ids": app_ids}
384
+ self._request("POST", "vapp/access-key/add-apps", json=payload)
385
+
386
+ def remove_apps_from_access_keys(self, access_keys: List[str], app_ids: List[str]) -> None:
387
+ """从访问密钥移除应用
388
+
389
+ Args:
390
+ access_keys: 访问密钥列表
391
+ app_ids: 要移除的应用ID列表
392
+ """
393
+ payload = {"access_keys": access_keys, "app_ids": app_ids}
394
+ self._request("POST", "vapp/access-key/remove-apps", json=payload)
395
+
396
+ def generate_vapp_url(
397
+ self,
398
+ app_id: str,
399
+ access_key: str,
400
+ key_id: str,
401
+ timeout: int = 15 * 60,
402
+ base_url: str = "https://vectorvein.com",
403
+ ) -> str:
404
+ """生成VApp访问链接
405
+
406
+ Args:
407
+ app_id: VApp ID
408
+ access_key: 访问密钥
409
+ key_id: 密钥ID
410
+ timeout: 超时时间(秒)
411
+ base_url: 基础URL
412
+
413
+ Returns:
414
+ str: VApp访问链接
415
+ """
416
+ timestamp = int(time.time())
417
+ message = f"{app_id}:{access_key}:{timestamp}:{timeout}"
418
+ encryption_key = self.api_key.encode()
419
+
420
+ cipher = AES.new(encryption_key, AES.MODE_CBC)
421
+ padded_data = pad(message.encode(), AES.block_size)
422
+ encrypted_data = cipher.encrypt(padded_data)
423
+ final_data = bytes(cipher.iv) + encrypted_data
424
+ token = base64.b64encode(final_data).decode("utf-8")
425
+ quoted_token = quote(token)
426
+
427
+ return f"{base_url}/public/v-app/{app_id}?token={quoted_token}&key_id={key_id}"
428
+
429
+
430
+ class AsyncVectorVeinClient:
431
+ """向量脉络 API 异步客户端类"""
432
+
433
+ API_VERSION = "20240508"
434
+ BASE_URL = "https://vectorvein.com/api/v1/open-api"
435
+
436
+ def __init__(self, api_key: str, base_url: Optional[str] = None):
437
+ """初始化异步客户端
438
+
439
+ Args:
440
+ api_key: API密钥
441
+ base_url: API基础URL,默认为https://vectorvein.com/api/v1/open-api
442
+
443
+ Raises:
444
+ APIKeyError: API密钥为空或格式不正确
445
+ """
446
+ if not api_key or not isinstance(api_key, str):
447
+ raise APIKeyError("API密钥不能为空且必须是字符串类型")
448
+
449
+ self.api_key = api_key
450
+ self.base_url = base_url or self.BASE_URL
451
+ self._client = httpx.AsyncClient(
452
+ headers={
453
+ "VECTORVEIN-API-KEY": api_key,
454
+ "VECTORVEIN-API-VERSION": self.API_VERSION,
455
+ }
456
+ )
457
+
458
+ async def __aenter__(self):
459
+ return self
460
+
461
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
462
+ await self._client.aclose()
463
+
464
+ async def _request(
465
+ self,
466
+ method: str,
467
+ endpoint: str,
468
+ params: Optional[Dict[str, Any]] = None,
469
+ json: Optional[Dict[str, Any]] = None,
470
+ **kwargs,
471
+ ) -> Dict[str, Any]:
472
+ """发送异步HTTP请求
473
+
474
+ Args:
475
+ method: HTTP方法
476
+ endpoint: API端点
477
+ params: URL参数
478
+ json: JSON请求体
479
+ **kwargs: 其他请求参数
480
+
481
+ Returns:
482
+ Dict[str, Any]: API响应
483
+
484
+ Raises:
485
+ RequestError: 请求错误
486
+ VectorVeinAPIError: API错误
487
+ APIKeyError: API密钥无效或已过期
488
+ """
489
+ url = f"{self.base_url}/{endpoint}"
490
+ try:
491
+ response = await self._client.request(method=method, url=url, params=params, json=json, **kwargs)
492
+ response.raise_for_status()
493
+ result = response.json()
494
+
495
+ if result["status"] in [401, 403]:
496
+ raise APIKeyError("API密钥无效或已过期")
497
+ if result["status"] != 200 and result["status"] != 202:
498
+ raise VectorVeinAPIError(message=result.get("msg", "Unknown error"), status_code=result["status"])
499
+ return result
500
+ except httpx.HTTPError as e:
501
+ raise RequestError(f"Request failed: {str(e)}")
502
+
503
+ @overload
504
+ async def run_workflow(
505
+ self,
506
+ wid: str,
507
+ input_fields: List[WorkflowInputField],
508
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
509
+ wait_for_completion: Literal[False] = False,
510
+ timeout: int = 30,
511
+ ) -> str: ...
512
+
513
+ @overload
514
+ async def run_workflow(
515
+ self,
516
+ wid: str,
517
+ input_fields: List[WorkflowInputField],
518
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
519
+ wait_for_completion: Literal[True] = True,
520
+ timeout: int = 30,
521
+ ) -> WorkflowRunResult: ...
522
+
523
+ async def run_workflow(
524
+ self,
525
+ wid: str,
526
+ input_fields: List[WorkflowInputField],
527
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
528
+ wait_for_completion: bool = False,
529
+ timeout: int = 30,
530
+ ) -> Union[str, WorkflowRunResult]:
531
+ """异步运行工作流
532
+
533
+ Args:
534
+ wid: 工作流ID
535
+ input_fields: 输入字段列表
536
+ output_scope: 输出范围,可选值:'all' 或 'output_fields_only'
537
+ wait_for_completion: 是否等待完成
538
+ timeout: 超时时间(秒)
539
+
540
+ Returns:
541
+ Union[str, WorkflowRunResult]: 工作流运行ID或运行结果
542
+
543
+ Raises:
544
+ WorkflowError: 工作流运行错误
545
+ TimeoutError: 超时错误
546
+ """
547
+ payload = {
548
+ "wid": wid,
549
+ "output_scope": output_scope,
550
+ "wait_for_completion": wait_for_completion,
551
+ "input_fields": [
552
+ {"node_id": field.node_id, "field_name": field.field_name, "value": field.value}
553
+ for field in input_fields
554
+ ],
555
+ }
556
+
557
+ result = await self._request("POST", "workflow/run", json=payload)
558
+
559
+ if not wait_for_completion:
560
+ return result["data"]["rid"]
561
+
562
+ rid = result.get("rid") or (isinstance(result["data"], dict) and result["data"].get("rid")) or ""
563
+ start_time = time.time()
564
+
565
+ while True:
566
+ if time.time() - start_time > timeout:
567
+ raise TimeoutError(f"Workflow execution timed out after {timeout} seconds")
568
+
569
+ status = await self.check_workflow_status(rid)
570
+ if status["status"] == 200:
571
+ return WorkflowRunResult(
572
+ rid=rid,
573
+ status=status["status"],
574
+ msg=status["msg"],
575
+ data=[WorkflowOutput(**output) for output in status["data"]],
576
+ )
577
+ elif status["status"] == 500:
578
+ raise WorkflowError(f"Workflow execution failed: {status['msg']}")
579
+
580
+ await asyncio.sleep(5)
581
+
582
+ async def check_workflow_status(self, rid: str) -> Dict:
583
+ """异步检查工作流运行状态
584
+
585
+ Args:
586
+ rid: 工作流运行记录ID
587
+
588
+ Returns:
589
+ Dict: 工作流状态信息
590
+ """
591
+ payload = {"rid": rid}
592
+ return await self._request("POST", "workflow/check-status", json=payload)
593
+
594
+ async def get_access_keys(
595
+ self, access_keys: Optional[List[str]] = None, get_type: Literal["selected", "all"] = "selected"
596
+ ) -> List[AccessKey]:
597
+ """异步获取访问密钥信息
598
+
599
+ Args:
600
+ access_keys: 访问密钥列表
601
+ get_type: 获取类型,可选值:'selected' 或 'all'
602
+
603
+ Returns:
604
+ List[AccessKey]: 访问密钥信息列表
605
+
606
+ Raises:
607
+ AccessKeyError: 访问密钥不存在或已失效
608
+ """
609
+ params = {"get_type": get_type}
610
+ if access_keys:
611
+ params["access_keys"] = ",".join(access_keys)
612
+
613
+ try:
614
+ result = await self._request("GET", "vapp/access-key/get", params=params)
615
+ return [AccessKey(**key) for key in result["data"]]
616
+ except VectorVeinAPIError as e:
617
+ if e.status_code == 404:
618
+ raise AccessKeyError("访问密钥不存在")
619
+ elif e.status_code == 403:
620
+ raise AccessKeyError("访问密钥已失效")
621
+ raise
622
+
623
+ async def create_access_keys(
624
+ self,
625
+ access_key_type: Literal["O", "M", "L"],
626
+ app_id: Optional[str] = None,
627
+ app_ids: Optional[List[str]] = None,
628
+ count: int = 1,
629
+ expire_time: Optional[str] = None,
630
+ max_credits: Optional[int] = None,
631
+ max_use_count: Optional[int] = None,
632
+ description: Optional[str] = None,
633
+ ) -> List[AccessKey]:
634
+ """异步创建访问密钥
635
+
636
+ Args:
637
+ access_key_type: 密钥类型,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
638
+ app_id: 单个应用ID
639
+ app_ids: 多个应用ID列表
640
+ count: 创建数量
641
+ expire_time: 过期时间
642
+ max_credits: 最大积分限制
643
+ max_use_count: 最大使用次数
644
+ description: 描述信息
645
+
646
+ Returns:
647
+ List[AccessKey]: 创建的访问密钥列表
648
+
649
+ Raises:
650
+ AccessKeyError: 创建访问密钥失败,如类型无效、应用不存在等
651
+ """
652
+ if access_key_type not in ["O", "M", "L"]:
653
+ raise AccessKeyError("无效的访问密钥类型,必须是 'O'(一次性)、'M'(多次) 或 'L'(长期)")
654
+
655
+ if app_id and app_ids:
656
+ raise AccessKeyError("不能同时指定 app_id 和 app_ids")
657
+
658
+ payload = {"access_key_type": access_key_type, "count": count}
659
+
660
+ if app_id:
661
+ payload["app_id"] = app_id
662
+ if app_ids:
663
+ payload["app_ids"] = app_ids
664
+ if expire_time:
665
+ payload["expire_time"] = expire_time
666
+ if max_credits is not None:
667
+ payload["max_credits"] = max_credits
668
+ if max_use_count is not None:
669
+ payload["max_use_count"] = max_use_count
670
+ if description:
671
+ payload["description"] = description
672
+
673
+ try:
674
+ result = await self._request("POST", "vapp/access-key/create", json=payload)
675
+ return [AccessKey(**key) for key in result["data"]]
676
+ except VectorVeinAPIError as e:
677
+ if e.status_code == 404:
678
+ raise AccessKeyError("指定的应用不存在")
679
+ elif e.status_code == 403:
680
+ raise AccessKeyError("没有权限创建访问密钥")
681
+ raise
682
+
683
+ async def list_access_keys(
684
+ self,
685
+ page: int = 1,
686
+ page_size: int = 10,
687
+ sort_field: str = "create_time",
688
+ sort_order: str = "descend",
689
+ app_id: Optional[str] = None,
690
+ status: Optional[List[str]] = None,
691
+ access_key_type: Optional[Literal["O", "M", "L"]] = None,
692
+ ) -> AccessKeyListResponse:
693
+ """异步列出访问密钥
694
+
695
+ Args:
696
+ page: 页码
697
+ page_size: 每页数量
698
+ sort_field: 排序字段
699
+ sort_order: 排序顺序
700
+ app_id: 应用ID
701
+ status: 状态列表
702
+ access_key_type: 密钥类型列表,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
703
+
704
+ Returns:
705
+ AccessKeyListResponse: 访问密钥列表响应
706
+ """
707
+ payload = {"page": page, "page_size": page_size, "sort_field": sort_field, "sort_order": sort_order}
708
+
709
+ if app_id:
710
+ payload["app_id"] = app_id
711
+ if status:
712
+ payload["status"] = status
713
+ if access_key_type:
714
+ payload["access_key_type"] = access_key_type
715
+
716
+ result = await self._request("POST", "vapp/access-key/list", json=payload)
717
+ return AccessKeyListResponse(**result["data"])
718
+
719
+ async def delete_access_keys(self, app_id: str, access_keys: List[str]) -> None:
720
+ """异步删除访问密钥
721
+
722
+ Args:
723
+ app_id: 应用ID
724
+ access_keys: 要删除的访问密钥列表
725
+ """
726
+ payload = {"app_id": app_id, "access_keys": access_keys}
727
+ await self._request("POST", "vapp/access-key/delete", json=payload)
728
+
729
+ async def update_access_keys(
730
+ self,
731
+ access_key: Optional[str] = None,
732
+ access_keys: Optional[List[str]] = None,
733
+ app_id: Optional[str] = None,
734
+ app_ids: Optional[List[str]] = None,
735
+ expire_time: Optional[str] = None,
736
+ max_use_count: Optional[int] = None,
737
+ max_credits: Optional[int] = None,
738
+ description: Optional[str] = None,
739
+ access_key_type: Optional[Literal["O", "M", "L"]] = None,
740
+ ) -> None:
741
+ """异步更新访问密钥
742
+
743
+ Args:
744
+ access_key: 单个访问密钥
745
+ access_keys: 多个访问密钥列表
746
+ app_id: 单个应用ID
747
+ app_ids: 多个应用ID列表
748
+ expire_time: 过期时间
749
+ max_use_count: 最大使用次数
750
+ max_credits: 最大积分限制
751
+ description: 描述信息
752
+ access_key_type: 密钥类型,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
753
+ """
754
+ payload = {}
755
+ if access_key:
756
+ payload["access_key"] = access_key
757
+ if access_keys:
758
+ payload["access_keys"] = access_keys
759
+ if app_id:
760
+ payload["app_id"] = app_id
761
+ if app_ids:
762
+ payload["app_ids"] = app_ids
763
+ if expire_time:
764
+ payload["expire_time"] = expire_time
765
+ if max_use_count is not None:
766
+ payload["max_use_count"] = max_use_count
767
+ if max_credits is not None:
768
+ payload["max_credits"] = max_credits
769
+ if description:
770
+ payload["description"] = description
771
+ if access_key_type:
772
+ payload["access_key_type"] = access_key_type
773
+
774
+ await self._request("POST", "vapp/access-key/update", json=payload)
775
+
776
+ async def add_apps_to_access_keys(self, access_keys: List[str], app_ids: List[str]) -> None:
777
+ """异步向访问密钥添加应用
778
+
779
+ Args:
780
+ access_keys: 访问密钥列表
781
+ app_ids: 要添加的应用ID列表
782
+ """
783
+ payload = {"access_keys": access_keys, "app_ids": app_ids}
784
+ await self._request("POST", "vapp/access-key/add-apps", json=payload)
785
+
786
+ async def remove_apps_from_access_keys(self, access_keys: List[str], app_ids: List[str]) -> None:
787
+ """异步从访问密钥移除应用
788
+
789
+ Args:
790
+ access_keys: 访问密钥列表
791
+ app_ids: 要移除的应用ID列表
792
+ """
793
+ payload = {"access_keys": access_keys, "app_ids": app_ids}
794
+ await self._request("POST", "vapp/access-key/remove-apps", json=payload)
795
+
796
+ async def generate_vapp_url(
797
+ self,
798
+ app_id: str,
799
+ access_key: str,
800
+ key_id: str,
801
+ timeout: int = 15 * 60,
802
+ base_url: str = "https://vectorvein.com",
803
+ ) -> str:
804
+ """异步生成VApp访问链接
805
+
806
+ Args:
807
+ app_id: VApp ID
808
+ access_key: 访问密钥
809
+ key_id: 密钥ID
810
+ timeout: 超时时间(秒)
811
+ base_url: 基础URL
812
+
813
+ Returns:
814
+ str: VApp访问链接
815
+ """
816
+ timestamp = int(time.time())
817
+ message = f"{app_id}:{access_key}:{timestamp}:{timeout}"
818
+ encryption_key = self.api_key.encode()
819
+
820
+ cipher = AES.new(encryption_key, AES.MODE_CBC)
821
+ padded_data = pad(message.encode(), AES.block_size)
822
+ encrypted_data = cipher.encrypt(padded_data)
823
+ final_data = bytes(cipher.iv) + encrypted_data
824
+ token = base64.b64encode(final_data).decode("utf-8")
825
+ quoted_token = quote(token)
826
+
827
+ return f"{base_url}/public/v-app/{app_id}?token={quoted_token}&key_id={key_id}"
@@ -0,0 +1,41 @@
1
+ """向量脉络 API 异常类定义"""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class VectorVeinAPIError(Exception):
7
+ """向量脉络 API 基础异常类"""
8
+
9
+ def __init__(self, message: str, status_code: Optional[int] = None):
10
+ super().__init__(message)
11
+ self.status_code = status_code
12
+
13
+
14
+ class APIKeyError(VectorVeinAPIError):
15
+ """API密钥相关错误"""
16
+
17
+ pass
18
+
19
+
20
+ class WorkflowError(VectorVeinAPIError):
21
+ """工作流相关错误"""
22
+
23
+ pass
24
+
25
+
26
+ class AccessKeyError(VectorVeinAPIError):
27
+ """访问密钥相关错误"""
28
+
29
+ pass
30
+
31
+
32
+ class RequestError(VectorVeinAPIError):
33
+ """请求相关错误"""
34
+
35
+ pass
36
+
37
+
38
+ class TimeoutError(VectorVeinAPIError):
39
+ """超时错误"""
40
+
41
+ pass
@@ -0,0 +1,74 @@
1
+ """向量脉络 API 数据模型定义"""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import List, Dict, Optional, Any
5
+
6
+
7
+ @dataclass
8
+ class VApp:
9
+ """VApp 信息"""
10
+
11
+ app_id: str
12
+ title: str
13
+ description: str
14
+ info: Dict[str, Any]
15
+ images: List[str]
16
+
17
+
18
+ @dataclass
19
+ class AccessKey:
20
+ """访问密钥信息"""
21
+
22
+ access_key: str
23
+ access_key_type: str # O: 一次性, M: 多次, L: 长期
24
+ use_count: int
25
+ max_use_count: Optional[int]
26
+ max_credits: Optional[int]
27
+ used_credits: int
28
+ v_app: Optional[VApp]
29
+ v_apps: List[VApp]
30
+ records: List[Any]
31
+ status: str # AC: 有效, IN: 无效, EX: 已过期, US: 已使用
32
+ access_scope: str # S: 单应用, M: 多应用
33
+ description: str
34
+ create_time: str
35
+ expire_time: str
36
+ last_use_time: Optional[str]
37
+
38
+
39
+ @dataclass
40
+ class WorkflowInputField:
41
+ """工作流输入字段"""
42
+
43
+ node_id: str
44
+ field_name: str
45
+ value: Any
46
+
47
+
48
+ @dataclass
49
+ class WorkflowOutput:
50
+ """工作流输出结果"""
51
+
52
+ type: str
53
+ title: str
54
+ value: Any
55
+
56
+
57
+ @dataclass
58
+ class WorkflowRunResult:
59
+ """工作流运行结果"""
60
+
61
+ rid: str
62
+ status: int
63
+ msg: str
64
+ data: List[WorkflowOutput]
65
+
66
+
67
+ @dataclass
68
+ class AccessKeyListResponse:
69
+ """访问密钥列表响应"""
70
+
71
+ access_keys: List[AccessKey]
72
+ total: int
73
+ page_size: int
74
+ page: int
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.1
2
+ Name: vectorvein
3
+ Version: 0.2.1
4
+ Summary: VectorVein python SDK
5
+ Author-Email: Anderson <andersonby@163.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: openai>=1.37.1
9
+ Requires-Dist: tiktoken>=0.7.0
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: anthropic[bedrock,vertex]>=0.31.2
12
+ Requires-Dist: pydantic>=2.8.2
13
+ Requires-Dist: Pillow>=10.4.0
14
+ Requires-Dist: deepseek-tokenizer>=0.1.0
15
+ Requires-Dist: qwen-tokenizer>=0.2.0
16
+ Requires-Dist: google-auth>=2.35.0
17
+ Requires-Dist: pycryptodome>=3.21.0
18
+ Provides-Extra: server
19
+ Requires-Dist: fastapi; extra == "server"
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"
25
+ Description-Content-Type: text/markdown
26
+
27
+ # 向量脉络 API 包
28
+
29
+ 这是一个用于调用向量脉络官方API的Python包装器。它提供了简单易用的接口来访问向量脉络的工作流和VApp功能。
30
+
31
+ ## 安装
32
+
33
+ ```bash
34
+ pip install -r requirements.txt
35
+ ```
36
+
37
+ ## 使用示例
38
+
39
+ ### 初始化客户端
40
+
41
+ ```python
42
+ from vectorvein.api import VectorVeinClient
43
+
44
+ # 创建客户端实例
45
+ client = VectorVeinClient(api_key="YOUR_API_KEY")
46
+ ```
47
+
48
+ ### 运行工作流
49
+
50
+ ```python
51
+ from vectorvein.api import WorkflowInputField
52
+
53
+ # 准备工作流输入字段
54
+ input_fields = [
55
+ WorkflowInputField(
56
+ node_id="8fc6eceb-8599-46a7-87fe-58bf7c0b633e",
57
+ field_name="商品名称",
58
+ value="测试商品"
59
+ )
60
+ ]
61
+
62
+ # 异步运行工作流
63
+ rid = client.run_workflow(
64
+ wid="abcde0985736457aa72cc667f17bfc89",
65
+ input_fields=input_fields,
66
+ wait_for_completion=False
67
+ )
68
+ print(f"工作流运行ID: {rid}")
69
+
70
+ # 同步运行工作流
71
+ result = client.run_workflow(
72
+ wid="abcde0985736457aa72cc667f17bfc89",
73
+ input_fields=input_fields,
74
+ wait_for_completion=True
75
+ )
76
+ print(f"工作流运行结果: {result}")
77
+ ```
78
+
79
+ ### 管理访问密钥
80
+
81
+ ```python
82
+ # 创建访问密钥
83
+ keys = client.create_access_keys(
84
+ access_key_type="L", # L: 长期, M: 多次, O: 一次性
85
+ app_id="YOUR_APP_ID",
86
+ count=1,
87
+ max_credits=500,
88
+ description="测试密钥"
89
+ )
90
+ print(f"创建的访问密钥: {keys}")
91
+
92
+ # 获取访问密钥信息
93
+ keys = client.get_access_keys(["ACCESS_KEY_1", "ACCESS_KEY_2"])
94
+ print(f"访问密钥信息: {keys}")
95
+
96
+ # 列出访问密钥
97
+ response = client.list_access_keys(
98
+ page=1,
99
+ page_size=10,
100
+ sort_field="create_time",
101
+ sort_order="descend"
102
+ )
103
+ print(f"访问密钥列表: {response}")
104
+
105
+ # 更新访问密钥
106
+ client.update_access_keys(
107
+ access_key="ACCESS_KEY",
108
+ description="更新的描述"
109
+ )
110
+
111
+ # 删除访问密钥
112
+ client.delete_access_keys(
113
+ app_id="YOUR_APP_ID",
114
+ access_keys=["ACCESS_KEY_1", "ACCESS_KEY_2"]
115
+ )
116
+ ```
117
+
118
+ ### 生成VApp访问链接
119
+
120
+ ```python
121
+ url = client.generate_vapp_url(
122
+ app_id="YOUR_APP_ID",
123
+ access_key="YOUR_ACCESS_KEY",
124
+ key_id="YOUR_KEY_ID"
125
+ )
126
+ print(f"VApp访问链接: {url}")
127
+ ```
128
+
129
+ ## API文档
130
+
131
+ ### VectorVeinClient
132
+
133
+ 主要的API客户端类,提供以下方法:
134
+
135
+ - `run_workflow()` - 运行工作流
136
+ - `check_workflow_status()` - 检查工作流运行状态
137
+ - `get_access_keys()` - 获取访问密钥信息
138
+ - `create_access_keys()` - 创建访问密钥
139
+ - `list_access_keys()` - 列出访问密钥
140
+ - `delete_access_keys()` - 删除访问密钥
141
+ - `update_access_keys()` - 更新访问密钥
142
+ - `add_apps_to_access_keys()` - 向访问密钥添加应用
143
+ - `remove_apps_from_access_keys()` - 从访问密钥移除应用
144
+ - `generate_vapp_url()` - 生成VApp访问链接
145
+
146
+ ### 数据模型
147
+
148
+ - `VApp` - VApp信息
149
+ - `AccessKey` - 访问密钥信息
150
+ - `WorkflowInputField` - 工作流输入字段
151
+ - `WorkflowOutput` - 工作流输出结果
152
+ - `WorkflowRunResult` - 工作流运行结果
153
+ - `AccessKeyListResponse` - 访问密钥列表响应
154
+
155
+ ### 异常类
156
+
157
+ - `VectorVeinAPIError` - API基础异常类
158
+ - `APIKeyError` - API密钥相关错误
159
+ - `WorkflowError` - 工作流相关错误
160
+ - `AccessKeyError` - 访问密钥相关错误
161
+ - `RequestError` - 请求相关错误
162
+ - `TimeoutError` - 超时错误
163
+
164
+ ## 注意事项
165
+
166
+ 1. 请妥善保管您的API密钥,不要将其泄露给他人。
167
+ 2. API调用有速率限制,每分钟最多60次调用。
168
+ 3. 建议在生产环境中使用异步方式运行工作流,避免长时间等待。
169
+ 4. 访问密钥的类型一旦创建就不能更改,请谨慎选择。
170
+ 5. 生成的VApp访问链接有效期默认为15分钟,请及时使用。
@@ -1,7 +1,11 @@
1
- vectorvein-0.1.95.dist-info/METADATA,sha256=9jaFqXZu5L1clFveApXDGA8IQEsC72Q7pyGibiFBAfU,775
2
- vectorvein-0.1.95.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- vectorvein-0.1.95.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1
+ vectorvein-0.2.1.dist-info/METADATA,sha256=MuvWxw_gudRvlueGAhNhgl-4rHhMpb2sygCZlqA-tjM,4413
2
+ vectorvein-0.2.1.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ vectorvein-0.2.1.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
4
  vectorvein/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ vectorvein/api/__init__.py,sha256=lfY-XA46fgD2iIZTU0VYP8i07AwA03Egj4Qua0vUKrQ,738
6
+ vectorvein/api/client.py,sha256=THL2epmILfYk5ZTzWbyeWgkFndlD8NNwDGHK2ammQyE,29110
7
+ vectorvein/api/exceptions.py,sha256=btfeXfNfc7zLykMKklpJePLnmJie5YSxCYHyMReCC9s,751
8
+ vectorvein/api/models.py,sha256=z5MeXMxWFHlNkP5vjVz6gEn5cxD1FbQ8pQvbx9KtgkE,1422
5
9
  vectorvein/chat_clients/__init__.py,sha256=omQuG4PRRPNflSAgtdU--rwsWG6vMpwMEyIGZyFVHVQ,18596
6
10
  vectorvein/chat_clients/anthropic_client.py,sha256=Zk6X1feIvv7Az5dgyipJXbm9TkgWgpFghSTxLiXKKA8,38405
7
11
  vectorvein/chat_clients/baichuan_client.py,sha256=CVMvpgjdrZGv0BWnTOBD-f2ufZ3wq3496wqukumsAr4,526
@@ -55,4 +59,4 @@ vectorvein/workflow/nodes/vector_db.py,sha256=t6I17q6iR3yQreiDHpRrksMdWDPIvgqJs0
55
59
  vectorvein/workflow/nodes/video_generation.py,sha256=qmdg-t_idpxq1veukd-jv_ChICMOoInKxprV9Z4Vi2w,4118
56
60
  vectorvein/workflow/nodes/web_crawlers.py,sha256=LsqomfXfqrXfHJDO1cl0Ox48f4St7X_SL12DSbAMSOw,5415
57
61
  vectorvein/workflow/utils/json_to_code.py,sha256=F7dhDy8kGc8ndOeihGLRLGFGlquoxVlb02ENtxnQ0C8,5914
58
- vectorvein-0.1.95.dist-info/RECORD,,
62
+ vectorvein-0.2.1.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: vectorvein
3
- Version: 0.1.95
4
- Summary: VectorVein python SDK
5
- Author-Email: Anderson <andersonby@163.com>
6
- License: MIT
7
- Requires-Python: >=3.10
8
- Requires-Dist: openai>=1.37.1
9
- Requires-Dist: tiktoken>=0.7.0
10
- Requires-Dist: httpx>=0.27.0
11
- Requires-Dist: anthropic[bedrock,vertex]>=0.31.2
12
- Requires-Dist: pydantic>=2.8.2
13
- Requires-Dist: Pillow>=10.4.0
14
- Requires-Dist: deepseek-tokenizer>=0.1.0
15
- Requires-Dist: qwen-tokenizer>=0.2.0
16
- Requires-Dist: google-auth>=2.35.0
17
- Provides-Extra: server
18
- Requires-Dist: fastapi; extra == "server"
19
- Requires-Dist: uvicorn; extra == "server"
20
- Provides-Extra: redis
21
- Requires-Dist: redis; extra == "redis"
22
- Provides-Extra: diskcache
23
- Requires-Dist: diskcache; extra == "diskcache"
24
- Description-Content-Type: text/markdown
25
-
26
- # vectorvein