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.
- vectorvein/api/__init__.py +36 -0
- vectorvein/api/client.py +827 -0
- vectorvein/api/exceptions.py +41 -0
- vectorvein/api/models.py +74 -0
- vectorvein-0.2.1.dist-info/METADATA +170 -0
- {vectorvein-0.1.95.dist-info → vectorvein-0.2.1.dist-info}/RECORD +8 -4
- vectorvein-0.1.95.dist-info/METADATA +0 -26
- {vectorvein-0.1.95.dist-info → vectorvein-0.2.1.dist-info}/WHEEL +0 -0
- {vectorvein-0.1.95.dist-info → vectorvein-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -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
|
+
]
|
vectorvein/api/client.py
ADDED
@@ -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
|
vectorvein/api/models.py
ADDED
@@ -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.
|
2
|
-
vectorvein-0.1.
|
3
|
-
vectorvein-0.1.
|
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.
|
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
|
File without changes
|
File without changes
|