kimpton-evalrouter-sdk 0.1.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.
@@ -0,0 +1,92 @@
1
+ """Typed API client. This package is independent of the API and native runners."""
2
+
3
+ from functools import cached_property as _cached_property
4
+
5
+ from . import types
6
+ from ._resources import Catalog as _Catalog
7
+ from ._resources import Connections as _Connections
8
+ from ._resources import Evals as _Evals
9
+ from ._resources import Evaluations as _Evaluations
10
+ from ._resources import Quotes as _Quotes
11
+ from ._resources import Runs as _Runs
12
+ from ._transport import APIError, ClientError, RequestOptions, RequestTimeout, WaitCancelled
13
+ from ._transport import Transport as _Transport
14
+
15
+ __all__ = [
16
+ "Client",
17
+ "EvalRouter",
18
+ "APIError",
19
+ "ClientError",
20
+ "RequestOptions",
21
+ "RequestTimeout",
22
+ "WaitCancelled",
23
+ "types",
24
+ ]
25
+ __version__ = "0.1.0"
26
+
27
+
28
+ class Client(_Transport):
29
+ @_cached_property
30
+ def catalog(self) -> _Catalog:
31
+ return _Catalog(self)
32
+
33
+ @_cached_property
34
+ def connections(self) -> _Connections:
35
+ return _Connections(self)
36
+
37
+ @_cached_property
38
+ def quotes(self) -> _Quotes:
39
+ return _Quotes(self)
40
+
41
+ @_cached_property
42
+ def runs(self) -> _Runs:
43
+ return _Runs(self)
44
+
45
+ @_cached_property
46
+ def evals(self) -> _Evals:
47
+ return _Evals(self)
48
+
49
+ @_cached_property
50
+ def evaluations(self) -> _Evaluations:
51
+ return _Evaluations(self)
52
+
53
+ def run(
54
+ self,
55
+ *,
56
+ model: str,
57
+ eval: str,
58
+ budget_usd: str | float | None = None,
59
+ max_charge_microusd: str | None = None,
60
+ provider: str | None = None,
61
+ coverage: dict | None = None,
62
+ name: str = "Evaluation",
63
+ metadata: dict[str, str] | None = None,
64
+ idempotency_key: str | None = None,
65
+ wait: bool = True,
66
+ timeout: float = 3600,
67
+ poll_interval: float = 2,
68
+ ) -> "types.RunRecord":
69
+ """The canonical call: route `eval` to whichever provider owns it and run it.
70
+
71
+ `model` is a managed route id or `connection:<uuid>`;
72
+ `eval` is `eval://provider/name/version` or `name@version`. Returns the
73
+ terminal run when `wait` is true, otherwise the admitted run.
74
+ """
75
+ from uuid import uuid4
76
+
77
+ body: dict = {"model": model, "eval": eval, "name": name, "metadata": metadata or {}}
78
+ if provider:
79
+ body["provider"] = provider
80
+ if coverage:
81
+ body["coverage"] = coverage
82
+ if max_charge_microusd is not None:
83
+ body["max_charge_microusd"] = max_charge_microusd
84
+ else:
85
+ body["budget_usd"] = str(budget_usd)
86
+ run = self.evaluations.create(body, idempotency_key=idempotency_key or f"run-{uuid4()}")
87
+ if not wait:
88
+ return run
89
+ return self.runs.wait(str(run["id"]), timeout=timeout, poll_interval=poll_interval)
90
+
91
+
92
+ EvalRouter = Client
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,50 @@
1
+ """Validate supported evaluation inputs before issuing a request."""
2
+
3
+ from ._transport import ClientError
4
+
5
+
6
+ def validate_submission(resource, body):
7
+ allowed = (
8
+ {"selection", "model", "coverage", "max_charge_microusd"}
9
+ if resource == "quotes"
10
+ else {
11
+ "model",
12
+ "eval",
13
+ "budget_usd",
14
+ "max_charge_microusd",
15
+ "provider",
16
+ "coverage",
17
+ "name",
18
+ "metadata",
19
+ "split",
20
+ }
21
+ )
22
+ if not isinstance(body, dict) or set(body) - allowed:
23
+ raise ClientError(
24
+ "unsupported_feature", "This submission contains unsupported input fields."
25
+ )
26
+ model = body.get("model")
27
+ if isinstance(model, str) and resource == "evaluations":
28
+ valid = bool(model) and not model.startswith(("checkpoint:", "agent:", "artifact:"))
29
+ elif isinstance(model, dict):
30
+ keys = {"managed": {"kind", "route_id"}, "connection": {"kind", "connection_id"}}
31
+ kind = model.get("kind")
32
+ valid = isinstance(kind, str) and kind in keys and set(model) == keys[kind]
33
+ else:
34
+ valid = False
35
+ if resource == "quotes":
36
+ selection = body.get("selection")
37
+ valid = (
38
+ valid
39
+ and isinstance(selection, dict)
40
+ and (
41
+ set(selection) == {"profile_ids"}
42
+ or "eval" in selection
43
+ and not set(selection) - {"eval", "provider", "split"}
44
+ )
45
+ )
46
+ if not valid:
47
+ raise ClientError(
48
+ "unsupported_feature",
49
+ "Choose an admitted eval and a managed model or checked connection.",
50
+ )
@@ -0,0 +1,101 @@
1
+ """Local waiting and explicit export destinations; neither starts evaluations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import tempfile
7
+ import threading
8
+ import time
9
+ from collections.abc import Callable
10
+ from pathlib import Path
11
+
12
+ from . import types
13
+ from ._transport import ClientError, RequestOptions, Resource, checkpoint, pause, positive
14
+
15
+ TERMINAL = {"completed", "partial", "failed", "cancelled"}
16
+ STATES = TERMINAL | {"queued", "running", "cancelling", "finalizing"}
17
+
18
+
19
+ class RunHelpers(Resource):
20
+ def get(self, run_id: str, *, options: RequestOptions | None = None) -> types.RunRecord:
21
+ raise NotImplementedError
22
+
23
+ def export(
24
+ self,
25
+ run_id: str,
26
+ *,
27
+ params: types.RunsExportParams | None = None,
28
+ options: RequestOptions | None = None,
29
+ ) -> bytes:
30
+ raise NotImplementedError
31
+
32
+ def wait(
33
+ self,
34
+ run_id: str,
35
+ *,
36
+ timeout: float = 3600,
37
+ poll_interval: float = 2,
38
+ cancel_event: threading.Event | None = None,
39
+ on_progress: Callable[[types.RunRecord], None] | None = None,
40
+ ) -> types.RunRecord:
41
+ deadline = time.monotonic() + positive(timeout, "Wait timeout")
42
+ interval = positive(poll_interval, "Poll interval")
43
+ while True:
44
+ remaining = checkpoint(deadline, cancel_event)
45
+ run = self.get(
46
+ run_id, options=RequestOptions(timeout=remaining, cancel_event=cancel_event)
47
+ )
48
+ checkpoint(deadline, cancel_event)
49
+ if run.get("status") not in STATES:
50
+ raise ClientError("invalid_response", "API returned an unknown evaluation state.")
51
+ if on_progress:
52
+ on_progress(run)
53
+ checkpoint(deadline, cancel_event)
54
+ if run["status"] in TERMINAL:
55
+ return run
56
+ pause(interval, deadline, cancel_event)
57
+
58
+ def export_to_file(
59
+ self,
60
+ run_id: str,
61
+ destination: str | Path,
62
+ *,
63
+ params: types.RunsExportParams | None = None,
64
+ options: RequestOptions | None = None,
65
+ overwrite: bool = False,
66
+ ) -> Path:
67
+ path = Path(destination)
68
+ if not overwrite and path.exists():
69
+ raise ClientError("destination_exists", "Export destination already exists.")
70
+ data = self.export(run_id, params=params, options=options)
71
+ return save_export(data, path, overwrite=overwrite)
72
+
73
+
74
+ def save_export(data: bytes, destination: str | Path, *, overwrite: bool = False) -> Path:
75
+ path = Path(destination)
76
+ if not overwrite and path.exists():
77
+ raise ClientError("destination_exists", "Export destination already exists.")
78
+ temporary = None
79
+ try:
80
+ with tempfile.NamedTemporaryFile(
81
+ dir=path.parent, prefix=".kimpton-export-", delete=False
82
+ ) as stream:
83
+ temporary = Path(stream.name)
84
+ stream.write(data)
85
+ stream.flush()
86
+ os.fsync(stream.fileno())
87
+ if overwrite:
88
+ os.replace(temporary, path)
89
+ else:
90
+ os.link(temporary, path)
91
+ temporary.unlink()
92
+ return path
93
+ except FileExistsError:
94
+ raise ClientError("destination_exists", "Export destination already exists.") from None
95
+ except OSError:
96
+ raise ClientError(
97
+ "export_write_failed", "Could not write the export destination."
98
+ ) from None
99
+ finally:
100
+ if temporary is not None:
101
+ temporary.unlink(missing_ok=True)
@@ -0,0 +1,330 @@
1
+ """Generated resource methods. Edit OpenAPI and client-operations.json."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import cast
6
+
7
+ from . import types
8
+ from ._boundary import validate_submission
9
+ from ._helpers import RunHelpers
10
+ from ._transport import RequestOptions, Resource, segment
11
+
12
+
13
+ class Catalog(Resource):
14
+ def evals(
15
+ self,
16
+ *,
17
+ params: types.CatalogEvalsParams | None = None,
18
+ options: RequestOptions | None = None,
19
+ ) -> types.Page_EvalRecord_:
20
+ return cast(
21
+ types.Page_EvalRecord_,
22
+ self._client.request("GET", "/evals", retry=True, options=options, query=params),
23
+ )
24
+
25
+ def eval(self, ref: str, *, options: RequestOptions | None = None) -> types.EvalRecord:
26
+ return cast(
27
+ types.EvalRecord,
28
+ self._client.request("GET", f"/evals/{segment(ref)}", retry=True, options=options),
29
+ )
30
+
31
+ def benchmarks(
32
+ self,
33
+ *,
34
+ params: types.CatalogBenchmarksParams | None = None,
35
+ options: RequestOptions | None = None,
36
+ ) -> types.Page_BenchmarkRecord_:
37
+ return cast(
38
+ types.Page_BenchmarkRecord_,
39
+ self._client.request(
40
+ "GET", "/catalog/benchmarks", retry=True, options=options, query=params
41
+ ),
42
+ )
43
+
44
+ def benchmark(
45
+ self, slug: str, *, options: RequestOptions | None = None
46
+ ) -> types.BenchmarkDetail:
47
+ return cast(
48
+ types.BenchmarkDetail,
49
+ self._client.request(
50
+ "GET", f"/catalog/benchmarks/{segment(slug)}", retry=True, options=options
51
+ ),
52
+ )
53
+
54
+ def models(
55
+ self,
56
+ *,
57
+ params: types.CatalogModelsParams | None = None,
58
+ options: RequestOptions | None = None,
59
+ ) -> types.Page_ManagedRoute_:
60
+ return cast(
61
+ types.Page_ManagedRoute_,
62
+ self._client.request(
63
+ "GET", "/catalog/models", retry=True, options=options, query=params
64
+ ),
65
+ )
66
+
67
+ def execution_options(self, *, options: RequestOptions | None = None) -> types.ExecutionOptions:
68
+ return cast(
69
+ types.ExecutionOptions,
70
+ self._client.request("GET", "/catalog/execution-options", retry=True, options=options),
71
+ )
72
+
73
+ def environments(
74
+ self,
75
+ *,
76
+ params: types.CatalogEnvironmentsParams | None = None,
77
+ options: RequestOptions | None = None,
78
+ ) -> types.Page_EnvironmentRecord_:
79
+ return cast(
80
+ types.Page_EnvironmentRecord_,
81
+ self._client.request(
82
+ "GET", "/catalog/environments", retry=True, options=options, query=params
83
+ ),
84
+ )
85
+
86
+ def environment(
87
+ self, family: str, *, options: RequestOptions | None = None
88
+ ) -> types.EnvironmentFamily:
89
+ return cast(
90
+ types.EnvironmentFamily,
91
+ self._client.request(
92
+ "GET", f"/catalog/environments/{segment(family)}", retry=True, options=options
93
+ ),
94
+ )
95
+
96
+
97
+ class Connections(Resource):
98
+ def list(
99
+ self,
100
+ *,
101
+ params: types.ConnectionsListParams | None = None,
102
+ options: RequestOptions | None = None,
103
+ ) -> types.Page_ConnectionRecord_:
104
+ return cast(
105
+ types.Page_ConnectionRecord_,
106
+ self._client.request("GET", "/connections", retry=True, options=options, query=params),
107
+ )
108
+
109
+ def create(
110
+ self, body: types.NewConnection, *, options: RequestOptions | None = None
111
+ ) -> types.ConnectionRecord:
112
+ return cast(
113
+ types.ConnectionRecord,
114
+ self._client.request("POST", "/connections", retry=False, options=options, body=body),
115
+ )
116
+
117
+ def check(
118
+ self, connection_id: str, *, options: RequestOptions | None = None
119
+ ) -> types.ConnectionRecord:
120
+ return cast(
121
+ types.ConnectionRecord,
122
+ self._client.request(
123
+ "POST", f"/connections/{segment(connection_id)}/check", retry=False, options=options
124
+ ),
125
+ )
126
+
127
+ def update(
128
+ self,
129
+ connection_id: str,
130
+ body: types.UpdateConnection,
131
+ *,
132
+ options: RequestOptions | None = None,
133
+ ) -> types.ConnectionRecord:
134
+ return cast(
135
+ types.ConnectionRecord,
136
+ self._client.request(
137
+ "PATCH",
138
+ f"/connections/{segment(connection_id)}",
139
+ retry=False,
140
+ options=options,
141
+ body=body,
142
+ ),
143
+ )
144
+
145
+ def disable(
146
+ self, connection_id: str, *, options: RequestOptions | None = None
147
+ ) -> types.OKResponse:
148
+ return cast(
149
+ types.OKResponse,
150
+ self._client.request(
151
+ "DELETE", f"/connections/{segment(connection_id)}", retry=False, options=options
152
+ ),
153
+ )
154
+
155
+
156
+ class Quotes(Resource):
157
+ def create(
158
+ self, body: types.NewQuote, *, options: RequestOptions | None = None
159
+ ) -> types.QuoteRecord:
160
+ validate_submission("quotes", body)
161
+ return cast(
162
+ types.QuoteRecord,
163
+ self._client.request("POST", "/quotes", retry=False, options=options, body=body),
164
+ )
165
+
166
+ def get(self, quote_id: str, *, options: RequestOptions | None = None) -> types.QuoteRecord:
167
+ return cast(
168
+ types.QuoteRecord,
169
+ self._client.request(
170
+ "GET", f"/quotes/{segment(quote_id)}", retry=True, options=options
171
+ ),
172
+ )
173
+
174
+
175
+ class Runs(RunHelpers):
176
+ def create(
177
+ self, body: types.NewRun, *, idempotency_key: str, options: RequestOptions | None = None
178
+ ) -> types.RunRecord:
179
+ return cast(
180
+ types.RunRecord,
181
+ self._client.request(
182
+ "POST",
183
+ "/runs",
184
+ retry=True,
185
+ options=options,
186
+ body=body,
187
+ idempotency_key=idempotency_key,
188
+ ),
189
+ )
190
+
191
+ def list(
192
+ self, *, params: types.RunsListParams | None = None, options: RequestOptions | None = None
193
+ ) -> types.Page_RunRecord_:
194
+ return cast(
195
+ types.Page_RunRecord_,
196
+ self._client.request("GET", "/runs", retry=True, options=options, query=params),
197
+ )
198
+
199
+ def get(self, run_id: str, *, options: RequestOptions | None = None) -> types.RunRecord:
200
+ return cast(
201
+ types.RunRecord,
202
+ self._client.request("GET", f"/runs/{segment(run_id)}", retry=True, options=options),
203
+ )
204
+
205
+ def receipt(self, run_id: str, *, options: RequestOptions | None = None) -> types.RunReceipt:
206
+ return cast(
207
+ types.RunReceipt,
208
+ self._client.request(
209
+ "GET", f"/runs/{segment(run_id)}/receipt", retry=True, options=options
210
+ ),
211
+ )
212
+
213
+ def executions(
214
+ self,
215
+ run_id: str,
216
+ *,
217
+ params: types.RunsExecutionsParams | None = None,
218
+ options: RequestOptions | None = None,
219
+ ) -> types.SandboxExecutionPage:
220
+ return cast(
221
+ types.SandboxExecutionPage,
222
+ self._client.request(
223
+ "GET",
224
+ f"/runs/{segment(run_id)}/executions",
225
+ retry=True,
226
+ options=options,
227
+ query=params,
228
+ ),
229
+ )
230
+
231
+ def events(
232
+ self,
233
+ run_id: str,
234
+ *,
235
+ params: types.RunsEventsParams | None = None,
236
+ options: RequestOptions | None = None,
237
+ ) -> types.EventPage:
238
+ return cast(
239
+ types.EventPage,
240
+ self._client.request(
241
+ "GET", f"/runs/{segment(run_id)}/events", retry=True, options=options, query=params
242
+ ),
243
+ )
244
+
245
+ def cancel(self, run_id: str, *, options: RequestOptions | None = None) -> types.RunRecord:
246
+ return cast(
247
+ types.RunRecord,
248
+ self._client.request(
249
+ "POST", f"/runs/{segment(run_id)}/cancel", retry=True, options=options
250
+ ),
251
+ )
252
+
253
+ def results(
254
+ self,
255
+ run_id: str,
256
+ *,
257
+ params: types.RunsResultsParams | None = None,
258
+ options: RequestOptions | None = None,
259
+ ) -> types.ResultRecord:
260
+ return cast(
261
+ types.ResultRecord,
262
+ self._client.request(
263
+ "GET", f"/runs/{segment(run_id)}/results", retry=True, options=options, query=params
264
+ ),
265
+ )
266
+
267
+ def export(
268
+ self,
269
+ run_id: str,
270
+ *,
271
+ params: types.RunsExportParams | None = None,
272
+ options: RequestOptions | None = None,
273
+ ) -> bytes:
274
+ return cast(
275
+ bytes,
276
+ self._client.request(
277
+ "GET",
278
+ f"/runs/{segment(run_id)}/export",
279
+ retry=True,
280
+ options=options,
281
+ query=params,
282
+ binary=True,
283
+ ),
284
+ )
285
+
286
+
287
+ class Evals(Resource):
288
+ def list(
289
+ self, *, params: types.EvalsListParams | None = None, options: RequestOptions | None = None
290
+ ) -> types.Page_EvalRecord_:
291
+ return cast(
292
+ types.Page_EvalRecord_,
293
+ self._client.request("GET", "/evals", retry=True, options=options, query=params),
294
+ )
295
+
296
+ def get(self, ref: str, *, options: RequestOptions | None = None) -> types.EvalRecord:
297
+ return cast(
298
+ types.EvalRecord,
299
+ self._client.request("GET", f"/evals/{segment(ref)}", retry=True, options=options),
300
+ )
301
+
302
+
303
+ class Evaluations(Resource):
304
+ def create(
305
+ self,
306
+ body: types.NewEvaluation,
307
+ *,
308
+ idempotency_key: str,
309
+ options: RequestOptions | None = None,
310
+ ) -> types.RunRecord:
311
+ validate_submission("evaluations", body)
312
+ return cast(
313
+ types.RunRecord,
314
+ self._client.request(
315
+ "POST",
316
+ "/evaluations",
317
+ retry=True,
318
+ options=options,
319
+ body=body,
320
+ idempotency_key=idempotency_key,
321
+ ),
322
+ )
323
+
324
+ def get(self, evaluation_id: str, *, options: RequestOptions | None = None) -> types.RunRecord:
325
+ return cast(
326
+ types.RunRecord,
327
+ self._client.request(
328
+ "GET", f"/evaluations/{segment(evaluation_id)}", retry=True, options=options
329
+ ),
330
+ )