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.
Files changed (139) hide show
  1. knowledge2-0.4.0.dist-info/METADATA +556 -0
  2. knowledge2-0.4.0.dist-info/RECORD +139 -0
  3. knowledge2-0.4.0.dist-info/WHEEL +5 -0
  4. knowledge2-0.4.0.dist-info/top_level.txt +1 -0
  5. sdk/__init__.py +70 -0
  6. sdk/_async_base.py +525 -0
  7. sdk/_async_paging.py +57 -0
  8. sdk/_base.py +541 -0
  9. sdk/_logging.py +41 -0
  10. sdk/_paging.py +73 -0
  11. sdk/_preview.py +70 -0
  12. sdk/_raw_response.py +25 -0
  13. sdk/_request_options.py +51 -0
  14. sdk/_transport.py +144 -0
  15. sdk/_validation.py +25 -0
  16. sdk/_validation_response.py +36 -0
  17. sdk/_version.py +3 -0
  18. sdk/async_client.py +320 -0
  19. sdk/async_resources/__init__.py +45 -0
  20. sdk/async_resources/_mixin_base.py +42 -0
  21. sdk/async_resources/a2a.py +230 -0
  22. sdk/async_resources/agents.py +489 -0
  23. sdk/async_resources/audit.py +145 -0
  24. sdk/async_resources/auth.py +133 -0
  25. sdk/async_resources/console.py +409 -0
  26. sdk/async_resources/corpora.py +276 -0
  27. sdk/async_resources/deployments.py +106 -0
  28. sdk/async_resources/documents.py +592 -0
  29. sdk/async_resources/feeds.py +248 -0
  30. sdk/async_resources/indexes.py +208 -0
  31. sdk/async_resources/jobs.py +165 -0
  32. sdk/async_resources/metadata.py +48 -0
  33. sdk/async_resources/models.py +102 -0
  34. sdk/async_resources/onboarding.py +538 -0
  35. sdk/async_resources/orgs.py +37 -0
  36. sdk/async_resources/pipelines.py +523 -0
  37. sdk/async_resources/projects.py +90 -0
  38. sdk/async_resources/search.py +262 -0
  39. sdk/async_resources/training.py +357 -0
  40. sdk/async_resources/usage.py +91 -0
  41. sdk/client.py +417 -0
  42. sdk/config.py +182 -0
  43. sdk/errors.py +178 -0
  44. sdk/examples/auth_factory.py +34 -0
  45. sdk/examples/batch_operations.py +57 -0
  46. sdk/examples/document_upload.py +56 -0
  47. sdk/examples/e2e_lifecycle.py +213 -0
  48. sdk/examples/error_handling.py +61 -0
  49. sdk/examples/pagination.py +64 -0
  50. sdk/examples/quickstart.py +36 -0
  51. sdk/examples/request_options.py +44 -0
  52. sdk/examples/search.py +64 -0
  53. sdk/integrations/__init__.py +57 -0
  54. sdk/integrations/_client.py +101 -0
  55. sdk/integrations/langchain/__init__.py +6 -0
  56. sdk/integrations/langchain/retriever.py +166 -0
  57. sdk/integrations/langchain/tools.py +108 -0
  58. sdk/integrations/llamaindex/__init__.py +11 -0
  59. sdk/integrations/llamaindex/filters.py +78 -0
  60. sdk/integrations/llamaindex/retriever.py +162 -0
  61. sdk/integrations/llamaindex/tools.py +109 -0
  62. sdk/integrations/llamaindex/vector_store.py +320 -0
  63. sdk/models/__init__.py +18 -0
  64. sdk/models/_base.py +24 -0
  65. sdk/models/_registry.py +457 -0
  66. sdk/models/a2a.py +92 -0
  67. sdk/models/agents.py +109 -0
  68. sdk/models/audit.py +28 -0
  69. sdk/models/auth.py +49 -0
  70. sdk/models/chunks.py +20 -0
  71. sdk/models/common.py +14 -0
  72. sdk/models/console.py +103 -0
  73. sdk/models/corpora.py +48 -0
  74. sdk/models/deployments.py +13 -0
  75. sdk/models/documents.py +126 -0
  76. sdk/models/embeddings.py +24 -0
  77. sdk/models/evaluation.py +17 -0
  78. sdk/models/feedback.py +9 -0
  79. sdk/models/feeds.py +57 -0
  80. sdk/models/indexes.py +36 -0
  81. sdk/models/jobs.py +52 -0
  82. sdk/models/models.py +26 -0
  83. sdk/models/onboarding.py +323 -0
  84. sdk/models/orgs.py +11 -0
  85. sdk/models/pipelines.py +147 -0
  86. sdk/models/projects.py +19 -0
  87. sdk/models/search.py +149 -0
  88. sdk/models/training.py +57 -0
  89. sdk/models/usage.py +39 -0
  90. sdk/namespaces.py +386 -0
  91. sdk/py.typed +0 -0
  92. sdk/resources/__init__.py +45 -0
  93. sdk/resources/_mixin_base.py +40 -0
  94. sdk/resources/a2a.py +230 -0
  95. sdk/resources/agents.py +487 -0
  96. sdk/resources/audit.py +144 -0
  97. sdk/resources/auth.py +138 -0
  98. sdk/resources/console.py +411 -0
  99. sdk/resources/corpora.py +269 -0
  100. sdk/resources/deployments.py +105 -0
  101. sdk/resources/documents.py +597 -0
  102. sdk/resources/feeds.py +246 -0
  103. sdk/resources/indexes.py +210 -0
  104. sdk/resources/jobs.py +164 -0
  105. sdk/resources/metadata.py +53 -0
  106. sdk/resources/models.py +99 -0
  107. sdk/resources/onboarding.py +542 -0
  108. sdk/resources/orgs.py +35 -0
  109. sdk/resources/pipeline_builder.py +257 -0
  110. sdk/resources/pipelines.py +520 -0
  111. sdk/resources/projects.py +87 -0
  112. sdk/resources/search.py +277 -0
  113. sdk/resources/training.py +358 -0
  114. sdk/resources/usage.py +92 -0
  115. sdk/types/__init__.py +366 -0
  116. sdk/types/a2a.py +88 -0
  117. sdk/types/agents.py +133 -0
  118. sdk/types/audit.py +26 -0
  119. sdk/types/auth.py +45 -0
  120. sdk/types/chunks.py +18 -0
  121. sdk/types/common.py +10 -0
  122. sdk/types/console.py +99 -0
  123. sdk/types/corpora.py +42 -0
  124. sdk/types/deployments.py +11 -0
  125. sdk/types/documents.py +104 -0
  126. sdk/types/embeddings.py +22 -0
  127. sdk/types/evaluation.py +15 -0
  128. sdk/types/feedback.py +7 -0
  129. sdk/types/feeds.py +61 -0
  130. sdk/types/indexes.py +30 -0
  131. sdk/types/jobs.py +50 -0
  132. sdk/types/models.py +22 -0
  133. sdk/types/onboarding.py +395 -0
  134. sdk/types/orgs.py +9 -0
  135. sdk/types/pipelines.py +177 -0
  136. sdk/types/projects.py +14 -0
  137. sdk/types/search.py +116 -0
  138. sdk/types/training.py +55 -0
  139. sdk/types/usage.py +37 -0
