vectorvein 0.2.0__py3-none-any.whl → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  """向量脉络 API 包"""
2
2
 
3
- from .client import VectorVeinClient
3
+ from .client import VectorVeinClient, AsyncVectorVeinClient
4
4
  from .models import (
5
5
  VApp,
6
6
  AccessKey,
@@ -20,6 +20,7 @@ from .exceptions import (
20
20
 
21
21
  __all__ = [
22
22
  "VectorVeinClient",
23
+ "AsyncVectorVeinClient",
23
24
  "VApp",
24
25
  "AccessKey",
25
26
  "WorkflowInputField",
vectorvein/api/client.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import time
4
4
  import base64
5
+ import asyncio
5
6
  from urllib.parse import quote
6
7
  from typing import List, Optional, Dict, Any, Union, Literal, overload
7
8
 
@@ -165,30 +166,34 @@ class VectorVeinClient:
165
166
  if time.time() - start_time > timeout:
166
167
  raise TimeoutError(f"Workflow execution timed out after {timeout} seconds")
167
168
 
168
- status = self.check_workflow_status(rid)
169
- if status["status"] == 200:
170
- return WorkflowRunResult(
171
- rid=rid,
172
- status=status["status"],
173
- msg=status["msg"],
174
- data=[WorkflowOutput(**output) for output in status["data"]],
175
- )
176
- elif status["status"] == 500:
177
- raise WorkflowError(f"Workflow execution failed: {status['msg']}")
169
+ result = self.check_workflow_status(rid)
170
+ if result.status == 200:
171
+ return result
172
+ elif result.status == 500:
173
+ raise WorkflowError(f"Workflow execution failed: {result.msg}")
178
174
 
179
175
  time.sleep(5)
180
176
 
181
- def check_workflow_status(self, rid: str) -> Dict:
177
+ def check_workflow_status(self, rid: str) -> WorkflowRunResult:
182
178
  """检查工作流运行状态
183
179
 
184
180
  Args:
185
181
  rid: 工作流运行记录ID
186
182
 
187
183
  Returns:
188
- Dict: 工作流状态信息
184
+ WorkflowRunResult: 工作流运行结果
189
185
  """
190
186
  payload = {"rid": rid}
191
- return self._request("POST", "workflow/check-status", json=payload)
187
+ response = self._request("POST", "workflow/check-status", json=payload)
188
+ if response["status"] in [200, 202]:
189
+ return WorkflowRunResult(
190
+ rid=rid,
191
+ status=response["status"],
192
+ msg=response["msg"],
193
+ data=[WorkflowOutput(**output) for output in response["data"]],
194
+ )
195
+ else:
196
+ raise WorkflowError(f"Workflow execution failed: {response['msg']}")
192
197
 
193
198
  def get_access_keys(
194
199
  self, access_keys: Optional[List[str]] = None, get_type: Literal["selected", "all"] = "selected"
@@ -424,3 +429,407 @@ class VectorVeinClient:
424
429
  quoted_token = quote(token)
425
430
 
426
431
  return f"{base_url}/public/v-app/{app_id}?token={quoted_token}&key_id={key_id}"
432
+
433
+
434
+ class AsyncVectorVeinClient:
435
+ """向量脉络 API 异步客户端类"""
436
+
437
+ API_VERSION = "20240508"
438
+ BASE_URL = "https://vectorvein.com/api/v1/open-api"
439
+
440
+ def __init__(self, api_key: str, base_url: Optional[str] = None):
441
+ """初始化异步客户端
442
+
443
+ Args:
444
+ api_key: API密钥
445
+ base_url: API基础URL,默认为https://vectorvein.com/api/v1/open-api
446
+
447
+ Raises:
448
+ APIKeyError: API密钥为空或格式不正确
449
+ """
450
+ if not api_key or not isinstance(api_key, str):
451
+ raise APIKeyError("API密钥不能为空且必须是字符串类型")
452
+
453
+ self.api_key = api_key
454
+ self.base_url = base_url or self.BASE_URL
455
+ self._client = httpx.AsyncClient(
456
+ headers={
457
+ "VECTORVEIN-API-KEY": api_key,
458
+ "VECTORVEIN-API-VERSION": self.API_VERSION,
459
+ }
460
+ )
461
+
462
+ async def __aenter__(self):
463
+ return self
464
+
465
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
466
+ await self._client.aclose()
467
+
468
+ async def _request(
469
+ self,
470
+ method: str,
471
+ endpoint: str,
472
+ params: Optional[Dict[str, Any]] = None,
473
+ json: Optional[Dict[str, Any]] = None,
474
+ **kwargs,
475
+ ) -> Dict[str, Any]:
476
+ """发送异步HTTP请求
477
+
478
+ Args:
479
+ method: HTTP方法
480
+ endpoint: API端点
481
+ params: URL参数
482
+ json: JSON请求体
483
+ **kwargs: 其他请求参数
484
+
485
+ Returns:
486
+ Dict[str, Any]: API响应
487
+
488
+ Raises:
489
+ RequestError: 请求错误
490
+ VectorVeinAPIError: API错误
491
+ APIKeyError: API密钥无效或已过期
492
+ """
493
+ url = f"{self.base_url}/{endpoint}"
494
+ try:
495
+ response = await self._client.request(method=method, url=url, params=params, json=json, **kwargs)
496
+ response.raise_for_status()
497
+ result = response.json()
498
+
499
+ if result["status"] in [401, 403]:
500
+ raise APIKeyError("API密钥无效或已过期")
501
+ if result["status"] != 200 and result["status"] != 202:
502
+ raise VectorVeinAPIError(message=result.get("msg", "Unknown error"), status_code=result["status"])
503
+ return result
504
+ except httpx.HTTPError as e:
505
+ raise RequestError(f"Request failed: {str(e)}")
506
+
507
+ @overload
508
+ async def run_workflow(
509
+ self,
510
+ wid: str,
511
+ input_fields: List[WorkflowInputField],
512
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
513
+ wait_for_completion: Literal[False] = False,
514
+ timeout: int = 30,
515
+ ) -> str: ...
516
+
517
+ @overload
518
+ async def run_workflow(
519
+ self,
520
+ wid: str,
521
+ input_fields: List[WorkflowInputField],
522
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
523
+ wait_for_completion: Literal[True] = True,
524
+ timeout: int = 30,
525
+ ) -> WorkflowRunResult: ...
526
+
527
+ async def run_workflow(
528
+ self,
529
+ wid: str,
530
+ input_fields: List[WorkflowInputField],
531
+ output_scope: Literal["all", "output_fields_only"] = "output_fields_only",
532
+ wait_for_completion: bool = False,
533
+ timeout: int = 30,
534
+ ) -> Union[str, WorkflowRunResult]:
535
+ """异步运行工作流
536
+
537
+ Args:
538
+ wid: 工作流ID
539
+ input_fields: 输入字段列表
540
+ output_scope: 输出范围,可选值:'all' 或 'output_fields_only'
541
+ wait_for_completion: 是否等待完成
542
+ timeout: 超时时间(秒)
543
+
544
+ Returns:
545
+ Union[str, WorkflowRunResult]: 工作流运行ID或运行结果
546
+
547
+ Raises:
548
+ WorkflowError: 工作流运行错误
549
+ TimeoutError: 超时错误
550
+ """
551
+ payload = {
552
+ "wid": wid,
553
+ "output_scope": output_scope,
554
+ "wait_for_completion": wait_for_completion,
555
+ "input_fields": [
556
+ {"node_id": field.node_id, "field_name": field.field_name, "value": field.value}
557
+ for field in input_fields
558
+ ],
559
+ }
560
+
561
+ result = await self._request("POST", "workflow/run", json=payload)
562
+
563
+ if not wait_for_completion:
564
+ return result["data"]["rid"]
565
+
566
+ rid = result.get("rid") or (isinstance(result["data"], dict) and result["data"].get("rid")) or ""
567
+ start_time = time.time()
568
+
569
+ while True:
570
+ if time.time() - start_time > timeout:
571
+ raise TimeoutError(f"Workflow execution timed out after {timeout} seconds")
572
+
573
+ result = await self.check_workflow_status(rid)
574
+ if result.status == 200:
575
+ return result
576
+ elif result.status == 500:
577
+ raise WorkflowError(f"Workflow execution failed: {result.msg}")
578
+
579
+ await asyncio.sleep(5)
580
+
581
+ async def check_workflow_status(self, rid: str) -> WorkflowRunResult:
582
+ """异步检查工作流运行状态
583
+
584
+ Args:
585
+ rid: 工作流运行记录ID
586
+
587
+ Returns:
588
+ WorkflowRunResult: 工作流运行结果
589
+ """
590
+ payload = {"rid": rid}
591
+ response = await self._request("POST", "workflow/check-status", json=payload)
592
+ if response["status"] in [200, 202]:
593
+ return WorkflowRunResult(
594
+ rid=rid,
595
+ status=response["status"],
596
+ msg=response["msg"],
597
+ data=[WorkflowOutput(**output) for output in response["data"]],
598
+ )
599
+ else:
600
+ raise WorkflowError(f"Workflow execution failed: {response['msg']}")
601
+
602
+ async def get_access_keys(
603
+ self, access_keys: Optional[List[str]] = None, get_type: Literal["selected", "all"] = "selected"
604
+ ) -> List[AccessKey]:
605
+ """异步获取访问密钥信息
606
+
607
+ Args:
608
+ access_keys: 访问密钥列表
609
+ get_type: 获取类型,可选值:'selected' 或 'all'
610
+
611
+ Returns:
612
+ List[AccessKey]: 访问密钥信息列表
613
+
614
+ Raises:
615
+ AccessKeyError: 访问密钥不存在或已失效
616
+ """
617
+ params = {"get_type": get_type}
618
+ if access_keys:
619
+ params["access_keys"] = ",".join(access_keys)
620
+
621
+ try:
622
+ result = await self._request("GET", "vapp/access-key/get", params=params)
623
+ return [AccessKey(**key) for key in result["data"]]
624
+ except VectorVeinAPIError as e:
625
+ if e.status_code == 404:
626
+ raise AccessKeyError("访问密钥不存在")
627
+ elif e.status_code == 403:
628
+ raise AccessKeyError("访问密钥已失效")
629
+ raise
630
+
631
+ async def create_access_keys(
632
+ self,
633
+ access_key_type: Literal["O", "M", "L"],
634
+ app_id: Optional[str] = None,
635
+ app_ids: Optional[List[str]] = None,
636
+ count: int = 1,
637
+ expire_time: Optional[str] = None,
638
+ max_credits: Optional[int] = None,
639
+ max_use_count: Optional[int] = None,
640
+ description: Optional[str] = None,
641
+ ) -> List[AccessKey]:
642
+ """异步创建访问密钥
643
+
644
+ Args:
645
+ access_key_type: 密钥类型,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
646
+ app_id: 单个应用ID
647
+ app_ids: 多个应用ID列表
648
+ count: 创建数量
649
+ expire_time: 过期时间
650
+ max_credits: 最大积分限制
651
+ max_use_count: 最大使用次数
652
+ description: 描述信息
653
+
654
+ Returns:
655
+ List[AccessKey]: 创建的访问密钥列表
656
+
657
+ Raises:
658
+ AccessKeyError: 创建访问密钥失败,如类型无效、应用不存在等
659
+ """
660
+ if access_key_type not in ["O", "M", "L"]:
661
+ raise AccessKeyError("无效的访问密钥类型,必须是 'O'(一次性)、'M'(多次) 或 'L'(长期)")
662
+
663
+ if app_id and app_ids:
664
+ raise AccessKeyError("不能同时指定 app_id 和 app_ids")
665
+
666
+ payload = {"access_key_type": access_key_type, "count": count}
667
+
668
+ if app_id:
669
+ payload["app_id"] = app_id
670
+ if app_ids:
671
+ payload["app_ids"] = app_ids
672
+ if expire_time:
673
+ payload["expire_time"] = expire_time
674
+ if max_credits is not None:
675
+ payload["max_credits"] = max_credits
676
+ if max_use_count is not None:
677
+ payload["max_use_count"] = max_use_count
678
+ if description:
679
+ payload["description"] = description
680
+
681
+ try:
682
+ result = await self._request("POST", "vapp/access-key/create", json=payload)
683
+ return [AccessKey(**key) for key in result["data"]]
684
+ except VectorVeinAPIError as e:
685
+ if e.status_code == 404:
686
+ raise AccessKeyError("指定的应用不存在")
687
+ elif e.status_code == 403:
688
+ raise AccessKeyError("没有权限创建访问密钥")
689
+ raise
690
+
691
+ async def list_access_keys(
692
+ self,
693
+ page: int = 1,
694
+ page_size: int = 10,
695
+ sort_field: str = "create_time",
696
+ sort_order: str = "descend",
697
+ app_id: Optional[str] = None,
698
+ status: Optional[List[str]] = None,
699
+ access_key_type: Optional[Literal["O", "M", "L"]] = None,
700
+ ) -> AccessKeyListResponse:
701
+ """异步列出访问密钥
702
+
703
+ Args:
704
+ page: 页码
705
+ page_size: 每页数量
706
+ sort_field: 排序字段
707
+ sort_order: 排序顺序
708
+ app_id: 应用ID
709
+ status: 状态列表
710
+ access_key_type: 密钥类型列表,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
711
+
712
+ Returns:
713
+ AccessKeyListResponse: 访问密钥列表响应
714
+ """
715
+ payload = {"page": page, "page_size": page_size, "sort_field": sort_field, "sort_order": sort_order}
716
+
717
+ if app_id:
718
+ payload["app_id"] = app_id
719
+ if status:
720
+ payload["status"] = status
721
+ if access_key_type:
722
+ payload["access_key_type"] = access_key_type
723
+
724
+ result = await self._request("POST", "vapp/access-key/list", json=payload)
725
+ return AccessKeyListResponse(**result["data"])
726
+
727
+ async def delete_access_keys(self, app_id: str, access_keys: List[str]) -> None:
728
+ """异步删除访问密钥
729
+
730
+ Args:
731
+ app_id: 应用ID
732
+ access_keys: 要删除的访问密钥列表
733
+ """
734
+ payload = {"app_id": app_id, "access_keys": access_keys}
735
+ await self._request("POST", "vapp/access-key/delete", json=payload)
736
+
737
+ async def update_access_keys(
738
+ self,
739
+ access_key: Optional[str] = None,
740
+ access_keys: Optional[List[str]] = None,
741
+ app_id: Optional[str] = None,
742
+ app_ids: Optional[List[str]] = None,
743
+ expire_time: Optional[str] = None,
744
+ max_use_count: Optional[int] = None,
745
+ max_credits: Optional[int] = None,
746
+ description: Optional[str] = None,
747
+ access_key_type: Optional[Literal["O", "M", "L"]] = None,
748
+ ) -> None:
749
+ """异步更新访问密钥
750
+
751
+ Args:
752
+ access_key: 单个访问密钥
753
+ access_keys: 多个访问密钥列表
754
+ app_id: 单个应用ID
755
+ app_ids: 多个应用ID列表
756
+ expire_time: 过期时间
757
+ max_use_count: 最大使用次数
758
+ max_credits: 最大积分限制
759
+ description: 描述信息
760
+ access_key_type: 密钥类型,可选值:'O'(一次性)、'M'(多次)、'L'(长期)
761
+ """
762
+ payload = {}
763
+ if access_key:
764
+ payload["access_key"] = access_key
765
+ if access_keys:
766
+ payload["access_keys"] = access_keys
767
+ if app_id:
768
+ payload["app_id"] = app_id
769
+ if app_ids:
770
+ payload["app_ids"] = app_ids
771
+ if expire_time:
772
+ payload["expire_time"] = expire_time
773
+ if max_use_count is not None:
774
+ payload["max_use_count"] = max_use_count
775
+ if max_credits is not None:
776
+ payload["max_credits"] = max_credits
777
+ if description:
778
+ payload["description"] = description
779
+ if access_key_type:
780
+ payload["access_key_type"] = access_key_type
781
+
782
+ await self._request("POST", "vapp/access-key/update", json=payload)
783
+
784
+ async def add_apps_to_access_keys(self, access_keys: List[str], app_ids: List[str]) -> None:
785
+ """异步向访问密钥添加应用
786
+
787
+ Args:
788
+ access_keys: 访问密钥列表
789
+ app_ids: 要添加的应用ID列表
790
+ """
791
+ payload = {"access_keys": access_keys, "app_ids": app_ids}
792
+ await self._request("POST", "vapp/access-key/add-apps", json=payload)
793
+
794
+ async def remove_apps_from_access_keys(self, access_keys: List[str], app_ids: List[str]) -> None:
795
+ """异步从访问密钥移除应用
796
+
797
+ Args:
798
+ access_keys: 访问密钥列表
799
+ app_ids: 要移除的应用ID列表
800
+ """
801
+ payload = {"access_keys": access_keys, "app_ids": app_ids}
802
+ await self._request("POST", "vapp/access-key/remove-apps", json=payload)
803
+
804
+ async def generate_vapp_url(
805
+ self,
806
+ app_id: str,
807
+ access_key: str,
808
+ key_id: str,
809
+ timeout: int = 15 * 60,
810
+ base_url: str = "https://vectorvein.com",
811
+ ) -> str:
812
+ """异步生成VApp访问链接
813
+
814
+ Args:
815
+ app_id: VApp ID
816
+ access_key: 访问密钥
817
+ key_id: 密钥ID
818
+ timeout: 超时时间(秒)
819
+ base_url: 基础URL
820
+
821
+ Returns:
822
+ str: VApp访问链接
823
+ """
824
+ timestamp = int(time.time())
825
+ message = f"{app_id}:{access_key}:{timestamp}:{timeout}"
826
+ encryption_key = self.api_key.encode()
827
+
828
+ cipher = AES.new(encryption_key, AES.MODE_CBC)
829
+ padded_data = pad(message.encode(), AES.block_size)
830
+ encrypted_data = cipher.encrypt(padded_data)
831
+ final_data = bytes(cipher.iv) + encrypted_data
832
+ token = base64.b64encode(final_data).decode("utf-8")
833
+ quoted_token = quote(token)
834
+
835
+ return f"{base_url}/public/v-app/{app_id}?token={quoted_token}&key_id={key_id}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vectorvein
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: VectorVein python SDK
5
5
  Author-Email: Anderson <andersonby@163.com>
6
6
  License: MIT
@@ -1,9 +1,9 @@
1
- vectorvein-0.2.0.dist-info/METADATA,sha256=zhUJelmK4DH7TnlwJtojTnTP_fyY58Cw6gHuz8ILvgs,4413
2
- vectorvein-0.2.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- vectorvein-0.2.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1
+ vectorvein-0.2.2.dist-info/METADATA,sha256=wS-CyMabwgi8OjyI4c9bcTRekuY2fmfOvekZB6Dh_PQ,4413
2
+ vectorvein-0.2.2.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ vectorvein-0.2.2.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
4
  vectorvein/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- vectorvein/api/__init__.py,sha256=A3TcCxRa-dnVWQ0BQyoO8IuLTO0MStcwIuLkoWsKpB8,685
6
- vectorvein/api/client.py,sha256=8UawPpShCK2wy2RpzvI0h4WHwgb1X6f5JpoP239x20k,14689
5
+ vectorvein/api/__init__.py,sha256=lfY-XA46fgD2iIZTU0VYP8i07AwA03Egj4Qua0vUKrQ,738
6
+ vectorvein/api/client.py,sha256=bym9p462CxtY3pkOyZb28EmjLUYaQQn9_eTWJSKS8mk,29464
7
7
  vectorvein/api/exceptions.py,sha256=btfeXfNfc7zLykMKklpJePLnmJie5YSxCYHyMReCC9s,751
8
8
  vectorvein/api/models.py,sha256=z5MeXMxWFHlNkP5vjVz6gEn5cxD1FbQ8pQvbx9KtgkE,1422
9
9
  vectorvein/chat_clients/__init__.py,sha256=omQuG4PRRPNflSAgtdU--rwsWG6vMpwMEyIGZyFVHVQ,18596
@@ -59,4 +59,4 @@ vectorvein/workflow/nodes/vector_db.py,sha256=t6I17q6iR3yQreiDHpRrksMdWDPIvgqJs0
59
59
  vectorvein/workflow/nodes/video_generation.py,sha256=qmdg-t_idpxq1veukd-jv_ChICMOoInKxprV9Z4Vi2w,4118
60
60
  vectorvein/workflow/nodes/web_crawlers.py,sha256=LsqomfXfqrXfHJDO1cl0Ox48f4St7X_SL12DSbAMSOw,5415
61
61
  vectorvein/workflow/utils/json_to_code.py,sha256=F7dhDy8kGc8ndOeihGLRLGFGlquoxVlb02ENtxnQ0C8,5914
62
- vectorvein-0.2.0.dist-info/RECORD,,
62
+ vectorvein-0.2.2.dist-info/RECORD,,