vikingdb-python-sdk 0.1.9__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.
- vikingdb/__init__.py +36 -0
- vikingdb/_client.py +262 -0
- vikingdb/auth.py +68 -0
- vikingdb/exceptions.py +160 -0
- vikingdb/memory/__init__.py +70 -0
- vikingdb/memory/client.py +302 -0
- vikingdb/memory/collection.py +1038 -0
- vikingdb/memory/exceptions.py +297 -0
- vikingdb/memory/types.py +18 -0
- vikingdb/request_options.py +33 -0
- vikingdb/vector/__init__.py +23 -0
- vikingdb/vector/base.py +51 -0
- vikingdb/vector/client.py +292 -0
- vikingdb/vector/collection.py +110 -0
- vikingdb/vector/embedding.py +33 -0
- vikingdb/vector/exceptions.py +40 -0
- vikingdb/vector/index.py +189 -0
- vikingdb/vector/models/__init__.py +106 -0
- vikingdb/vector/models/base.py +68 -0
- vikingdb/vector/models/collection.py +67 -0
- vikingdb/vector/models/embedding.py +50 -0
- vikingdb/vector/models/index.py +122 -0
- vikingdb/vector/models/rerank.py +35 -0
- vikingdb/vector/rerank.py +33 -0
- vikingdb/version.py +4 -0
- vikingdb_python_sdk-0.1.9.dist-info/METADATA +228 -0
- vikingdb_python_sdk-0.1.9.dist-info/RECORD +30 -0
- vikingdb_python_sdk-0.1.9.dist-info/WHEEL +5 -0
- vikingdb_python_sdk-0.1.9.dist-info/licenses/LICENSE.txt +202 -0
- vikingdb_python_sdk-0.1.9.dist-info/top_level.txt +1 -0
vikingdb/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from .auth import APIKey, IAM
|
|
7
|
+
from .request_options import RequestOptions
|
|
8
|
+
from . import vector
|
|
9
|
+
from . import memory
|
|
10
|
+
from .vector import (
|
|
11
|
+
CollectionClient,
|
|
12
|
+
EmbeddingClient,
|
|
13
|
+
IndexClient,
|
|
14
|
+
RerankClient,
|
|
15
|
+
VikingVector,
|
|
16
|
+
)
|
|
17
|
+
from .memory import (
|
|
18
|
+
VikingMem,
|
|
19
|
+
Collection,
|
|
20
|
+
)
|
|
21
|
+
from .version import __version__
|
|
22
|
+
__all__ = [
|
|
23
|
+
"IAM",
|
|
24
|
+
"APIKey",
|
|
25
|
+
"CollectionClient",
|
|
26
|
+
"EmbeddingClient",
|
|
27
|
+
"RerankClient",
|
|
28
|
+
"IndexClient",
|
|
29
|
+
"RequestOptions",
|
|
30
|
+
"VikingVector",
|
|
31
|
+
"vector",
|
|
32
|
+
"memory",
|
|
33
|
+
"VikingMem",
|
|
34
|
+
"Collection",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|
vikingdb/_client.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Shared base client logic for Viking services."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from json import JSONDecodeError
|
|
11
|
+
from typing import Any, Mapping, Optional
|
|
12
|
+
|
|
13
|
+
import aiohttp
|
|
14
|
+
|
|
15
|
+
from volcengine.ApiInfo import ApiInfo
|
|
16
|
+
from volcengine.ServiceInfo import ServiceInfo
|
|
17
|
+
from volcengine.base.Request import Request
|
|
18
|
+
from volcengine.base.Service import Service
|
|
19
|
+
import requests
|
|
20
|
+
|
|
21
|
+
from .auth import Auth, IAM, APIKey
|
|
22
|
+
from .exceptions import (
|
|
23
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
24
|
+
VikingAPIException,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_REQUEST_ID_HEADER = "X-Tt-Logid"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Client(Service, ABC):
|
|
32
|
+
"""Reusable base client built on top of volcengine Service."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
host: str,
|
|
38
|
+
region: str,
|
|
39
|
+
service: str,
|
|
40
|
+
auth: Auth,
|
|
41
|
+
sts_token: str = "",
|
|
42
|
+
scheme: str = "http",
|
|
43
|
+
timeout: int = 30,
|
|
44
|
+
):
|
|
45
|
+
self.region = region
|
|
46
|
+
self.service = service
|
|
47
|
+
self.auth_provider = auth
|
|
48
|
+
credentials = auth.initialize(service=service, region=region)
|
|
49
|
+
self.service_info = self._build_service_info(
|
|
50
|
+
host=host,
|
|
51
|
+
credentials=credentials,
|
|
52
|
+
scheme=scheme,
|
|
53
|
+
timeout=timeout,
|
|
54
|
+
)
|
|
55
|
+
self.api_info = self._build_api_info()
|
|
56
|
+
# 判断auth是不是IAM 还是 APIKey类型
|
|
57
|
+
if isinstance(auth, IAM):
|
|
58
|
+
super().__init__(self.service_info, self.api_info)
|
|
59
|
+
elif isinstance(auth, APIKey):
|
|
60
|
+
self.session = requests.session()
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError("auth must be IAM or APIKey type")
|
|
63
|
+
|
|
64
|
+
if sts_token:
|
|
65
|
+
self.set_session_token(session_token=sts_token)
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def _build_api_info(self) -> Mapping[str, ApiInfo]:
|
|
69
|
+
"""Return the API metadata mapping used by this client."""
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _build_service_info(
|
|
73
|
+
*,
|
|
74
|
+
host: str,
|
|
75
|
+
credentials,
|
|
76
|
+
scheme: str,
|
|
77
|
+
timeout: int,
|
|
78
|
+
) -> ServiceInfo:
|
|
79
|
+
return ServiceInfo(
|
|
80
|
+
host,
|
|
81
|
+
{},
|
|
82
|
+
credentials,
|
|
83
|
+
timeout,
|
|
84
|
+
timeout,
|
|
85
|
+
scheme=scheme,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def prepare_request(self, api_info: ApiInfo, params: Optional[Mapping[str, Any]], doseq: int = 0):
|
|
89
|
+
"""Prepare a volcengine request without adding implicit headers."""
|
|
90
|
+
request = Request()
|
|
91
|
+
request.set_shema(self.service_info.scheme)
|
|
92
|
+
request.set_method(api_info.method)
|
|
93
|
+
request.set_host(self.service_info.host)
|
|
94
|
+
request.set_path(api_info.path)
|
|
95
|
+
request.set_connection_timeout(self.service_info.connection_timeout)
|
|
96
|
+
request.set_socket_timeout(self.service_info.socket_timeout)
|
|
97
|
+
request.set_headers(dict(api_info.header))
|
|
98
|
+
if params:
|
|
99
|
+
request.set_query(params)
|
|
100
|
+
return request
|
|
101
|
+
|
|
102
|
+
def _json(
|
|
103
|
+
self,
|
|
104
|
+
api: str,
|
|
105
|
+
params: Optional[Mapping[str, Any]],
|
|
106
|
+
body: Any,
|
|
107
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
108
|
+
timeout: Optional[int] = None,
|
|
109
|
+
) -> Any:
|
|
110
|
+
"""Send a JSON request synchronously.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
api: API name
|
|
114
|
+
params: Query parameters
|
|
115
|
+
body: Request body
|
|
116
|
+
headers: Additional headers
|
|
117
|
+
timeout: Timeout in seconds (optional). If not provided, uses default connection_timeout and socket_timeout.
|
|
118
|
+
"""
|
|
119
|
+
if api not in self.api_info:
|
|
120
|
+
raise Exception("no such api")
|
|
121
|
+
api_info = self.api_info[api]
|
|
122
|
+
request = self.prepare_request(api_info, params)
|
|
123
|
+
if headers:
|
|
124
|
+
for key, value in headers.items():
|
|
125
|
+
request.headers[key] = value
|
|
126
|
+
request.headers["Content-Type"] = "application/json"
|
|
127
|
+
request.body = body
|
|
128
|
+
self.auth_provider.sign_request(request)
|
|
129
|
+
url = request.build()
|
|
130
|
+
|
|
131
|
+
request_id_value = request.headers.get(_REQUEST_ID_HEADER)
|
|
132
|
+
request_id = str(request_id_value) if request_id_value else "unknown"
|
|
133
|
+
|
|
134
|
+
# Use custom timeout if provided, otherwise use default
|
|
135
|
+
if timeout is not None:
|
|
136
|
+
request_timeout = (timeout, timeout)
|
|
137
|
+
else:
|
|
138
|
+
request_timeout = (
|
|
139
|
+
self.service_info.connection_timeout,
|
|
140
|
+
self.service_info.socket_timeout,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
response = self.session.post(
|
|
145
|
+
url,
|
|
146
|
+
headers=request.headers,
|
|
147
|
+
data=request.body,
|
|
148
|
+
timeout=request_timeout,
|
|
149
|
+
)
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
raise VikingAPIException(
|
|
152
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
153
|
+
request_id=request_id,
|
|
154
|
+
message=f"failed to run session.post {api}: {exc}",
|
|
155
|
+
) from exc
|
|
156
|
+
|
|
157
|
+
payload_text_attr = getattr(response, "text", "")
|
|
158
|
+
payload_text = payload_text_attr if isinstance(payload_text_attr, str) else ""
|
|
159
|
+
payload_text = payload_text or ""
|
|
160
|
+
|
|
161
|
+
if response.status_code != 200:
|
|
162
|
+
error = VikingAPIException.from_response(
|
|
163
|
+
payload_text,
|
|
164
|
+
request_id=request_id,
|
|
165
|
+
status_code=response.status_code,
|
|
166
|
+
)
|
|
167
|
+
raise error
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
return response.json()
|
|
171
|
+
except (ValueError, JSONDecodeError) as exc:
|
|
172
|
+
raise VikingAPIException(
|
|
173
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
174
|
+
request_id=request_id,
|
|
175
|
+
message=f"failed to decode JSON response for {api}: {exc}",
|
|
176
|
+
status_code=response.status_code,
|
|
177
|
+
) from exc
|
|
178
|
+
|
|
179
|
+
except Exception as exc: # pragma: no cover - defensive fallback
|
|
180
|
+
raise VikingAPIException(
|
|
181
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
182
|
+
request_id=request_id,
|
|
183
|
+
message=f"unexpected error parsing response for {api}: {exc}",
|
|
184
|
+
status_code=response.status_code,
|
|
185
|
+
) from exc
|
|
186
|
+
|
|
187
|
+
async def async_json(
|
|
188
|
+
self,
|
|
189
|
+
api: str,
|
|
190
|
+
params: Optional[Mapping[str, Any]],
|
|
191
|
+
body: Any,
|
|
192
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
193
|
+
timeout: Optional[int] = None,
|
|
194
|
+
) -> Any:
|
|
195
|
+
"""Send a JSON request asynchronously.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
api: API name
|
|
199
|
+
params: Query parameters
|
|
200
|
+
body: Request body
|
|
201
|
+
headers: Additional headers
|
|
202
|
+
timeout: Timeout in seconds (optional). If not provided, uses default connection_timeout and socket_timeout.
|
|
203
|
+
"""
|
|
204
|
+
if api not in self.api_info:
|
|
205
|
+
raise Exception("no such api")
|
|
206
|
+
api_info = self.api_info[api]
|
|
207
|
+
request = self.prepare_request(api_info, params)
|
|
208
|
+
if headers:
|
|
209
|
+
for key, value in headers.items():
|
|
210
|
+
request.headers[key] = value
|
|
211
|
+
request.headers["Content-Type"] = "application/json"
|
|
212
|
+
request.body = body
|
|
213
|
+
|
|
214
|
+
self.auth_provider.sign_request(request)
|
|
215
|
+
|
|
216
|
+
# Use custom timeout if provided, otherwise use default
|
|
217
|
+
if timeout is not None:
|
|
218
|
+
client_timeout = aiohttp.ClientTimeout(
|
|
219
|
+
connect=timeout,
|
|
220
|
+
sock_connect=timeout,
|
|
221
|
+
sock_read=timeout,
|
|
222
|
+
)
|
|
223
|
+
else:
|
|
224
|
+
client_timeout = aiohttp.ClientTimeout(
|
|
225
|
+
connect=self.service_info.connection_timeout,
|
|
226
|
+
sock_connect=self.service_info.socket_timeout,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
url = request.build()
|
|
230
|
+
try:
|
|
231
|
+
async with aiohttp.request(
|
|
232
|
+
"POST",
|
|
233
|
+
url,
|
|
234
|
+
headers=request.headers,
|
|
235
|
+
data=request.body,
|
|
236
|
+
timeout=client_timeout,
|
|
237
|
+
) as response:
|
|
238
|
+
request_id_value = response.headers.get(_REQUEST_ID_HEADER)
|
|
239
|
+
request_id = str(request_id_value) if request_id_value else "unknown"
|
|
240
|
+
payload = await response.text(encoding="utf-8")
|
|
241
|
+
if response.status != 200:
|
|
242
|
+
error = VikingAPIException.from_response(
|
|
243
|
+
payload,
|
|
244
|
+
request_id=request_id,
|
|
245
|
+
status_code=response.status,
|
|
246
|
+
)
|
|
247
|
+
raise error
|
|
248
|
+
try:
|
|
249
|
+
return json.loads(payload)
|
|
250
|
+
except JSONDecodeError as exc:
|
|
251
|
+
raise VikingAPIException(
|
|
252
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
253
|
+
request_id=request_id,
|
|
254
|
+
message=f"failed to decode JSON response for {api}: {exc}",
|
|
255
|
+
status_code=response.status,
|
|
256
|
+
) from exc
|
|
257
|
+
except Exception as exc:
|
|
258
|
+
raise VikingAPIException(
|
|
259
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
260
|
+
request_id=request_id,
|
|
261
|
+
message=f"failed to run aiohttp {api}: {exc}",
|
|
262
|
+
) from exc
|
vikingdb/auth.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Common authentication helpers shared by Viking services."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from volcengine.Credentials import Credentials
|
|
10
|
+
from volcengine.auth.SignerV4 import SignerV4
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Auth(ABC):
|
|
14
|
+
"""Base class for authentication providers compatible with volcengine."""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def initialize(self, *, service: str, region: str):
|
|
18
|
+
"""Prepare the provider for the given service/region. Returns credentials understood by ServiceInfo."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def sign_request(self, request) -> None:
|
|
22
|
+
"""Sign or otherwise authorise the outgoing request."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IAM(Auth):
|
|
26
|
+
"""IAM-style AK/SK signature authentication provider."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, *, ak: str, sk: str):
|
|
29
|
+
if not ak or not sk:
|
|
30
|
+
raise ValueError("ak and sk must be provided for IAM authentication")
|
|
31
|
+
self._ak = ak
|
|
32
|
+
self._sk = sk
|
|
33
|
+
self._credentials = None
|
|
34
|
+
self._service = None
|
|
35
|
+
self._region = None
|
|
36
|
+
|
|
37
|
+
def initialize(self, *, service: str, region: str):
|
|
38
|
+
if not service or not region:
|
|
39
|
+
raise ValueError("service and region must be provided for IAM credentials")
|
|
40
|
+
if self._credentials is not None and (service != self._service or region != self._region):
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"IAM credentials already initialised for service={self._service!r}, region={self._region!r}"
|
|
43
|
+
)
|
|
44
|
+
self._service = service
|
|
45
|
+
self._region = region
|
|
46
|
+
if self._credentials is None:
|
|
47
|
+
self._credentials = Credentials(self._ak, self._sk, service, region)
|
|
48
|
+
return self._credentials
|
|
49
|
+
|
|
50
|
+
def sign_request(self, request) -> None:
|
|
51
|
+
if self._credentials is None:
|
|
52
|
+
raise ValueError("IAM provider must be initialised before signing requests")
|
|
53
|
+
SignerV4.sign(request, self._credentials)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class APIKey(Auth):
|
|
57
|
+
"""API Key authentication provider (placeholder for future support)."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, *, api_key: str):
|
|
60
|
+
if not api_key:
|
|
61
|
+
raise ValueError("api_key must be provided for API key authentication")
|
|
62
|
+
self.api_key = api_key
|
|
63
|
+
|
|
64
|
+
def initialize(self, *, service: str, region: str):
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def sign_request(self, request) -> None:
|
|
68
|
+
request.headers["Authorization"] = f"Bearer {self.api_key}"
|
vikingdb/exceptions.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Mapping, Optional, Type, TypeVar, Union
|
|
6
|
+
|
|
7
|
+
DEFAULT_UNKNOWN_ERROR_CODE: Union[int, str] = 1000028
|
|
8
|
+
NETWORK_ERROR_CODE = 1001
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ParsedError:
|
|
13
|
+
code: Union[int, str]
|
|
14
|
+
request_id: str
|
|
15
|
+
message: Optional[str]
|
|
16
|
+
payload: Any
|
|
17
|
+
raw: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _normalize_code(value: Any, default: Union[int, str]) -> Union[int, str]:
|
|
21
|
+
if value is None:
|
|
22
|
+
return default
|
|
23
|
+
if isinstance(value, int):
|
|
24
|
+
return value
|
|
25
|
+
if isinstance(value, str):
|
|
26
|
+
stripped = value.strip()
|
|
27
|
+
return stripped or default
|
|
28
|
+
try:
|
|
29
|
+
return int(value)
|
|
30
|
+
except (TypeError, ValueError):
|
|
31
|
+
return default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_error_payload(payload: Any) -> ParsedError:
|
|
35
|
+
"""
|
|
36
|
+
Parse a server error payload into a consistent structure.
|
|
37
|
+
|
|
38
|
+
Supports both the legacy {"ResponseMetadata": {"Error": {...}}} format and the
|
|
39
|
+
newer flat {"code": ..., "request_id": ..., "message": ...} shape. When JSON
|
|
40
|
+
decoding fails, the raw text is preserved as the message.
|
|
41
|
+
"""
|
|
42
|
+
code = DEFAULT_UNKNOWN_ERROR_CODE
|
|
43
|
+
request_id = "unknown"
|
|
44
|
+
message: Optional[str] = None
|
|
45
|
+
raw_text: Optional[str] = None
|
|
46
|
+
parsed_payload: Any = payload
|
|
47
|
+
|
|
48
|
+
if isinstance(payload, bytes):
|
|
49
|
+
raw_text = payload.decode("utf-8", errors="replace")
|
|
50
|
+
elif isinstance(payload, str):
|
|
51
|
+
raw_text = payload
|
|
52
|
+
|
|
53
|
+
if raw_text is not None:
|
|
54
|
+
try:
|
|
55
|
+
parsed_payload = json.loads(raw_text)
|
|
56
|
+
except json.JSONDecodeError:
|
|
57
|
+
message = raw_text.strip() or None
|
|
58
|
+
return ParsedError(code, request_id, message, raw_text, raw_text)
|
|
59
|
+
|
|
60
|
+
if isinstance(parsed_payload, Mapping):
|
|
61
|
+
metadata = parsed_payload.get("ResponseMetadata")
|
|
62
|
+
if isinstance(metadata, Mapping):
|
|
63
|
+
error = metadata.get("Error")
|
|
64
|
+
if isinstance(error, Mapping):
|
|
65
|
+
code_value = error.get("Code") if "Code" in error else error.get("code")
|
|
66
|
+
code = _normalize_code(code_value, code)
|
|
67
|
+
request_id = str(error.get("RequestId") or request_id)
|
|
68
|
+
if error.get("Message"):
|
|
69
|
+
message = str(error["Message"])
|
|
70
|
+
if metadata.get("RequestId"):
|
|
71
|
+
request_id = str(metadata["RequestId"])
|
|
72
|
+
else:
|
|
73
|
+
code_value = parsed_payload.get("code")
|
|
74
|
+
if code_value is None and "Code" in parsed_payload:
|
|
75
|
+
code_value = parsed_payload.get("Code")
|
|
76
|
+
code = _normalize_code(code_value, code)
|
|
77
|
+
if parsed_payload.get("request_id"):
|
|
78
|
+
request_id = str(parsed_payload["request_id"])
|
|
79
|
+
if parsed_payload.get("message"):
|
|
80
|
+
message = str(parsed_payload["message"])
|
|
81
|
+
else:
|
|
82
|
+
if message is None and raw_text is None:
|
|
83
|
+
message = str(parsed_payload)
|
|
84
|
+
|
|
85
|
+
if raw_text is None and isinstance(parsed_payload, (dict, list)):
|
|
86
|
+
try:
|
|
87
|
+
raw_text = json.dumps(parsed_payload, ensure_ascii=False)
|
|
88
|
+
except Exception:
|
|
89
|
+
raw_text = None
|
|
90
|
+
|
|
91
|
+
return ParsedError(code, request_id, message, parsed_payload, raw_text)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
T_VikingException = TypeVar("T_VikingException", bound="VikingException")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class VikingException(Exception):
|
|
98
|
+
"""
|
|
99
|
+
Base exception for all Viking SDK errors.
|
|
100
|
+
|
|
101
|
+
Captures standard fields returned by the service for consistent error handling.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
code: Union[int, str],
|
|
107
|
+
request_id: str = "unknown",
|
|
108
|
+
message: Optional[str] = None,
|
|
109
|
+
*,
|
|
110
|
+
status_code: Optional[int] = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
self.code = code
|
|
113
|
+
self.request_id = request_id or "unknown"
|
|
114
|
+
self.status_code = status_code
|
|
115
|
+
self.message = message or f"request failed (code={self.code})"
|
|
116
|
+
super().__init__(self.message)
|
|
117
|
+
|
|
118
|
+
def __str__(self) -> str:
|
|
119
|
+
status = f", http_status={self.status_code}" if self.status_code is not None else ""
|
|
120
|
+
return f"{self.message} (code={self.code}, request_id={self.request_id}{status})"
|
|
121
|
+
|
|
122
|
+
def promote(self, target_cls: Type[T_VikingException]) -> T_VikingException:
|
|
123
|
+
"""
|
|
124
|
+
Promote this exception to a more specific subclass, reusing captured context.
|
|
125
|
+
"""
|
|
126
|
+
if isinstance(self, target_cls):
|
|
127
|
+
return self # type: ignore[return-value]
|
|
128
|
+
return target_cls(
|
|
129
|
+
self.code,
|
|
130
|
+
self.request_id,
|
|
131
|
+
self.message,
|
|
132
|
+
status_code=self.status_code,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class VikingAPIException(VikingException):
|
|
137
|
+
"""Raised when the remote API returns an error payload."""
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_response(cls, payload: Any, *, request_id: Optional[str] = "unknown", status_code: Optional[int] = None) -> "VikingAPIException":
|
|
141
|
+
parsed = parse_error_payload(payload)
|
|
142
|
+
return cls(
|
|
143
|
+
parsed.code,
|
|
144
|
+
parsed.request_id or request_id,
|
|
145
|
+
parsed.message or "unknown api error",
|
|
146
|
+
status_code=status_code,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def promote_exception(
|
|
151
|
+
exc: VikingException,
|
|
152
|
+
*,
|
|
153
|
+
exception_map: Optional[Mapping[int, Type[T_VikingException]]] = None,
|
|
154
|
+
default_cls: Type[T_VikingException],
|
|
155
|
+
) -> T_VikingException:
|
|
156
|
+
"""
|
|
157
|
+
Promote a VikingException to a service-specific subclass using the provided map.
|
|
158
|
+
"""
|
|
159
|
+
target_cls = (exception_map or {}).get(exc.code, default_cls)
|
|
160
|
+
return exc.promote(target_cls)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd.
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Viking Memory SDK
|
|
7
|
+
|
|
8
|
+
Provides memory management features including:
|
|
9
|
+
- Collection management (create, delete, update, query)
|
|
10
|
+
- Profile management (add, delete, update)
|
|
11
|
+
- Event management (add, update, delete, batch delete)
|
|
12
|
+
- Session management (add messages)
|
|
13
|
+
- Memory search (semantic search)
|
|
14
|
+
|
|
15
|
+
All APIs return plain dictionaries (dict) or lists of dictionaries (list[dict]) without object encapsulation.
|
|
16
|
+
|
|
17
|
+
Authentication support:
|
|
18
|
+
- AK/SK signature authentication (standard volcengine SignerV4)
|
|
19
|
+
- API Key authentication (reserved interface)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .client import VikingMem
|
|
23
|
+
from .collection import Collection
|
|
24
|
+
from .exceptions import (
|
|
25
|
+
VikingMemException,
|
|
26
|
+
UnauthorizedException,
|
|
27
|
+
InvalidRequestException,
|
|
28
|
+
CollectionExistException,
|
|
29
|
+
CollectionNotExistException,
|
|
30
|
+
IndexExistException,
|
|
31
|
+
IndexNotExistException,
|
|
32
|
+
DataNotFoundException,
|
|
33
|
+
DelOpFailedException,
|
|
34
|
+
UpsertOpFailedException,
|
|
35
|
+
InvalidVectorException,
|
|
36
|
+
InvalidPrimaryKeyException,
|
|
37
|
+
InvalidFilterException,
|
|
38
|
+
IndexSearchException,
|
|
39
|
+
IndexFetchException,
|
|
40
|
+
IndexInitializingException,
|
|
41
|
+
EmbeddingException,
|
|
42
|
+
InternalServerException,
|
|
43
|
+
QuotaLimiterException,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
# Main client classes
|
|
48
|
+
"VikingMem",
|
|
49
|
+
"Collection",
|
|
50
|
+
# Exception classes
|
|
51
|
+
"VikingMemException",
|
|
52
|
+
"UnauthorizedException",
|
|
53
|
+
"InvalidRequestException",
|
|
54
|
+
"CollectionExistException",
|
|
55
|
+
"CollectionNotExistException",
|
|
56
|
+
"IndexExistException",
|
|
57
|
+
"IndexNotExistException",
|
|
58
|
+
"DataNotFoundException",
|
|
59
|
+
"DelOpFailedException",
|
|
60
|
+
"UpsertOpFailedException",
|
|
61
|
+
"InvalidVectorException",
|
|
62
|
+
"InvalidPrimaryKeyException",
|
|
63
|
+
"InvalidFilterException",
|
|
64
|
+
"IndexSearchException",
|
|
65
|
+
"IndexFetchException",
|
|
66
|
+
"IndexInitializingException",
|
|
67
|
+
"EmbeddingException",
|
|
68
|
+
"InternalServerException",
|
|
69
|
+
"QuotaLimiterException",
|
|
70
|
+
]
|