@@ -0,0 +1,262 @@
1
+ """Async search 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._validation import require_str
9
+ from sdk.async_resources._mixin_base import AsyncRequesterMixin
10
+ from sdk.types import (
11
+ EmbeddingModelListResponse,
12
+ EmbeddingsResponse,
13
+ FeedbackResponse,
14
+ SearchBatchResponse,
15
+ SearchGenerateResponse,
16
+ SearchResponse,
17
+ )
18
+ from sdk.types.search import (
19
+ SearchGenerationConfig,
20
+ SearchHybridConfig,
21
+ SearchRerankConfig,
22
+ SearchReturnConfig,
23
+ )
24
+
25
+
26
+ class AsyncSearchMixin(AsyncRequesterMixin):
27
+ async def search(
28
+ self,
29
+ corpus_id: str,
30
+ query: str,
31
+ *,
32
+ top_k: int = 10,
33
+ filters: dict[str, Any] | None = None,
34
+ hybrid: SearchHybridConfig | dict[str, Any] | None = None,
35
+ rerank: SearchRerankConfig | dict[str, Any] | None = None,
36
+ return_config: SearchReturnConfig | dict[str, Any] | None = None,
37
+ request_options: RequestOptions | None = None,
38
+ ) -> SearchResponse:
39
+ """Search a corpus for relevant chunks.
40
+
41
+ Args:
42
+ corpus_id: The corpus to search.
43
+ query: The search query text.
44
+ top_k: Maximum number of results to return.
45
+ filters: Optional metadata filters to narrow results.
46
+ hybrid: Hybrid search configuration (dense/sparse weighting).
47
+ rerank: Reranking configuration applied after initial retrieval.
48
+ return_config: Controls which fields are included in the
49
+ response (e.g. text, metadata, provenance).
50
+
51
+ Returns:
52
+ Search results with scored chunks, metadata, and text.
53
+
54
+ Raises:
55
+ NotFoundError: If the corpus does not exist.
56
+ Knowledge2Error: If the API request fails.
57
+ """
58
+ corpus_id = require_str(corpus_id, "corpus_id")
59
+ payload: dict[str, Any] = {"query": query, "top_k": top_k}
60
+ if filters is not None:
61
+ payload["filters"] = filters
62
+ if hybrid is not None:
63
+ payload["hybrid"] = hybrid
64
+ if rerank is not None:
65
+ payload["rerank"] = rerank
66
+ if return_config is not None:
67
+ payload["return"] = return_config
68
+ data = await self._request(
69
+ "POST", f"/v1/corpora/{corpus_id}/search", json=payload, request_options=request_options
70
+ )
71
+ return self._maybe_validate(data, "SearchResponse")
72
+
73
+ async def search_batch(
74
+ self,
75
+ corpus_id: str,
76
+ queries: list[str],
77
+ *,
78
+ top_k: int = 10,
79
+ filters: dict[str, Any] | None = None,
80
+ hybrid: SearchHybridConfig | dict[str, Any] | None = None,
81
+ rerank: SearchRerankConfig | dict[str, Any] | None = None,
82
+ return_config: SearchReturnConfig | dict[str, Any] | None = None,
83
+ request_options: RequestOptions | None = None,
84
+ ) -> SearchBatchResponse:
85
+ """Execute multiple search queries against a corpus in a single request.
86
+
87
+ Args:
88
+ corpus_id: The corpus to search.
89
+ queries: List of search query texts.
90
+ top_k: Maximum number of results to return per query.
91
+ filters: Optional metadata filters applied to all queries.
92
+ hybrid: Hybrid search configuration (dense/sparse weighting).
93
+ rerank: Reranking configuration applied after initial retrieval.
94
+ return_config: Controls which fields are included in each
95
+ query's results.
96
+
97
+ Returns:
98
+ Batch search results -- one result set per input query.
99
+
100
+ Raises:
101
+ NotFoundError: If the corpus does not exist.
102
+ Knowledge2Error: If the API request fails.
103
+ """
104
+ corpus_id = require_str(corpus_id, "corpus_id")
105
+ payload: dict[str, Any] = {"queries": queries, "top_k": top_k}
106
+ if filters is not None:
107
+ payload["filters"] = filters
108
+ if hybrid is not None:
109
+ payload["hybrid"] = hybrid
110
+ if rerank is not None:
111
+ payload["rerank"] = rerank
112
+ if return_config is not None:
113
+ payload["return"] = return_config
114
+ data = await self._request(
115
+ "POST",
116
+ f"/v1/corpora/{corpus_id}/search:batch",
117
+ json=payload,
118
+ request_options=request_options,
119
+ )
120
+ return self._maybe_validate(data, "SearchBatchResponse")
121
+
122
+ async def search_generate(
123
+ self,
124
+ corpus_id: str,
125
+ query: str,
126
+ *,
127
+ top_k: int = 10,
128
+ filters: dict[str, Any] | None = None,
129
+ hybrid: SearchHybridConfig | dict[str, Any] | None = None,
130
+ rerank: SearchRerankConfig | dict[str, Any] | None = None,
131
+ return_config: SearchReturnConfig | dict[str, Any] | None = None,
132
+ generation: SearchGenerationConfig | dict[str, Any] | None = None,
133
+ request_options: RequestOptions | None = None,
134
+ ) -> SearchGenerateResponse:
135
+ """Search a corpus and generate an LLM answer grounded in the results.
136
+
137
+ Args:
138
+ corpus_id: The corpus to search.
139
+ query: The search query text.
140
+ top_k: Maximum number of retrieval results fed to the generator.
141
+ filters: Optional metadata filters to narrow retrieval results.
142
+ hybrid: Hybrid search configuration (dense/sparse weighting).
143
+ rerank: Reranking configuration applied after initial retrieval.
144
+ return_config: Controls which fields are included in the
145
+ retrieval results.
146
+ generation: LLM generation settings (model, temperature,
147
+ system prompt, etc.).
148
+
149
+ Returns:
150
+ The generated answer together with the supporting retrieval
151
+ results.
152
+
153
+ Raises:
154
+ NotFoundError: If the corpus does not exist.
155
+ Knowledge2Error: If the API request fails.
156
+ """
157
+ corpus_id = require_str(corpus_id, "corpus_id")
158
+ payload: dict[str, Any] = {"query": query, "top_k": top_k}
159
+ if filters is not None:
160
+ payload["filters"] = filters
161
+ if hybrid is not None:
162
+ payload["hybrid"] = hybrid
163
+ if rerank is not None:
164
+ payload["rerank"] = rerank
165
+ if return_config is not None:
166
+ payload["return"] = return_config
167
+ if generation is not None:
168
+ payload["generation"] = generation
169
+ data = await self._request(
170
+ "POST",
171
+ f"/v1/corpora/{corpus_id}/search:generate",
172
+ json=payload,
173
+ request_options=request_options,
174
+ )
175
+ return self._maybe_validate(data, "SearchGenerateResponse")
176
+
177
+ async def embeddings(
178
+ self,
179
+ model: str,
180
+ inputs: list[str],
181
+ embed_type: str = "query",
182
+ request_options: RequestOptions | None = None,
183
+ ) -> EmbeddingsResponse:
184
+ """Generate vector embeddings for the given texts.
185
+
186
+ Args:
187
+ model: Name or ID of the embedding model to use.
188
+ inputs: List of text strings to embed.
189
+ embed_type: Embedding type -- ``"query"`` for search queries
190
+ or ``"document"`` for corpus documents.
191
+
192
+ Returns:
193
+ Embedding vectors for each input text.
194
+
195
+ Raises:
196
+ Knowledge2Error: If the API request fails.
197
+ """
198
+ payload = {"model": model, "input": inputs, "type": embed_type}
199
+ data = await self._request(
200
+ "POST", "/v1/embeddings", json=payload, request_options=request_options
201
+ )
202
+ return self._maybe_validate(data, "EmbeddingsResponse")
203
+
204
+ async def list_embedding_models(
205
+ self,
206
+ request_options: RequestOptions | None = None,
207
+ ) -> EmbeddingModelListResponse:
208
+ """List available embedding models.
209
+
210
+ Returns the set of embedding models that can be used with the
211
+ ``embeddings`` method, indicating which model is the default.
212
+
213
+ Returns:
214
+ The list of available embedding models with default flag.
215
+
216
+ Raises:
217
+ Knowledge2Error: If the API request fails.
218
+ """
219
+ data = await self._request("GET", "/v1/embeddings/models", request_options=request_options)
220
+ return self._maybe_validate(data, "EmbeddingModelListResponse")
221
+
222
+ async def create_feedback(
223
+ self,
224
+ corpus_id: str,
225
+ query: str,
226
+ *,
227
+ clicked_chunk_ids: list[str] | None = None,
228
+ rating: int | None = None,
229
+ abstained: bool = False,
230
+ request_options: RequestOptions | None = None,
231
+ ) -> FeedbackResponse:
232
+ """Submit relevance feedback for a search query.
233
+
234
+ Args:
235
+ corpus_id: The corpus the query was executed against.
236
+ query: The original search query text.
237
+ clicked_chunk_ids: IDs of chunks the user found relevant.
238
+ rating: Overall relevance rating for the result set.
239
+ abstained: If ``True``, indicates the user chose not to
240
+ rate the results.
241
+
242
+ Returns:
243
+ Confirmation that the feedback was recorded.
244
+
245
+ Raises:
246
+ NotFoundError: If the corpus does not exist.
247
+ Knowledge2Error: If the API request fails.
248
+ """
249
+ corpus_id = require_str(corpus_id, "corpus_id")
250
+ payload: dict[str, Any] = {
251
+ "query": query,
252
+ "clicked_chunk_ids": clicked_chunk_ids,
253
+ "rating": rating,
254
+ "abstained": abstained,
255
+ }
256
+ data = await self._request(
257
+ "POST",
258
+ f"/v1/corpora/{corpus_id}/feedback",
259
+ json=payload,
260
+ request_options=request_options,
261
+ )
262
+ return self._maybe_validate(data, "FeedbackResponse")
@@ -0,0 +1,357 @@
1
+ """Async training and tuning resource mixin for the Knowledge2 SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from sdk._async_paging import AsyncPager
8
+ from sdk._paging import Page
9
+ from sdk._request_options import RequestOptions
10
+ from sdk._validation import require_str
11
+ from sdk.async_resources._mixin_base import AsyncRequesterMixin
12
+ from sdk.types import (
13
+ CancelTuningRunResponse,
14
+ EvalRunDetailResponse,
15
+ PromoteResponse,
16
+ TrainingDataBuildResponse,
17
+ TuningRunBuildResponse,
18
+ TuningRunDetailResponse,
19
+ TuningRunLogsResponse,
20
+ TuningRunResponse,
21
+ )
22
+
23
+
24
+ class AsyncTrainingMixin(AsyncRequesterMixin):
25
+ async def build_training_data(
26
+ self,
27
+ corpus_id: str,
28
+ idempotency_key: str | None = None,
29
+ request_options: RequestOptions | None = None,
30
+ ) -> TrainingDataBuildResponse:
31
+ """Trigger a training data build job for a corpus.
32
+
33
+ Args:
34
+ corpus_id: The corpus to build training data from.
35
+ idempotency_key: Optional idempotency key to prevent
36
+ duplicate builds.
37
+
38
+ Returns:
39
+ The training data build response, including the background
40
+ job ID.
41
+
42
+ Raises:
43
+ NotFoundError: If the corpus does not exist.
44
+ ConflictError: If a duplicate idempotency key is detected.
45
+ Knowledge2Error: If the API request fails.
46
+ """
47
+ corpus_id = require_str(corpus_id, "corpus_id")
48
+ headers = self._idempotency_headers(idempotency_key)
49
+ data = await self._request(
50
+ "POST",
51
+ f"/v1/corpora/{corpus_id}/training-data:build",
52
+ json={},
53
+ headers=headers,
54
+ request_options=request_options,
55
+ )
56
+ return self._maybe_validate(data, "TrainingDataBuildResponse")
57
+
58
+ async def list_training_data(
59
+ self,
60
+ corpus_id: str,
61
+ limit: int = 100,
62
+ offset: int = 0,
63
+ request_options: RequestOptions | None = None,
64
+ ) -> Page[dict[str, Any]]:
65
+ """List training datasets generated for a corpus.
66
+
67
+ Args:
68
+ corpus_id: The corpus whose training data to list.
69
+ limit: Maximum number of datasets to return per page.
70
+ offset: Number of datasets to skip for pagination.
71
+
72
+ Returns:
73
+ A Page containing training dataset records with pagination metadata.
74
+
75
+ Raises:
76
+ Knowledge2Error: If the API request fails.
77
+ """
78
+ corpus_id = require_str(corpus_id, "corpus_id")
79
+ return await self._list_page(
80
+ "GET",
81
+ f"/v1/corpora/{corpus_id}/training-data",
82
+ items_key="datasets",
83
+ limit=limit,
84
+ offset=offset,
85
+ )
86
+
87
+ def iter_training_data(
88
+ self,
89
+ corpus_id: str,
90
+ *,
91
+ limit: int = 100,
92
+ request_options: RequestOptions | None = None,
93
+ ) -> AsyncPager[dict[str, Any]]:
94
+ """Iterate over training data, automatically paginating.
95
+
96
+ Args:
97
+ corpus_id: The corpus whose training data to iterate.
98
+ limit: Page size used for each underlying API request.
99
+
100
+ Yields:
101
+ Individual training dataset dicts.
102
+
103
+ Raises:
104
+ Knowledge2Error: If any underlying API request fails.
105
+ """
106
+ corpus_id = require_str(corpus_id, "corpus_id")
107
+ return self._paginate(
108
+ "GET",
109
+ f"/v1/corpora/{corpus_id}/training-data",
110
+ items_key="datasets",
111
+ limit=limit,
112
+ )
113
+
114
+ async def list_tuning_runs(
115
+ self,
116
+ corpus_id: str,
117
+ limit: int = 100,
118
+ offset: int = 0,
119
+ request_options: RequestOptions | None = None,
120
+ ) -> Page[dict[str, Any]]:
121
+ """List tuning runs for a corpus.
122
+
123
+ Args:
124
+ corpus_id: The corpus whose tuning runs to list.
125
+ limit: Maximum number of tuning runs to return per page.
126
+ offset: Number of tuning runs to skip for pagination.
127
+
128
+ Returns:
129
+ A Page containing tuning run records with pagination metadata.
130
+
131
+ Raises:
132
+ Knowledge2Error: If the API request fails.
133
+ """
134
+ corpus_id = require_str(corpus_id, "corpus_id")
135
+ return await self._list_page(
136
+ "GET",
137
+ f"/v1/corpora/{corpus_id}/tuning-runs",
138
+ items_key="runs",
139
+ limit=limit,
140
+ offset=offset,
141
+ )
142
+
143
+ def iter_tuning_runs(
144
+ self,
145
+ corpus_id: str,
146
+ *,
147
+ limit: int = 100,
148
+ request_options: RequestOptions | None = None,
149
+ ) -> AsyncPager[dict[str, Any]]:
150
+ """Iterate over tuning runs, automatically paginating.
151
+
152
+ Args:
153
+ corpus_id: The corpus whose tuning runs to iterate.
154
+ limit: Page size used for each underlying API request.
155
+
156
+ Yields:
157
+ Individual tuning run dicts.
158
+
159
+ Raises:
160
+ Knowledge2Error: If any underlying API request fails.
161
+ """
162
+ corpus_id = require_str(corpus_id, "corpus_id")
163
+ return self._paginate(
164
+ "GET",
165
+ f"/v1/corpora/{corpus_id}/tuning-runs",
166
+ items_key="runs",
167
+ limit=limit,
168
+ )
169
+
170
+ async def create_tuning_run(
171
+ self,
172
+ corpus_id: str,
173
+ idempotency_key: str | None = None,
174
+ *,
175
+ request_options: RequestOptions | None = None,
176
+ ) -> TuningRunResponse:
177
+ """Create a tuning run for the corpus.
178
+
179
+ Args:
180
+ corpus_id: The corpus to tune.
181
+ idempotency_key: Optional idempotency key.
182
+ """
183
+ corpus_id = require_str(corpus_id, "corpus_id")
184
+ headers = self._idempotency_headers(idempotency_key)
185
+ data = await self._request(
186
+ "POST",
187
+ f"/v1/corpora/{corpus_id}/tuning-runs",
188
+ json={},
189
+ headers=headers,
190
+ request_options=request_options,
191
+ )
192
+ return self._maybe_validate(data, "TuningRunResponse")
193
+
194
+ async def build_and_start_tuning_run(
195
+ self,
196
+ corpus_id: str,
197
+ idempotency_key: str | None = None,
198
+ *,
199
+ wait: bool = True,
200
+ poll_s: int = 5,
201
+ request_options: RequestOptions | None = None,
202
+ ) -> TuningRunBuildResponse:
203
+ """Build training data and start a tuning run in one step.
204
+
205
+ Args:
206
+ corpus_id: The corpus to tune.
207
+ idempotency_key: Optional idempotency key to prevent
208
+ duplicate runs.
209
+ wait: If ``True`` (default), block until the build job
210
+ completes.
211
+ poll_s: Polling interval in seconds when *wait* is ``True``.
212
+
213
+ Returns:
214
+ The tuning run build response, including the background
215
+ job ID.
216
+
217
+ Raises:
218
+ NotFoundError: If the corpus does not exist.
219
+ ConflictError: If a duplicate idempotency key is detected.
220
+ Knowledge2Error: If the API request fails.
221
+ """
222
+ corpus_id = require_str(corpus_id, "corpus_id")
223
+ headers = self._idempotency_headers(idempotency_key)
224
+ data = await self._request(
225
+ "POST",
226
+ f"/v1/corpora/{corpus_id}/tuning-runs:build",
227
+ json={},
228
+ headers=headers,
229
+ request_options=request_options,
230
+ )
231
+ if wait:
232
+ job_id = data.get("build_job_id") or data.get("job_id")
233
+ if job_id:
234
+ await self._wait_for_job(job_id, poll_s=poll_s)
235
+ return self._maybe_validate(data, "TuningRunBuildResponse")
236
+
237
+ async def get_tuning_run(
238
+ self,
239
+ run_id: str,
240
+ request_options: RequestOptions | None = None,
241
+ ) -> TuningRunDetailResponse:
242
+ """Retrieve details of a tuning run.
243
+
244
+ Args:
245
+ run_id: Unique identifier of the tuning run.
246
+
247
+ Returns:
248
+ Detailed tuning run information including status, metrics,
249
+ and configuration.
250
+
251
+ Raises:
252
+ NotFoundError: If the tuning run does not exist.
253
+ Knowledge2Error: If the API request fails.
254
+ """
255
+ run_id = require_str(run_id, "run_id")
256
+ data = await self._request(
257
+ "GET", f"/v1/tuning-runs/{run_id}", request_options=request_options
258
+ )
259
+ return self._maybe_validate(data, "TuningRunDetailResponse")
260
+
261
+ async def get_tuning_run_logs(
262
+ self,
263
+ run_id: str,
264
+ tail: int = 200,
265
+ request_options: RequestOptions | None = None,
266
+ ) -> TuningRunLogsResponse:
267
+ """Retrieve log output from a tuning run.
268
+
269
+ Args:
270
+ run_id: Unique identifier of the tuning run.
271
+ tail: Number of most recent log lines to return.
272
+
273
+ Returns:
274
+ The requested log lines from the tuning run.
275
+
276
+ Raises:
277
+ NotFoundError: If the tuning run does not exist.
278
+ Knowledge2Error: If the API request fails.
279
+ """
280
+ run_id = require_str(run_id, "run_id")
281
+ data = await self._request(
282
+ "GET",
283
+ f"/v1/tuning-runs/{run_id}/logs",
284
+ params={"tail": tail},
285
+ request_options=request_options,
286
+ )
287
+ return self._maybe_validate(data, "TuningRunLogsResponse")
288
+
289
+ async def cancel_tuning_run(
290
+ self,
291
+ run_id: str,
292
+ request_options: RequestOptions | None = None,
293
+ ) -> CancelTuningRunResponse:
294
+ """Cancel a running or pending tuning run.
295
+
296
+ Args:
297
+ run_id: Unique identifier of the tuning run to cancel.
298
+
299
+ Returns:
300
+ Confirmation of the cancellation.
301
+
302
+ Raises:
303
+ NotFoundError: If the tuning run does not exist.
304
+ Knowledge2Error: If the API request fails.
305
+ """
306
+ run_id = require_str(run_id, "run_id")
307
+ data = await self._request(
308
+ "POST", f"/v1/tuning-runs/{run_id}:cancel", request_options=request_options
309
+ )
310
+ return self._maybe_validate(data, "CancelTuningRunResponse")
311
+
312
+ async def promote_tuning_run(
313
+ self,
314
+ run_id: str,
315
+ request_options: RequestOptions | None = None,
316
+ ) -> PromoteResponse:
317
+ """Promote a completed tuning run to create a deployable model.
318
+
319
+ Args:
320
+ run_id: Unique identifier of the tuning run to promote.
321
+
322
+ Returns:
323
+ The promotion result, including the created model ID.
324
+
325
+ Raises:
326
+ NotFoundError: If the tuning run does not exist.
327
+ Knowledge2Error: If the API request fails.
328
+ """
329
+ run_id = require_str(run_id, "run_id")
330
+ data = await self._request(
331
+ "POST", f"/v1/tuning-runs/{run_id}:promote", request_options=request_options
332
+ )
333
+ return self._maybe_validate(data, "PromoteResponse")
334
+
335
+ async def get_eval_run(
336
+ self,
337
+ eval_id: str,
338
+ request_options: RequestOptions | None = None,
339
+ ) -> EvalRunDetailResponse:
340
+ """Retrieve details of an evaluation run.
341
+
342
+ Args:
343
+ eval_id: Unique identifier of the evaluation run.
344
+
345
+ Returns:
346
+ Detailed evaluation run information including metrics and
347
+ status.
348
+
349
+ Raises:
350
+ NotFoundError: If the evaluation run does not exist.
351
+ Knowledge2Error: If the API request fails.
352
+ """
353
+ eval_id = require_str(eval_id, "eval_id")
354
+ data = await self._request(
355
+ "GET", f"/v1/eval-runs/{eval_id}", request_options=request_options
356
+ )
357
+ return self._maybe_validate(data, "EvalRunDetailResponse")