knowledge2 0.4.0__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.
- knowledge2-0.4.0.dist-info/METADATA +556 -0
- knowledge2-0.4.0.dist-info/RECORD +139 -0
- knowledge2-0.4.0.dist-info/WHEEL +5 -0
- knowledge2-0.4.0.dist-info/top_level.txt +1 -0
- sdk/__init__.py +70 -0
- sdk/_async_base.py +525 -0
- sdk/_async_paging.py +57 -0
- sdk/_base.py +541 -0
- sdk/_logging.py +41 -0
- sdk/_paging.py +73 -0
- sdk/_preview.py +70 -0
- sdk/_raw_response.py +25 -0
- sdk/_request_options.py +51 -0
- sdk/_transport.py +144 -0
- sdk/_validation.py +25 -0
- sdk/_validation_response.py +36 -0
- sdk/_version.py +3 -0
- sdk/async_client.py +320 -0
- sdk/async_resources/__init__.py +45 -0
- sdk/async_resources/_mixin_base.py +42 -0
- sdk/async_resources/a2a.py +230 -0
- sdk/async_resources/agents.py +489 -0
- sdk/async_resources/audit.py +145 -0
- sdk/async_resources/auth.py +133 -0
- sdk/async_resources/console.py +409 -0
- sdk/async_resources/corpora.py +276 -0
- sdk/async_resources/deployments.py +106 -0
- sdk/async_resources/documents.py +592 -0
- sdk/async_resources/feeds.py +248 -0
- sdk/async_resources/indexes.py +208 -0
- sdk/async_resources/jobs.py +165 -0
- sdk/async_resources/metadata.py +48 -0
- sdk/async_resources/models.py +102 -0
- sdk/async_resources/onboarding.py +538 -0
- sdk/async_resources/orgs.py +37 -0
- sdk/async_resources/pipelines.py +523 -0
- sdk/async_resources/projects.py +90 -0
- sdk/async_resources/search.py +262 -0
- sdk/async_resources/training.py +357 -0
- sdk/async_resources/usage.py +91 -0
- sdk/client.py +417 -0
- sdk/config.py +182 -0
- sdk/errors.py +178 -0
- sdk/examples/auth_factory.py +34 -0
- sdk/examples/batch_operations.py +57 -0
- sdk/examples/document_upload.py +56 -0
- sdk/examples/e2e_lifecycle.py +213 -0
- sdk/examples/error_handling.py +61 -0
- sdk/examples/pagination.py +64 -0
- sdk/examples/quickstart.py +36 -0
- sdk/examples/request_options.py +44 -0
- sdk/examples/search.py +64 -0
- sdk/integrations/__init__.py +57 -0
- sdk/integrations/_client.py +101 -0
- sdk/integrations/langchain/__init__.py +6 -0
- sdk/integrations/langchain/retriever.py +166 -0
- sdk/integrations/langchain/tools.py +108 -0
- sdk/integrations/llamaindex/__init__.py +11 -0
- sdk/integrations/llamaindex/filters.py +78 -0
- sdk/integrations/llamaindex/retriever.py +162 -0
- sdk/integrations/llamaindex/tools.py +109 -0
- sdk/integrations/llamaindex/vector_store.py +320 -0
- sdk/models/__init__.py +18 -0
- sdk/models/_base.py +24 -0
- sdk/models/_registry.py +457 -0
- sdk/models/a2a.py +92 -0
- sdk/models/agents.py +109 -0
- sdk/models/audit.py +28 -0
- sdk/models/auth.py +49 -0
- sdk/models/chunks.py +20 -0
- sdk/models/common.py +14 -0
- sdk/models/console.py +103 -0
- sdk/models/corpora.py +48 -0
- sdk/models/deployments.py +13 -0
- sdk/models/documents.py +126 -0
- sdk/models/embeddings.py +24 -0
- sdk/models/evaluation.py +17 -0
- sdk/models/feedback.py +9 -0
- sdk/models/feeds.py +57 -0
- sdk/models/indexes.py +36 -0
- sdk/models/jobs.py +52 -0
- sdk/models/models.py +26 -0
- sdk/models/onboarding.py +323 -0
- sdk/models/orgs.py +11 -0
- sdk/models/pipelines.py +147 -0
- sdk/models/projects.py +19 -0
- sdk/models/search.py +149 -0
- sdk/models/training.py +57 -0
- sdk/models/usage.py +39 -0
- sdk/namespaces.py +386 -0
- sdk/py.typed +0 -0
- sdk/resources/__init__.py +45 -0
- sdk/resources/_mixin_base.py +40 -0
- sdk/resources/a2a.py +230 -0
- sdk/resources/agents.py +487 -0
- sdk/resources/audit.py +144 -0
- sdk/resources/auth.py +138 -0
- sdk/resources/console.py +411 -0
- sdk/resources/corpora.py +269 -0
- sdk/resources/deployments.py +105 -0
- sdk/resources/documents.py +597 -0
- sdk/resources/feeds.py +246 -0
- sdk/resources/indexes.py +210 -0
- sdk/resources/jobs.py +164 -0
- sdk/resources/metadata.py +53 -0
- sdk/resources/models.py +99 -0
- sdk/resources/onboarding.py +542 -0
- sdk/resources/orgs.py +35 -0
- sdk/resources/pipeline_builder.py +257 -0
- sdk/resources/pipelines.py +520 -0
- sdk/resources/projects.py +87 -0
- sdk/resources/search.py +277 -0
- sdk/resources/training.py +358 -0
- sdk/resources/usage.py +92 -0
- sdk/types/__init__.py +366 -0
- sdk/types/a2a.py +88 -0
- sdk/types/agents.py +133 -0
- sdk/types/audit.py +26 -0
- sdk/types/auth.py +45 -0
- sdk/types/chunks.py +18 -0
- sdk/types/common.py +10 -0
- sdk/types/console.py +99 -0
- sdk/types/corpora.py +42 -0
- sdk/types/deployments.py +11 -0
- sdk/types/documents.py +104 -0
- sdk/types/embeddings.py +22 -0
- sdk/types/evaluation.py +15 -0
- sdk/types/feedback.py +7 -0
- sdk/types/feeds.py +61 -0
- sdk/types/indexes.py +30 -0
- sdk/types/jobs.py +50 -0
- sdk/types/models.py +22 -0
- sdk/types/onboarding.py +395 -0
- sdk/types/orgs.py +9 -0
- sdk/types/pipelines.py +177 -0
- sdk/types/projects.py +14 -0
- sdk/types/search.py +116 -0
- sdk/types/training.py +55 -0
- sdk/types/usage.py +37 -0
sdk/_preview.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Decorator for marking SDK resource classes as preview features."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import inspect
|
|
7
|
+
import warnings
|
|
8
|
+
from typing import Any, TypeVar
|
|
9
|
+
|
|
10
|
+
_warned: set[str] = set()
|
|
11
|
+
|
|
12
|
+
_T = TypeVar("_T", bound=type)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def preview_resource(cls: _T) -> _T:
|
|
16
|
+
"""Class decorator that wraps public methods to emit a one-shot RuntimeWarning.
|
|
17
|
+
|
|
18
|
+
The warning fires once per method name per process, alerting callers
|
|
19
|
+
that the resource requires a server-side feature flag.
|
|
20
|
+
|
|
21
|
+
Properties and private/dunder methods are left untouched.
|
|
22
|
+
"""
|
|
23
|
+
for name, attr in list(vars(cls).items()):
|
|
24
|
+
if name.startswith("_"):
|
|
25
|
+
continue
|
|
26
|
+
if isinstance(attr, (property, classmethod, staticmethod)):
|
|
27
|
+
continue
|
|
28
|
+
if not callable(attr):
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
fn = attr
|
|
32
|
+
|
|
33
|
+
if inspect.iscoroutinefunction(fn):
|
|
34
|
+
|
|
35
|
+
@functools.wraps(fn)
|
|
36
|
+
async def async_wrapper(
|
|
37
|
+
*args: Any, _fn: Any = fn, _name: str = name, **kwargs: Any
|
|
38
|
+
) -> Any:
|
|
39
|
+
key = f"{_fn.__qualname__}"
|
|
40
|
+
if key not in _warned:
|
|
41
|
+
_warned.add(key)
|
|
42
|
+
warnings.warn(
|
|
43
|
+
f"{_name}() is a preview feature and may not be available "
|
|
44
|
+
f"in all environments. The underlying API requires a "
|
|
45
|
+
f"feature flag to be enabled.",
|
|
46
|
+
RuntimeWarning,
|
|
47
|
+
stacklevel=2,
|
|
48
|
+
)
|
|
49
|
+
return await _fn(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
setattr(cls, name, async_wrapper)
|
|
52
|
+
else:
|
|
53
|
+
|
|
54
|
+
@functools.wraps(fn)
|
|
55
|
+
def sync_wrapper(*args: Any, _fn: Any = fn, _name: str = name, **kwargs: Any) -> Any:
|
|
56
|
+
key = f"{_fn.__qualname__}"
|
|
57
|
+
if key not in _warned:
|
|
58
|
+
_warned.add(key)
|
|
59
|
+
warnings.warn(
|
|
60
|
+
f"{_name}() is a preview feature and may not be available "
|
|
61
|
+
f"in all environments. The underlying API requires a "
|
|
62
|
+
f"feature flag to be enabled.",
|
|
63
|
+
RuntimeWarning,
|
|
64
|
+
stacklevel=2,
|
|
65
|
+
)
|
|
66
|
+
return _fn(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
setattr(cls, name, sync_wrapper)
|
|
69
|
+
|
|
70
|
+
return cls
|
sdk/_raw_response.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Raw HTTP response wrapper for the Knowledge2 SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Generic, TypeVar
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class RawResponse(Generic[T]):
|
|
13
|
+
"""Typed wrapper exposing HTTP-level details alongside the parsed body.
|
|
14
|
+
|
|
15
|
+
Returned by ``client.with_raw_response.<method>(...)``.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
status_code: HTTP status code (e.g. 200, 201).
|
|
19
|
+
headers: Response headers as a plain dict.
|
|
20
|
+
parsed: The same typed result the normal method would return.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
status_code: int
|
|
24
|
+
headers: dict[str, str]
|
|
25
|
+
parsed: T
|
sdk/_request_options.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Per-call request options for the Knowledge2 SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from sdk._base import ClientTimeouts
|
|
8
|
+
|
|
9
|
+
_BLOCKED_HEADERS = frozenset(
|
|
10
|
+
{
|
|
11
|
+
"authorization",
|
|
12
|
+
"content-type",
|
|
13
|
+
"accept",
|
|
14
|
+
"content-length",
|
|
15
|
+
"x-api-key",
|
|
16
|
+
"x-admin-token",
|
|
17
|
+
"user-agent",
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RequestOptions:
|
|
24
|
+
"""Per-call overrides for timeout, retry, and passthrough headers.
|
|
25
|
+
|
|
26
|
+
Any field left as ``None`` inherits the client-level default.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
timeout: Override the client-level :class:`ClientTimeouts` for
|
|
30
|
+
this single call.
|
|
31
|
+
max_retries: Override the client-level ``max_retries`` for this
|
|
32
|
+
single call.
|
|
33
|
+
passthrough_headers: Non-semantic headers (e.g. ``X-Request-ID``,
|
|
34
|
+
``X-Correlation-ID``) forwarded verbatim. SDK-managed headers
|
|
35
|
+
(``Authorization``, ``Content-Type``, ``X-API-Key``, etc.)
|
|
36
|
+
raise :class:`ValueError` if supplied.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
timeout: ClientTimeouts | None = None
|
|
40
|
+
max_retries: int | None = None
|
|
41
|
+
passthrough_headers: dict[str, str] | None = None
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
if self.passthrough_headers:
|
|
45
|
+
for name in self.passthrough_headers:
|
|
46
|
+
if name.lower() in _BLOCKED_HEADERS:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Header {name!r} is managed by the SDK and cannot be "
|
|
49
|
+
f"set via passthrough_headers. "
|
|
50
|
+
f"Blocked headers: {sorted(_BLOCKED_HEADERS)}"
|
|
51
|
+
)
|
sdk/_transport.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Shared transport helpers for sync and async HTTP clients.
|
|
2
|
+
|
|
3
|
+
All functions are stateless and side-effect-free (except logging).
|
|
4
|
+
They are extracted from :class:`BaseClient` so that
|
|
5
|
+
:class:`AsyncBaseClient` can reuse the same logic without duplication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import contextlib
|
|
11
|
+
import random
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from sdk.errors import (
|
|
17
|
+
APIError,
|
|
18
|
+
AuthenticationError,
|
|
19
|
+
BadRequestError,
|
|
20
|
+
ConflictError,
|
|
21
|
+
Knowledge2Error,
|
|
22
|
+
NotFoundError,
|
|
23
|
+
PermissionDeniedError,
|
|
24
|
+
RateLimitError,
|
|
25
|
+
ServerError,
|
|
26
|
+
ValidationError,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Status codes that map to specific error subclasses.
|
|
30
|
+
_STATUS_ERROR_MAP: dict[int, type[APIError]] = {
|
|
31
|
+
400: BadRequestError,
|
|
32
|
+
401: AuthenticationError,
|
|
33
|
+
403: PermissionDeniedError,
|
|
34
|
+
404: NotFoundError,
|
|
35
|
+
409: ConflictError,
|
|
36
|
+
422: ValidationError,
|
|
37
|
+
429: RateLimitError,
|
|
38
|
+
500: ServerError,
|
|
39
|
+
502: ServerError,
|
|
40
|
+
503: ServerError,
|
|
41
|
+
504: ServerError,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def error_from_response(response: httpx.Response) -> APIError:
|
|
46
|
+
"""Parse an error response into the appropriate APIError subclass."""
|
|
47
|
+
request_id = response.headers.get("X-Request-Id")
|
|
48
|
+
code: str | None = None
|
|
49
|
+
details: Any = None
|
|
50
|
+
message = response.text or response.reason_phrase or "Unknown error"
|
|
51
|
+
try:
|
|
52
|
+
payload = response.json()
|
|
53
|
+
except ValueError:
|
|
54
|
+
payload = None
|
|
55
|
+
if isinstance(payload, dict):
|
|
56
|
+
error = payload.get("error")
|
|
57
|
+
if isinstance(error, dict):
|
|
58
|
+
code = error.get("code")
|
|
59
|
+
details = error.get("details")
|
|
60
|
+
request_id = error.get("request_id") or request_id
|
|
61
|
+
message = error.get("message") or message
|
|
62
|
+
elif "detail" in payload:
|
|
63
|
+
detail = payload.get("detail")
|
|
64
|
+
if isinstance(detail, str):
|
|
65
|
+
message = detail
|
|
66
|
+
else:
|
|
67
|
+
details = detail
|
|
68
|
+
if request_id:
|
|
69
|
+
message = f"{message} (request_id={request_id})"
|
|
70
|
+
|
|
71
|
+
status = response.status_code
|
|
72
|
+
error_cls = _STATUS_ERROR_MAP.get(status)
|
|
73
|
+
if error_cls is None:
|
|
74
|
+
error_cls = ServerError if 500 <= status < 600 else APIError
|
|
75
|
+
|
|
76
|
+
if error_cls is RateLimitError:
|
|
77
|
+
retry_after_raw = response.headers.get("Retry-After")
|
|
78
|
+
retry_after: float | None = None
|
|
79
|
+
if retry_after_raw is not None:
|
|
80
|
+
with contextlib.suppress(ValueError, TypeError):
|
|
81
|
+
retry_after = float(retry_after_raw)
|
|
82
|
+
return RateLimitError(
|
|
83
|
+
message,
|
|
84
|
+
status_code=status,
|
|
85
|
+
retry_after=retry_after,
|
|
86
|
+
code=code,
|
|
87
|
+
details=details,
|
|
88
|
+
request_id=request_id,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return error_cls(
|
|
92
|
+
message,
|
|
93
|
+
status_code=status,
|
|
94
|
+
code=code,
|
|
95
|
+
details=details,
|
|
96
|
+
request_id=request_id,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def calculate_backoff(
|
|
101
|
+
attempt: int,
|
|
102
|
+
error: Knowledge2Error | None,
|
|
103
|
+
*,
|
|
104
|
+
backoff_factor: float = 0.5,
|
|
105
|
+
backoff_max: float = 8.0,
|
|
106
|
+
) -> float:
|
|
107
|
+
"""Calculate exponential backoff delay with jitter."""
|
|
108
|
+
if isinstance(error, RateLimitError) and error.retry_after is not None:
|
|
109
|
+
return error.retry_after
|
|
110
|
+
base = backoff_factor * (2**attempt)
|
|
111
|
+
jitter = random.random() * 0.25 * base
|
|
112
|
+
return min(base + jitter, backoff_max)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_auth_headers(
|
|
116
|
+
*,
|
|
117
|
+
api_key: str | None,
|
|
118
|
+
bearer_token: str | None,
|
|
119
|
+
admin_token: str | None,
|
|
120
|
+
user_agent: str,
|
|
121
|
+
default_headers: dict[str, str],
|
|
122
|
+
extra: dict[str, str] | None = None,
|
|
123
|
+
) -> dict[str, str]:
|
|
124
|
+
"""Assemble request headers with auth credentials."""
|
|
125
|
+
headers = dict(default_headers)
|
|
126
|
+
extra_headers = extra if extra is not None else {}
|
|
127
|
+
if api_key:
|
|
128
|
+
headers["X-API-Key"] = api_key
|
|
129
|
+
if "X-API-Key" in extra_headers:
|
|
130
|
+
extra_headers["X-API-Key"] = api_key
|
|
131
|
+
if bearer_token:
|
|
132
|
+
bearer_value = f"Bearer {bearer_token}"
|
|
133
|
+
headers["Authorization"] = bearer_value
|
|
134
|
+
if "Authorization" in extra_headers:
|
|
135
|
+
extra_headers["Authorization"] = bearer_value
|
|
136
|
+
if admin_token:
|
|
137
|
+
headers["X-Admin-Token"] = admin_token
|
|
138
|
+
if "X-Admin-Token" in extra_headers:
|
|
139
|
+
extra_headers["X-Admin-Token"] = admin_token
|
|
140
|
+
if user_agent and "User-Agent" not in headers and "User-Agent" not in extra_headers:
|
|
141
|
+
headers["User-Agent"] = user_agent
|
|
142
|
+
if extra_headers:
|
|
143
|
+
headers.update(extra_headers)
|
|
144
|
+
return headers
|
sdk/_validation.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Parameter validation utilities for the Knowledge2 SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def require_str(value: str, name: str) -> str:
|
|
7
|
+
"""Validate that a required string parameter is non-empty.
|
|
8
|
+
|
|
9
|
+
Strips whitespace and raises :class:`ValueError` if the result is
|
|
10
|
+
empty. Returns the stripped value for use as the canonical form.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
value: The parameter value to validate.
|
|
14
|
+
name: The parameter name (used in the error message).
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The stripped, non-empty string.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: If *value* is not a string or is empty/whitespace-only.
|
|
21
|
+
"""
|
|
22
|
+
stripped = value.strip() if isinstance(value, str) else ""
|
|
23
|
+
if not stripped:
|
|
24
|
+
raise ValueError(f"'{name}' must be a non-empty string, got {value!r}")
|
|
25
|
+
return stripped
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Shared response validation logic for sync and async clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def maybe_validate(data: Any, model_name: str, *, validate: bool, raw_response_cls: type) -> Any:
|
|
9
|
+
"""Validate response data through its Pydantic model if validation is enabled.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
data: The raw dict from ``_request()``.
|
|
13
|
+
model_name: The TypedDict class name (e.g. ``"SearchResponse"``).
|
|
14
|
+
validate: Whether response validation is enabled.
|
|
15
|
+
raw_response_cls: The RawResponse class to check against.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The validated Pydantic model instance if validation is on,
|
|
19
|
+
otherwise the raw dict unchanged.
|
|
20
|
+
"""
|
|
21
|
+
if isinstance(data, raw_response_cls):
|
|
22
|
+
return data
|
|
23
|
+
if not validate or data is None:
|
|
24
|
+
return data
|
|
25
|
+
try:
|
|
26
|
+
from sdk.models._registry import get_model_for_type
|
|
27
|
+
except ImportError as exc:
|
|
28
|
+
raise ImportError(
|
|
29
|
+
"Response validation requires the optional 'pydantic' dependency. "
|
|
30
|
+
"Install with: pip install 'knowledge2[pydantic]'"
|
|
31
|
+
) from exc
|
|
32
|
+
|
|
33
|
+
model_cls = get_model_for_type(model_name)
|
|
34
|
+
if model_cls is None:
|
|
35
|
+
return data
|
|
36
|
+
return model_cls.model_validate(data) # type: ignore[attr-defined]
|
sdk/_version.py
ADDED
sdk/async_client.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Async Knowledge2 API client.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
from sdk import AsyncKnowledge2
|
|
6
|
+
|
|
7
|
+
async with AsyncKnowledge2(api_key="k2_...") as client:
|
|
8
|
+
results = await client.search(corpus_id, "my query")
|
|
9
|
+
|
|
10
|
+
# Or use the async factory to auto-detect org_id:
|
|
11
|
+
client = await AsyncKnowledge2.create(api_key="k2_...")
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import contextvars
|
|
17
|
+
import functools
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from sdk._async_base import AsyncBaseClient
|
|
25
|
+
from sdk._base import ClientLimits, ClientTimeouts
|
|
26
|
+
from sdk._logging import set_debug as _set_debug
|
|
27
|
+
from sdk.async_resources import (
|
|
28
|
+
AsyncA2AMixin,
|
|
29
|
+
AsyncAgentsMixin,
|
|
30
|
+
AsyncAuditMixin,
|
|
31
|
+
AsyncAuthMixin,
|
|
32
|
+
AsyncConsoleMixin,
|
|
33
|
+
AsyncCorporaMixin,
|
|
34
|
+
AsyncDeploymentsMixin,
|
|
35
|
+
AsyncDocumentsMixin,
|
|
36
|
+
AsyncFeedsMixin,
|
|
37
|
+
AsyncIndexesMixin,
|
|
38
|
+
AsyncJobsMixin,
|
|
39
|
+
AsyncMetadataMixin,
|
|
40
|
+
AsyncModelsMixin,
|
|
41
|
+
AsyncOnboardingMixin,
|
|
42
|
+
AsyncOrgsMixin,
|
|
43
|
+
AsyncPipelinesMixin,
|
|
44
|
+
AsyncProjectsMixin,
|
|
45
|
+
AsyncSearchMixin,
|
|
46
|
+
AsyncTrainingMixin,
|
|
47
|
+
AsyncUsageMixin,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if TYPE_CHECKING:
|
|
51
|
+
from sdk.config import K2Config
|
|
52
|
+
|
|
53
|
+
DEFAULT_API_HOST = "https://api.knowledge2.ai"
|
|
54
|
+
|
|
55
|
+
_SENTINEL = object()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AsyncKnowledge2(
|
|
59
|
+
AsyncBaseClient,
|
|
60
|
+
AsyncOrgsMixin,
|
|
61
|
+
AsyncAuthMixin,
|
|
62
|
+
AsyncProjectsMixin,
|
|
63
|
+
AsyncCorporaMixin,
|
|
64
|
+
AsyncModelsMixin,
|
|
65
|
+
AsyncDocumentsMixin,
|
|
66
|
+
AsyncIndexesMixin,
|
|
67
|
+
AsyncSearchMixin,
|
|
68
|
+
AsyncMetadataMixin,
|
|
69
|
+
AsyncTrainingMixin,
|
|
70
|
+
AsyncDeploymentsMixin,
|
|
71
|
+
AsyncJobsMixin,
|
|
72
|
+
AsyncAuditMixin,
|
|
73
|
+
AsyncUsageMixin,
|
|
74
|
+
AsyncConsoleMixin,
|
|
75
|
+
AsyncOnboardingMixin,
|
|
76
|
+
AsyncAgentsMixin,
|
|
77
|
+
AsyncFeedsMixin,
|
|
78
|
+
AsyncPipelinesMixin,
|
|
79
|
+
AsyncA2AMixin,
|
|
80
|
+
):
|
|
81
|
+
"""Async Knowledge2 API client.
|
|
82
|
+
|
|
83
|
+
The async variant of :class:`~sdk.client.Knowledge2`. Supports
|
|
84
|
+
``async with`` context management and ``await``-based API calls.
|
|
85
|
+
|
|
86
|
+
Note: ``org_id`` auto-detection requires an async network call, so
|
|
87
|
+
it cannot happen in ``__init__``. Use :meth:`create` for automatic
|
|
88
|
+
``org_id`` resolution.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
config: Optional :class:`~sdk.config.K2Config` instance.
|
|
92
|
+
api_host: Base URL of the Knowledge2 API.
|
|
93
|
+
api_key: API key for authentication (``X-API-Key`` header).
|
|
94
|
+
org_id: Organisation ID. Use :meth:`create` for auto-detection.
|
|
95
|
+
bearer_token: Bearer token for console / Auth0 authentication.
|
|
96
|
+
bearer_token_factory: Callable returning a bearer token string.
|
|
97
|
+
token_cache_ttl: Seconds to cache the factory-produced token.
|
|
98
|
+
admin_token: Admin token (``X-Admin-Token`` header).
|
|
99
|
+
headers: Extra default headers sent with every request.
|
|
100
|
+
user_agent: Custom ``User-Agent`` header value.
|
|
101
|
+
timeout: Request timeout in seconds (or an ``httpx.Timeout``).
|
|
102
|
+
limits: Connection pool limits.
|
|
103
|
+
max_retries: Maximum number of automatic retries.
|
|
104
|
+
validate_responses: Enable Pydantic response validation.
|
|
105
|
+
http_client: Pre-configured ``httpx.AsyncClient``. When
|
|
106
|
+
supplied the SDK does **not** close it — the caller retains
|
|
107
|
+
ownership.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
*,
|
|
113
|
+
config: K2Config | None = None,
|
|
114
|
+
api_host: str | object = _SENTINEL,
|
|
115
|
+
api_key: str | None | object = _SENTINEL,
|
|
116
|
+
org_id: str | None | object = _SENTINEL,
|
|
117
|
+
bearer_token: str | None | object = _SENTINEL,
|
|
118
|
+
bearer_token_factory: Callable[[], str] | None = None,
|
|
119
|
+
token_cache_ttl: float = 300.0,
|
|
120
|
+
admin_token: str | None | object = _SENTINEL,
|
|
121
|
+
headers: dict[str, str] | None = None,
|
|
122
|
+
user_agent: str | None = None,
|
|
123
|
+
timeout: float | ClientTimeouts | httpx.Timeout | None = None,
|
|
124
|
+
limits: ClientLimits | None = None,
|
|
125
|
+
max_retries: int | object = _SENTINEL,
|
|
126
|
+
validate_responses: bool | object = _SENTINEL,
|
|
127
|
+
http_client: httpx.AsyncClient | None = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
# Resolve config vs explicit kwargs (explicit kwargs win)
|
|
130
|
+
resolved_api_host: str = DEFAULT_API_HOST
|
|
131
|
+
resolved_api_key: str | None = None
|
|
132
|
+
resolved_org_id: str | None = None
|
|
133
|
+
resolved_bearer_token: str | None = None
|
|
134
|
+
resolved_admin_token: str | None = None
|
|
135
|
+
resolved_max_retries: int = 2
|
|
136
|
+
resolved_validate_responses: bool = False
|
|
137
|
+
|
|
138
|
+
if config is not None:
|
|
139
|
+
resolved_api_host = str(config.api_host)
|
|
140
|
+
resolved_api_key = config.api_key
|
|
141
|
+
resolved_org_id = config.org_id
|
|
142
|
+
resolved_bearer_token = config.bearer_token
|
|
143
|
+
resolved_admin_token = config.admin_token
|
|
144
|
+
resolved_max_retries = config.max_retries
|
|
145
|
+
resolved_validate_responses = getattr(config, "validate_responses", False)
|
|
146
|
+
# Build timeout from config if no explicit timeout given
|
|
147
|
+
if timeout is None:
|
|
148
|
+
timeout = ClientTimeouts(
|
|
149
|
+
connect=config.timeout_connect,
|
|
150
|
+
read=config.timeout_read,
|
|
151
|
+
write=config.timeout_write,
|
|
152
|
+
)
|
|
153
|
+
# Build limits from config if no explicit limits given
|
|
154
|
+
if limits is None:
|
|
155
|
+
limits = ClientLimits(
|
|
156
|
+
max_connections=config.max_connections,
|
|
157
|
+
max_keepalive_connections=config.max_keepalive_connections,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Explicit kwargs override config
|
|
161
|
+
if api_host is not _SENTINEL:
|
|
162
|
+
resolved_api_host = api_host # type: ignore[assignment]
|
|
163
|
+
if api_key is not _SENTINEL:
|
|
164
|
+
resolved_api_key = api_key # type: ignore[assignment]
|
|
165
|
+
if org_id is not _SENTINEL:
|
|
166
|
+
resolved_org_id = org_id # type: ignore[assignment]
|
|
167
|
+
if bearer_token is not _SENTINEL:
|
|
168
|
+
resolved_bearer_token = bearer_token # type: ignore[assignment]
|
|
169
|
+
if admin_token is not _SENTINEL:
|
|
170
|
+
resolved_admin_token = admin_token # type: ignore[assignment]
|
|
171
|
+
if max_retries is not _SENTINEL:
|
|
172
|
+
resolved_max_retries = max_retries # type: ignore[assignment]
|
|
173
|
+
if validate_responses is not _SENTINEL:
|
|
174
|
+
resolved_validate_responses = validate_responses # type: ignore[assignment]
|
|
175
|
+
|
|
176
|
+
super().__init__(
|
|
177
|
+
resolved_api_host,
|
|
178
|
+
resolved_api_key,
|
|
179
|
+
bearer_token=resolved_bearer_token,
|
|
180
|
+
bearer_token_factory=bearer_token_factory,
|
|
181
|
+
token_cache_ttl=token_cache_ttl,
|
|
182
|
+
admin_token=resolved_admin_token,
|
|
183
|
+
headers=headers,
|
|
184
|
+
user_agent=user_agent,
|
|
185
|
+
timeout=timeout,
|
|
186
|
+
limits=limits,
|
|
187
|
+
max_retries=resolved_max_retries,
|
|
188
|
+
validate_responses=resolved_validate_responses,
|
|
189
|
+
http_client=http_client,
|
|
190
|
+
)
|
|
191
|
+
self.org_id = resolved_org_id
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
# Async factory
|
|
195
|
+
# ------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
async def create(cls, *, lazy: bool = False, **kwargs: Any) -> AsyncKnowledge2:
|
|
199
|
+
"""Create an async client with automatic org_id detection.
|
|
200
|
+
|
|
201
|
+
Equivalent to constructing the client and then calling
|
|
202
|
+
``get_whoami()`` to resolve the org_id when an API key is
|
|
203
|
+
provided but no org_id is given.
|
|
204
|
+
|
|
205
|
+
All keyword arguments are forwarded to :class:`AsyncKnowledge2`.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
lazy: When ``True``, skip the automatic
|
|
209
|
+
``GET /v1/auth/whoami`` call. ``org_id`` will remain
|
|
210
|
+
``None`` unless provided explicitly or resolved later
|
|
211
|
+
via :meth:`get_whoami`. Defaults to ``False``.
|
|
212
|
+
**kwargs: Forwarded to :class:`AsyncKnowledge2`.
|
|
213
|
+
"""
|
|
214
|
+
client = cls(**kwargs)
|
|
215
|
+
if not lazy and client.org_id is None and client.api_key is not None:
|
|
216
|
+
whoami = await client.get_whoami()
|
|
217
|
+
client.org_id = whoami["org_id"] if isinstance(whoami, dict) else whoami.org_id # type: ignore[attr-defined]
|
|
218
|
+
return client
|
|
219
|
+
|
|
220
|
+
# ------------------------------------------------------------------
|
|
221
|
+
# Factory classmethods
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
async def from_env(cls, **overrides: Any) -> AsyncKnowledge2:
|
|
226
|
+
"""Create a client from ``K2_*`` environment variables.
|
|
227
|
+
|
|
228
|
+
Equivalent to ``AsyncKnowledge2.create(config=K2Config())``.
|
|
229
|
+
"""
|
|
230
|
+
from sdk.config import K2Config
|
|
231
|
+
|
|
232
|
+
config = K2Config()
|
|
233
|
+
return await cls.create(config=config, **overrides)
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
async def from_file(cls, path: str | Path, **overrides: Any) -> AsyncKnowledge2:
|
|
237
|
+
"""Create a client from a JSON or YAML config file.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
path: Path to the configuration file.
|
|
241
|
+
**overrides: K2Config field values that override file values.
|
|
242
|
+
"""
|
|
243
|
+
from sdk.config import K2Config
|
|
244
|
+
|
|
245
|
+
config = K2Config.from_file(path, **overrides)
|
|
246
|
+
return await cls.create(config=config)
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
async def from_profile(
|
|
250
|
+
cls,
|
|
251
|
+
name: str,
|
|
252
|
+
path: str | Path | None = None,
|
|
253
|
+
**overrides: Any,
|
|
254
|
+
) -> AsyncKnowledge2:
|
|
255
|
+
"""Create a client from a named profile in a config file.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
name: Profile key to load (e.g. ``"staging"``).
|
|
259
|
+
path: Config file path. Defaults to ``~/.k2/config.yaml``.
|
|
260
|
+
**overrides: K2Config field values that override profile values.
|
|
261
|
+
"""
|
|
262
|
+
from sdk.config import K2Config
|
|
263
|
+
|
|
264
|
+
config = K2Config.from_profile(name, path=path, **overrides)
|
|
265
|
+
return await cls.create(config=config)
|
|
266
|
+
|
|
267
|
+
# ------------------------------------------------------------------
|
|
268
|
+
# Auth & debug helpers
|
|
269
|
+
# ------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
def is_authenticated(self) -> bool:
|
|
272
|
+
"""Return ``True`` if any authentication credential is configured."""
|
|
273
|
+
return bool(
|
|
274
|
+
self.api_key or self.bearer_token or self.admin_token or self._bearer_token_factory
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def set_debug(enabled: bool = True) -> None:
|
|
279
|
+
"""Enable or disable SDK debug logging."""
|
|
280
|
+
_set_debug(enabled)
|
|
281
|
+
|
|
282
|
+
@functools.cached_property
|
|
283
|
+
def with_raw_response(self) -> AsyncWithRawResponse:
|
|
284
|
+
"""Return an adapter that wraps every method to return :class:`RawResponse`.
|
|
285
|
+
|
|
286
|
+
Usage::
|
|
287
|
+
|
|
288
|
+
raw = await async_client.with_raw_response.list_corpora()
|
|
289
|
+
raw.status_code # 200
|
|
290
|
+
raw.headers # {"content-type": "application/json", ...}
|
|
291
|
+
raw.parsed # Page[dict] — same typed result as normal call
|
|
292
|
+
"""
|
|
293
|
+
return AsyncWithRawResponse(self)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class AsyncWithRawResponse:
|
|
297
|
+
"""Adapter that wraps an :class:`AsyncKnowledge2` client so that every
|
|
298
|
+
public method returns a :class:`~sdk._raw_response.RawResponse`.
|
|
299
|
+
|
|
300
|
+
Task-safe: uses a ``contextvars.ContextVar`` so concurrent tasks
|
|
301
|
+
do not interfere with each other.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
def __init__(self, client: AsyncKnowledge2) -> None:
|
|
305
|
+
self._client = client
|
|
306
|
+
|
|
307
|
+
def __getattr__(self, name: str) -> Any:
|
|
308
|
+
attr = getattr(self._client, name)
|
|
309
|
+
if not callable(attr) or name.startswith("_"):
|
|
310
|
+
raise AttributeError(name)
|
|
311
|
+
|
|
312
|
+
@functools.wraps(attr)
|
|
313
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
314
|
+
token = self._client._raw_response_flag.set(True)
|
|
315
|
+
try:
|
|
316
|
+
return await attr(*args, **kwargs)
|
|
317
|
+
finally:
|
|
318
|
+
self._client._raw_response_flag.reset(token)
|
|
319
|
+
|
|
320
|
+
return wrapper
|