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