vectorvein 0.2.0__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.
@@ -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
 
@@ -424,3 +425,403 @@ class VectorVeinClient:
424
425
  quoted_token = quote(token)
425
426
 
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}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vectorvein
3
- Version: 0.2.0
3
+ Version: 0.2.1
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.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=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=THL2epmILfYk5ZTzWbyeWgkFndlD8NNwDGHK2ammQyE,29110
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.1.dist-info/RECORD,,