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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Async usage metrics resource mixin for the Knowledge2 SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from sdk._request_options import RequestOptions
|
|
8
|
+
from sdk.async_resources._mixin_base import AsyncRequesterMixin
|
|
9
|
+
from sdk.types import UsageByCorpusResponse, UsageByKeyResponse, UsageSummaryResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncUsageMixin(AsyncRequesterMixin):
|
|
13
|
+
async def usage_summary(
|
|
14
|
+
self,
|
|
15
|
+
*,
|
|
16
|
+
range_value: str = "7d",
|
|
17
|
+
corpus_id: str | None = None,
|
|
18
|
+
request_options: RequestOptions | None = None,
|
|
19
|
+
) -> UsageSummaryResponse:
|
|
20
|
+
"""Retrieve an aggregate usage summary.
|
|
21
|
+
|
|
22
|
+
.. note:: Requires **bearer token** auth, not an API key.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
range_value: Time range for the summary (e.g. ``"7d"``,
|
|
26
|
+
``"30d"``).
|
|
27
|
+
corpus_id: Optional corpus to scope the summary to.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Aggregated usage statistics for the requested period.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
AuthenticationError: If called with an API key instead of a
|
|
34
|
+
bearer token.
|
|
35
|
+
Knowledge2Error: If the API request fails.
|
|
36
|
+
"""
|
|
37
|
+
params: dict[str, Any] = {"range": range_value}
|
|
38
|
+
if corpus_id:
|
|
39
|
+
params["corpus_id"] = corpus_id
|
|
40
|
+
data = await self._request(
|
|
41
|
+
"GET", "/v1/usage/summary", params=params, request_options=request_options
|
|
42
|
+
)
|
|
43
|
+
return self._maybe_validate(data, "UsageSummaryResponse")
|
|
44
|
+
|
|
45
|
+
async def usage_by_corpus(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
range_value: str = "7d",
|
|
49
|
+
request_options: RequestOptions | None = None,
|
|
50
|
+
) -> UsageByCorpusResponse:
|
|
51
|
+
"""Retrieve usage metrics broken down by corpus.
|
|
52
|
+
|
|
53
|
+
.. note:: Requires **bearer token** auth, not an API key.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
range_value: Time range for the breakdown (e.g. ``"7d"``,
|
|
57
|
+
``"30d"``).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Per-corpus usage statistics for the requested period.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
AuthenticationError: If called with an API key instead of a
|
|
64
|
+
bearer token.
|
|
65
|
+
Knowledge2Error: If the API request fails.
|
|
66
|
+
"""
|
|
67
|
+
data = await self._request(
|
|
68
|
+
"GET",
|
|
69
|
+
"/v1/usage/by_corpus",
|
|
70
|
+
params={"range": range_value},
|
|
71
|
+
request_options=request_options,
|
|
72
|
+
)
|
|
73
|
+
return self._maybe_validate(data, "UsageByCorpusResponse")
|
|
74
|
+
|
|
75
|
+
async def usage_by_key(
|
|
76
|
+
self, *, request_options: RequestOptions | None = None
|
|
77
|
+
) -> UsageByKeyResponse:
|
|
78
|
+
"""Retrieve usage metrics broken down by API key.
|
|
79
|
+
|
|
80
|
+
.. note:: Requires **bearer token** auth, not an API key.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Per-key usage statistics.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
AuthenticationError: If called with an API key instead of a
|
|
87
|
+
bearer token.
|
|
88
|
+
Knowledge2Error: If the API request fails.
|
|
89
|
+
"""
|
|
90
|
+
data = await self._request("GET", "/v1/usage/by_key", request_options=request_options)
|
|
91
|
+
return self._maybe_validate(data, "UsageByKeyResponse")
|
sdk/client.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""Knowledge2 API client.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
from sdk import Knowledge2
|
|
6
|
+
|
|
7
|
+
client = Knowledge2(api_key="k2_...")
|
|
8
|
+
results = client.search(corpus_id, "my query")
|
|
9
|
+
|
|
10
|
+
# With validated config object:
|
|
11
|
+
from sdk import K2Config
|
|
12
|
+
config = K2Config(api_key="k2_...", max_retries=5)
|
|
13
|
+
client = Knowledge2(config=config)
|
|
14
|
+
|
|
15
|
+
# From environment variables:
|
|
16
|
+
client = Knowledge2.from_env()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import functools
|
|
22
|
+
import threading
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from functools import cached_property
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import TYPE_CHECKING, Any
|
|
27
|
+
|
|
28
|
+
import httpx
|
|
29
|
+
|
|
30
|
+
from sdk._base import BaseClient, ClientLimits, ClientTimeouts
|
|
31
|
+
from sdk._logging import set_debug as _set_debug
|
|
32
|
+
from sdk.resources import (
|
|
33
|
+
A2AMixin,
|
|
34
|
+
AgentsMixin,
|
|
35
|
+
AuditMixin,
|
|
36
|
+
AuthMixin,
|
|
37
|
+
ConsoleMixin,
|
|
38
|
+
CorporaMixin,
|
|
39
|
+
DeploymentsMixin,
|
|
40
|
+
DocumentsMixin,
|
|
41
|
+
FeedsMixin,
|
|
42
|
+
IndexesMixin,
|
|
43
|
+
JobsMixin,
|
|
44
|
+
MetadataMixin,
|
|
45
|
+
ModelsMixin,
|
|
46
|
+
OnboardingMixin,
|
|
47
|
+
OrgsMixin,
|
|
48
|
+
PipelinesMixin,
|
|
49
|
+
ProjectsMixin,
|
|
50
|
+
SearchMixin,
|
|
51
|
+
TrainingMixin,
|
|
52
|
+
UsageMixin,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if TYPE_CHECKING:
|
|
56
|
+
from sdk.config import K2Config
|
|
57
|
+
from sdk.namespaces import (
|
|
58
|
+
AuthNamespace,
|
|
59
|
+
CorporaNamespace,
|
|
60
|
+
DeploymentsNamespace,
|
|
61
|
+
DocumentsNamespace,
|
|
62
|
+
JobsNamespace,
|
|
63
|
+
ModelsNamespace,
|
|
64
|
+
SearchNamespace,
|
|
65
|
+
TrainingNamespace,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
DEFAULT_API_HOST = "https://api.knowledge2.ai"
|
|
69
|
+
|
|
70
|
+
_SENTINEL = object()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Knowledge2(
|
|
74
|
+
BaseClient,
|
|
75
|
+
OrgsMixin,
|
|
76
|
+
AuthMixin,
|
|
77
|
+
ProjectsMixin,
|
|
78
|
+
CorporaMixin,
|
|
79
|
+
ModelsMixin,
|
|
80
|
+
DocumentsMixin,
|
|
81
|
+
IndexesMixin,
|
|
82
|
+
SearchMixin,
|
|
83
|
+
MetadataMixin,
|
|
84
|
+
TrainingMixin,
|
|
85
|
+
DeploymentsMixin,
|
|
86
|
+
JobsMixin,
|
|
87
|
+
AuditMixin,
|
|
88
|
+
UsageMixin,
|
|
89
|
+
ConsoleMixin,
|
|
90
|
+
OnboardingMixin,
|
|
91
|
+
AgentsMixin,
|
|
92
|
+
FeedsMixin,
|
|
93
|
+
PipelinesMixin,
|
|
94
|
+
A2AMixin,
|
|
95
|
+
):
|
|
96
|
+
"""Knowledge2 API client.
|
|
97
|
+
|
|
98
|
+
The SDK is intentionally self-contained so it can be published directly from
|
|
99
|
+
the ``sdk/`` directory.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
config: Optional :class:`~sdk.config.K2Config` instance. When
|
|
103
|
+
provided, its values are used as defaults that can be
|
|
104
|
+
overridden by explicit keyword arguments.
|
|
105
|
+
api_host: Base URL of the Knowledge2 API.
|
|
106
|
+
api_key: API key for authentication (``X-API-Key`` header).
|
|
107
|
+
org_id: Organisation ID. Auto-detected from the API key when
|
|
108
|
+
omitted via a ``GET /v1/auth/whoami`` call during construction.
|
|
109
|
+
Pass explicitly to skip this network call.
|
|
110
|
+
bearer_token: Bearer token for console / Auth0 authentication.
|
|
111
|
+
bearer_token_factory: Callable returning a bearer token string.
|
|
112
|
+
Called lazily on first request and cached for
|
|
113
|
+
``token_cache_ttl`` seconds. Mutually exclusive with
|
|
114
|
+
``bearer_token``.
|
|
115
|
+
token_cache_ttl: Seconds to cache the factory-produced token
|
|
116
|
+
(default 300). Set to ``0`` to disable caching.
|
|
117
|
+
admin_token: Admin token (``X-Admin-Token`` header).
|
|
118
|
+
headers: Extra default headers sent with every request.
|
|
119
|
+
user_agent: Custom ``User-Agent`` header value.
|
|
120
|
+
timeout: Request timeout in seconds (or an ``httpx.Timeout``).
|
|
121
|
+
limits: Connection pool limits.
|
|
122
|
+
max_retries: Maximum number of automatic retries for transient
|
|
123
|
+
errors (5xx, 429, connection failures, timeouts). Set to
|
|
124
|
+
``0`` to disable.
|
|
125
|
+
http_client: Pre-configured ``httpx.Client`` to use for HTTP
|
|
126
|
+
transport. When supplied, the SDK does **not** close it —
|
|
127
|
+
the caller retains ownership. ``timeout`` and ``limits``
|
|
128
|
+
are ignored (configure them on the client directly).
|
|
129
|
+
lazy: When ``True``, skip the automatic ``GET /v1/auth/whoami``
|
|
130
|
+
call during construction. ``org_id`` will remain ``None``
|
|
131
|
+
unless provided explicitly or resolved later via
|
|
132
|
+
:meth:`get_whoami`. Defaults to ``False``.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
*,
|
|
138
|
+
config: K2Config | None = None,
|
|
139
|
+
api_host: str | object = _SENTINEL,
|
|
140
|
+
api_key: str | None | object = _SENTINEL,
|
|
141
|
+
org_id: str | None | object = _SENTINEL,
|
|
142
|
+
bearer_token: str | None | object = _SENTINEL,
|
|
143
|
+
bearer_token_factory: Callable[[], str] | None = None,
|
|
144
|
+
token_cache_ttl: float = 300.0,
|
|
145
|
+
admin_token: str | None | object = _SENTINEL,
|
|
146
|
+
headers: dict[str, str] | None = None,
|
|
147
|
+
user_agent: str | None = None,
|
|
148
|
+
timeout: float | ClientTimeouts | httpx.Timeout | None = None,
|
|
149
|
+
limits: ClientLimits | None = None,
|
|
150
|
+
max_retries: int | object = _SENTINEL,
|
|
151
|
+
validate_responses: bool | object = _SENTINEL,
|
|
152
|
+
http_client: httpx.Client | None = None,
|
|
153
|
+
lazy: bool = False,
|
|
154
|
+
) -> None:
|
|
155
|
+
# Resolve config vs explicit kwargs (explicit kwargs win)
|
|
156
|
+
resolved_api_host: str = DEFAULT_API_HOST
|
|
157
|
+
resolved_api_key: str | None = None
|
|
158
|
+
resolved_org_id: str | None = None
|
|
159
|
+
resolved_bearer_token: str | None = None
|
|
160
|
+
resolved_admin_token: str | None = None
|
|
161
|
+
resolved_max_retries: int = 2
|
|
162
|
+
resolved_validate_responses: bool = False
|
|
163
|
+
|
|
164
|
+
if config is not None:
|
|
165
|
+
resolved_api_host = str(config.api_host)
|
|
166
|
+
resolved_api_key = config.api_key
|
|
167
|
+
resolved_org_id = config.org_id
|
|
168
|
+
resolved_bearer_token = config.bearer_token
|
|
169
|
+
resolved_admin_token = config.admin_token
|
|
170
|
+
resolved_max_retries = config.max_retries
|
|
171
|
+
resolved_validate_responses = getattr(config, "validate_responses", False)
|
|
172
|
+
# Build timeout from config if no explicit timeout given
|
|
173
|
+
if timeout is None:
|
|
174
|
+
timeout = ClientTimeouts(
|
|
175
|
+
connect=config.timeout_connect,
|
|
176
|
+
read=config.timeout_read,
|
|
177
|
+
write=config.timeout_write,
|
|
178
|
+
)
|
|
179
|
+
# Build limits from config if no explicit limits given
|
|
180
|
+
if limits is None:
|
|
181
|
+
limits = ClientLimits(
|
|
182
|
+
max_connections=config.max_connections,
|
|
183
|
+
max_keepalive_connections=config.max_keepalive_connections,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Explicit kwargs override config
|
|
187
|
+
if api_host is not _SENTINEL:
|
|
188
|
+
resolved_api_host = api_host # type: ignore[assignment]
|
|
189
|
+
if api_key is not _SENTINEL:
|
|
190
|
+
resolved_api_key = api_key # type: ignore[assignment]
|
|
191
|
+
if org_id is not _SENTINEL:
|
|
192
|
+
resolved_org_id = org_id # type: ignore[assignment]
|
|
193
|
+
if bearer_token is not _SENTINEL:
|
|
194
|
+
resolved_bearer_token = bearer_token # type: ignore[assignment]
|
|
195
|
+
if admin_token is not _SENTINEL:
|
|
196
|
+
resolved_admin_token = admin_token # type: ignore[assignment]
|
|
197
|
+
if max_retries is not _SENTINEL:
|
|
198
|
+
resolved_max_retries = max_retries # type: ignore[assignment]
|
|
199
|
+
if validate_responses is not _SENTINEL:
|
|
200
|
+
resolved_validate_responses = validate_responses # type: ignore[assignment]
|
|
201
|
+
|
|
202
|
+
super().__init__(
|
|
203
|
+
resolved_api_host,
|
|
204
|
+
resolved_api_key,
|
|
205
|
+
bearer_token=resolved_bearer_token,
|
|
206
|
+
bearer_token_factory=bearer_token_factory,
|
|
207
|
+
token_cache_ttl=token_cache_ttl,
|
|
208
|
+
admin_token=resolved_admin_token,
|
|
209
|
+
headers=headers,
|
|
210
|
+
user_agent=user_agent,
|
|
211
|
+
timeout=timeout,
|
|
212
|
+
limits=limits,
|
|
213
|
+
max_retries=resolved_max_retries,
|
|
214
|
+
validate_responses=resolved_validate_responses,
|
|
215
|
+
http_client=http_client,
|
|
216
|
+
)
|
|
217
|
+
self.org_id = resolved_org_id
|
|
218
|
+
if not lazy and self.org_id is None and resolved_api_key is not None:
|
|
219
|
+
whoami = self.get_whoami()
|
|
220
|
+
self.org_id = whoami["org_id"] if isinstance(whoami, dict) else whoami.org_id # type: ignore[attr-defined]
|
|
221
|
+
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
# Factory classmethods
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def from_env(cls, **overrides: Any) -> Knowledge2:
|
|
228
|
+
"""Create a client from ``K2_*`` environment variables.
|
|
229
|
+
|
|
230
|
+
Equivalent to ``Knowledge2(config=K2Config())``, with any
|
|
231
|
+
*overrides* passed as constructor kwargs.
|
|
232
|
+
|
|
233
|
+
Requires ``pydantic-settings`` — install via
|
|
234
|
+
``pip install knowledge2[config]``.
|
|
235
|
+
"""
|
|
236
|
+
from sdk.config import K2Config
|
|
237
|
+
|
|
238
|
+
config = K2Config()
|
|
239
|
+
return cls(config=config, **overrides)
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def from_file(cls, path: str | Path, **overrides: Any) -> Knowledge2:
|
|
243
|
+
"""Create a client from a JSON or YAML config file.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
path: Path to the configuration file.
|
|
247
|
+
**overrides: K2Config field values that override file
|
|
248
|
+
values (e.g. ``api_key="k2_..."``).
|
|
249
|
+
"""
|
|
250
|
+
from sdk.config import K2Config
|
|
251
|
+
|
|
252
|
+
config = K2Config.from_file(path, **overrides)
|
|
253
|
+
return cls(config=config)
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def from_profile(
|
|
257
|
+
cls,
|
|
258
|
+
name: str,
|
|
259
|
+
path: str | Path | None = None,
|
|
260
|
+
**overrides: Any,
|
|
261
|
+
) -> Knowledge2:
|
|
262
|
+
"""Create a client from a named profile in a config file.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
name: Profile key to load (e.g. ``"staging"``).
|
|
266
|
+
path: Config file path. Defaults to ``~/.k2/config.yaml``.
|
|
267
|
+
**overrides: K2Config field values that override profile
|
|
268
|
+
values (e.g. ``api_key="k2_..."``).
|
|
269
|
+
"""
|
|
270
|
+
from sdk.config import K2Config
|
|
271
|
+
|
|
272
|
+
config = K2Config.from_profile(name, path=path, **overrides)
|
|
273
|
+
return cls(config=config)
|
|
274
|
+
|
|
275
|
+
# ------------------------------------------------------------------
|
|
276
|
+
# Auth & debug helpers
|
|
277
|
+
# ------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
def is_authenticated(self) -> bool:
|
|
280
|
+
"""Return ``True`` if any authentication credential is configured."""
|
|
281
|
+
return bool(
|
|
282
|
+
self.api_key or self.bearer_token or self.admin_token or self._bearer_token_factory
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def set_debug(enabled: bool = True) -> None:
|
|
287
|
+
"""Enable or disable SDK debug logging.
|
|
288
|
+
|
|
289
|
+
When enabled, request method, URL, response status, and retry
|
|
290
|
+
attempts are logged to stderr. Authentication headers are
|
|
291
|
+
redacted.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
enabled: ``True`` to turn on debug logging, ``False`` to
|
|
295
|
+
turn it off.
|
|
296
|
+
"""
|
|
297
|
+
_set_debug(enabled)
|
|
298
|
+
|
|
299
|
+
# ------------------------------------------------------------------
|
|
300
|
+
# Namespace accessors
|
|
301
|
+
# ------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
@cached_property
|
|
304
|
+
def with_raw_response(self) -> WithRawResponse:
|
|
305
|
+
"""Return an adapter that wraps every method to return :class:`RawResponse`.
|
|
306
|
+
|
|
307
|
+
Usage::
|
|
308
|
+
|
|
309
|
+
raw = client.with_raw_response.list_corpora()
|
|
310
|
+
raw.status_code # 200
|
|
311
|
+
raw.headers # {"content-type": "application/json", ...}
|
|
312
|
+
raw.parsed # Page[dict] — same typed result as normal call
|
|
313
|
+
"""
|
|
314
|
+
return WithRawResponse(self)
|
|
315
|
+
|
|
316
|
+
@cached_property
|
|
317
|
+
def documents(self) -> DocumentsNamespace:
|
|
318
|
+
"""Document operations namespace: ``client.documents.*``"""
|
|
319
|
+
from sdk.namespaces import DocumentsNamespace
|
|
320
|
+
|
|
321
|
+
return DocumentsNamespace(self)
|
|
322
|
+
|
|
323
|
+
@cached_property
|
|
324
|
+
def corpora(self) -> CorporaNamespace:
|
|
325
|
+
"""Corpus operations namespace: ``client.corpora.*``"""
|
|
326
|
+
from sdk.namespaces import CorporaNamespace
|
|
327
|
+
|
|
328
|
+
return CorporaNamespace(self)
|
|
329
|
+
|
|
330
|
+
@cached_property
|
|
331
|
+
def search_ns(self) -> SearchNamespace:
|
|
332
|
+
"""Search operations namespace: ``client.search_ns.*``
|
|
333
|
+
|
|
334
|
+
Named ``search_ns`` to avoid shadowing the :meth:`search` method.
|
|
335
|
+
"""
|
|
336
|
+
from sdk.namespaces import SearchNamespace
|
|
337
|
+
|
|
338
|
+
return SearchNamespace(self)
|
|
339
|
+
|
|
340
|
+
@cached_property
|
|
341
|
+
def models_ns(self) -> ModelsNamespace:
|
|
342
|
+
"""Model operations namespace: ``client.models_ns.*``
|
|
343
|
+
|
|
344
|
+
Named ``models_ns`` to avoid shadowing the ``models`` attribute
|
|
345
|
+
used by some serialization libraries.
|
|
346
|
+
"""
|
|
347
|
+
from sdk.namespaces import ModelsNamespace
|
|
348
|
+
|
|
349
|
+
return ModelsNamespace(self)
|
|
350
|
+
|
|
351
|
+
@cached_property
|
|
352
|
+
def jobs(self) -> JobsNamespace:
|
|
353
|
+
"""Job operations namespace: ``client.jobs.*``"""
|
|
354
|
+
from sdk.namespaces import JobsNamespace
|
|
355
|
+
|
|
356
|
+
return JobsNamespace(self)
|
|
357
|
+
|
|
358
|
+
@cached_property
|
|
359
|
+
def training_ns(self) -> TrainingNamespace:
|
|
360
|
+
"""Training operations namespace: ``client.training_ns.*``"""
|
|
361
|
+
from sdk.namespaces import TrainingNamespace
|
|
362
|
+
|
|
363
|
+
return TrainingNamespace(self)
|
|
364
|
+
|
|
365
|
+
@cached_property
|
|
366
|
+
def deployments(self) -> DeploymentsNamespace:
|
|
367
|
+
"""Deployment operations namespace: ``client.deployments.*``"""
|
|
368
|
+
from sdk.namespaces import DeploymentsNamespace
|
|
369
|
+
|
|
370
|
+
return DeploymentsNamespace(self)
|
|
371
|
+
|
|
372
|
+
@cached_property
|
|
373
|
+
def auth(self) -> AuthNamespace:
|
|
374
|
+
"""Auth operations namespace: ``client.auth.*``"""
|
|
375
|
+
from sdk.namespaces import AuthNamespace
|
|
376
|
+
|
|
377
|
+
return AuthNamespace(self)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class WithRawResponse:
|
|
381
|
+
"""Adapter that wraps a :class:`Knowledge2` client so that every
|
|
382
|
+
public method returns a :class:`~sdk._raw_response.RawResponse`
|
|
383
|
+
exposing HTTP status code, headers, and the parsed body.
|
|
384
|
+
|
|
385
|
+
Thread-safe: uses a ``threading.local`` flag so concurrent threads
|
|
386
|
+
do not interfere with each other.
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
def __init__(self, client: Knowledge2) -> None:
|
|
390
|
+
self._client = client
|
|
391
|
+
|
|
392
|
+
def __getattr__(self, name: str) -> Any:
|
|
393
|
+
attr = getattr(self._client, name)
|
|
394
|
+
if not callable(attr) or name.startswith("_"):
|
|
395
|
+
raise AttributeError(name)
|
|
396
|
+
|
|
397
|
+
@functools.wraps(attr)
|
|
398
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
399
|
+
self._client._raw_response_flag.enabled = True
|
|
400
|
+
try:
|
|
401
|
+
return attr(*args, **kwargs)
|
|
402
|
+
finally:
|
|
403
|
+
self._client._raw_response_flag.enabled = False
|
|
404
|
+
|
|
405
|
+
return wrapper
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class Knowledge2Validated(Knowledge2):
|
|
409
|
+
"""Knowledge2 client with Pydantic response validation enabled by default.
|
|
410
|
+
|
|
411
|
+
Equivalent to ``Knowledge2(validate_responses=True)``.
|
|
412
|
+
Requires: ``pip install knowledge2[pydantic]``
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
416
|
+
kwargs.setdefault("validate_responses", True)
|
|
417
|
+
super().__init__(**kwargs)
|
sdk/config.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Typed, validated, immutable configuration for the Knowledge2 SDK.
|
|
2
|
+
|
|
3
|
+
Requires the ``pydantic-settings`` package::
|
|
4
|
+
|
|
5
|
+
pip install knowledge2[config]
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from sdk import Knowledge2, K2Config
|
|
10
|
+
|
|
11
|
+
# From environment variables (K2_API_KEY, K2_API_HOST, etc.)
|
|
12
|
+
config = K2Config()
|
|
13
|
+
client = Knowledge2(config=config)
|
|
14
|
+
|
|
15
|
+
# Explicit values — invalid config raises at construction
|
|
16
|
+
config = K2Config(api_key="k2_...", max_retries=5)
|
|
17
|
+
client = Knowledge2(config=config)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from pydantic import Field, PositiveFloat, PositiveInt
|
|
28
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
29
|
+
except ImportError as _exc:
|
|
30
|
+
raise ImportError(
|
|
31
|
+
"K2Config requires 'pydantic-settings'. Install it with: pip install knowledge2[config]"
|
|
32
|
+
) from _exc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class K2Config(BaseSettings):
|
|
36
|
+
"""Frozen, validated configuration for :class:`~sdk.client.Knowledge2`.
|
|
37
|
+
|
|
38
|
+
Populated from environment variables with the ``K2_`` prefix
|
|
39
|
+
(e.g. ``K2_API_KEY``, ``K2_API_HOST``), or passed directly.
|
|
40
|
+
|
|
41
|
+
Invalid values (negative timeouts, unknown keys) raise a
|
|
42
|
+
:class:`pydantic.ValidationError` at construction time — not at
|
|
43
|
+
first request.
|
|
44
|
+
|
|
45
|
+
The object is **frozen** after creation; mutating fields raises an
|
|
46
|
+
error.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
api_host: str = Field(default="https://api.knowledge2.ai")
|
|
50
|
+
api_key: str | None = Field(default=None, repr=False)
|
|
51
|
+
org_id: str | None = Field(default=None)
|
|
52
|
+
bearer_token: str | None = Field(default=None, repr=False)
|
|
53
|
+
admin_token: str | None = Field(default=None, repr=False)
|
|
54
|
+
max_retries: PositiveInt = Field(default=2)
|
|
55
|
+
timeout_connect: PositiveFloat | None = Field(default=5.0)
|
|
56
|
+
timeout_read: PositiveFloat | None = Field(default=60.0)
|
|
57
|
+
timeout_write: PositiveFloat | None = Field(default=30.0)
|
|
58
|
+
max_connections: PositiveInt = Field(default=20)
|
|
59
|
+
max_keepalive_connections: PositiveInt = Field(default=10)
|
|
60
|
+
|
|
61
|
+
model_config = SettingsConfigDict(
|
|
62
|
+
env_prefix="K2_",
|
|
63
|
+
frozen=True,
|
|
64
|
+
extra="forbid",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_file(cls, path: str | Path, **overrides: Any) -> K2Config:
|
|
69
|
+
"""Load configuration from a JSON or YAML file.
|
|
70
|
+
|
|
71
|
+
YAML support requires ``PyYAML`` (install via
|
|
72
|
+
``pip install knowledge2[yaml]``).
|
|
73
|
+
|
|
74
|
+
Explicit *overrides* take precedence over file values.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
path: Path to a JSON or YAML configuration file.
|
|
78
|
+
**overrides: K2Config field values that override the file
|
|
79
|
+
contents (e.g. ``api_key="k2_..."``). Only config
|
|
80
|
+
field names are accepted.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A frozen :class:`K2Config` instance.
|
|
84
|
+
"""
|
|
85
|
+
p = Path(path).expanduser()
|
|
86
|
+
raw = p.read_text(encoding="utf-8")
|
|
87
|
+
|
|
88
|
+
suffix = p.suffix.lower()
|
|
89
|
+
if suffix in {".yaml", ".yml"}:
|
|
90
|
+
try:
|
|
91
|
+
import yaml # type: ignore[import-untyped]
|
|
92
|
+
except ImportError as exc:
|
|
93
|
+
raise ImportError(
|
|
94
|
+
"YAML config requires 'PyYAML'. Install it with: pip install knowledge2[yaml]"
|
|
95
|
+
) from exc
|
|
96
|
+
data: dict[str, Any] = yaml.safe_load(raw) or {}
|
|
97
|
+
else:
|
|
98
|
+
data = json.loads(raw)
|
|
99
|
+
|
|
100
|
+
if not isinstance(data, dict):
|
|
101
|
+
raise TypeError(
|
|
102
|
+
f"Config file must contain a JSON/YAML mapping, got {type(data).__name__}"
|
|
103
|
+
)
|
|
104
|
+
data.update(overrides)
|
|
105
|
+
return cls(**data)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_profile(
|
|
109
|
+
cls,
|
|
110
|
+
name: str,
|
|
111
|
+
path: str | Path | None = None,
|
|
112
|
+
**overrides: Any,
|
|
113
|
+
) -> K2Config:
|
|
114
|
+
"""Load a named profile from a multi-profile config file.
|
|
115
|
+
|
|
116
|
+
The file must contain a top-level mapping where each key is a
|
|
117
|
+
profile name and each value is a config dict::
|
|
118
|
+
|
|
119
|
+
# ~/.k2/config.yaml
|
|
120
|
+
default:
|
|
121
|
+
api_key: k2_prod_...
|
|
122
|
+
staging:
|
|
123
|
+
api_host: https://staging.api.knowledge2.ai
|
|
124
|
+
api_key: k2_stg_...
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
name: The profile key to load.
|
|
128
|
+
path: Config file path. Defaults to ``~/.k2/config.yaml``
|
|
129
|
+
(falls back to ``~/.k2/config.json``).
|
|
130
|
+
**overrides: K2Config field values that override the
|
|
131
|
+
profile contents. Only config field names are accepted.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A frozen :class:`K2Config` instance.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
FileNotFoundError: If no config file is found.
|
|
138
|
+
KeyError: If the profile name is not in the file.
|
|
139
|
+
"""
|
|
140
|
+
if path is not None:
|
|
141
|
+
resolved = Path(path).expanduser()
|
|
142
|
+
else:
|
|
143
|
+
base = Path.home() / ".k2"
|
|
144
|
+
yaml_path = base / "config.yaml"
|
|
145
|
+
yml_path = base / "config.yml"
|
|
146
|
+
json_path = base / "config.json"
|
|
147
|
+
if yaml_path.exists():
|
|
148
|
+
resolved = yaml_path
|
|
149
|
+
elif yml_path.exists():
|
|
150
|
+
resolved = yml_path
|
|
151
|
+
elif json_path.exists():
|
|
152
|
+
resolved = json_path
|
|
153
|
+
else:
|
|
154
|
+
raise FileNotFoundError(
|
|
155
|
+
f"No config file found at {yaml_path}, {yml_path}, or {json_path}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
raw = resolved.read_text(encoding="utf-8")
|
|
159
|
+
suffix = resolved.suffix.lower()
|
|
160
|
+
if suffix in {".yaml", ".yml"}:
|
|
161
|
+
try:
|
|
162
|
+
import yaml
|
|
163
|
+
except ImportError as exc:
|
|
164
|
+
raise ImportError(
|
|
165
|
+
"YAML config requires 'PyYAML'. Install it with: pip install knowledge2[yaml]"
|
|
166
|
+
) from exc
|
|
167
|
+
all_profiles: dict[str, Any] = yaml.safe_load(raw) or {}
|
|
168
|
+
else:
|
|
169
|
+
all_profiles = json.loads(raw)
|
|
170
|
+
|
|
171
|
+
if not isinstance(all_profiles, dict):
|
|
172
|
+
raise TypeError(
|
|
173
|
+
f"Config file must contain a mapping of profiles, got {type(all_profiles).__name__}"
|
|
174
|
+
)
|
|
175
|
+
if name not in all_profiles:
|
|
176
|
+
raise KeyError(
|
|
177
|
+
f"Profile '{name}' not found. Available profiles: {', '.join(all_profiles.keys())}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
data = dict(all_profiles[name])
|
|
181
|
+
data.update(overrides)
|
|
182
|
+
return cls(**data)
|