kimpton-evalrouter-sdk 0.1.0__tar.gz

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,29 @@
1
+ Kimpton EvalRouter SDK License
2
+
3
+ Copyright (c) 2026 Kimpton. All rights reserved.
4
+
5
+ Permission is granted to download, install, execute, and make internal copies of
6
+ this SDK and its documentation solely to access the EvalRouter service and to
7
+ integrate that access into applications you own or control, subject to your
8
+ applicable EvalRouter service agreement and authorization.
9
+
10
+ This is proprietary software, not an open-source license. Except for the limited
11
+ permission above or as required by applicable law, no permission is granted to
12
+ modify, redistribute, sublicense, sell, or use this software to provide a
13
+ competing service. Preserve this license and all copyright notices in permitted
14
+ copies. All rights not expressly granted are reserved.
15
+
16
+ This license covers only the distributed SDK, CLI, and accompanying
17
+ documentation. It grants no rights to EvalRouter backend or service source,
18
+ benchmarks, datasets, models, credentials, trademarks, or other excluded material.
19
+ Third-party dependencies remain subject to their own licenses.
20
+
21
+ Downloading this software does not create an account, grant service access,
22
+ provide credits, or waive service charges. Access remains subject to separate
23
+ account, workspace, authorization, and billing requirements.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
27
+ PURPOSE, AND NONINFRINGEMENT. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW,
28
+ THE COPYRIGHT HOLDERS SHALL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
29
+ LIABILITY ARISING FROM THE SOFTWARE OR ITS USE.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: kimpton-evalrouter-sdk
3
+ Version: 0.1.0
4
+ Summary: Typed Kimpton evaluation client and command-line interface
5
+ License-Expression: LicenseRef-Kimpton-EvalRouter-SDK
6
+ License-File: LICENSE
7
+ Requires-Python: <3.14,>=3.12
8
+ Requires-Dist: httpx==0.28.1
9
+ Description-Content-Type: text/markdown
10
+
11
+ # EvalRouter Python SDK and CLI
12
+
13
+ Python SDK and CLI for Python 3.12–3.13, distributed under the proprietary
14
+ [Kimpton EvalRouter SDK License](LICENSE). Discover evaluations and models,
15
+ submit bounded evaluation runs, inspect results, and export reports.
16
+
17
+ Install with `python -m pip install kimpton-evalrouter-sdk==0.1.0`. The same
18
+ installation supplies `kimpton_evalrouter` and the `evalrouter` console command.
19
+ No repository checkout or editable installation is required.
20
+
21
+ Set `EVALROUTER_BASE_URL` to the configured API origin, `EVALROUTER_API_KEY` to a
22
+ workspace API key, and `EVALROUTER_WORKSPACE_ID` to its workspace. The public API
23
+ origin is `https://api.evalrouter.ai`. Package download does not grant service
24
+ access. While website access is restricted, obtain an invitation from your
25
+ EvalRouter operator, then create and fund the account in the web application.
26
+ Never paste credentials into command arguments, source files, logs, or browser
27
+ code. HTTPS is required outside explicit loopback development (`--allow-http`).
28
+
29
+ ```python
30
+ from kimpton_evalrouter import Client
31
+
32
+ with Client() as client:
33
+ available = client.evals.list()
34
+ # Choose an admitted exact eval reference and compatible model from discovery.
35
+ run = client.run(
36
+ model="YOUR_MANAGED_MODEL",
37
+ eval="YOUR_EXACT_EVAL_REFERENCE",
38
+ budget_usd="1.00",
39
+ idempotency_key="my-durable-evaluation-request",
40
+ )
41
+ result = client.runs.results(run["id"])
42
+ client.runs.export_to_file(run["id"], "result.json", params={"format": "json"})
43
+ ```
44
+
45
+ The budget is a cap, not a price guarantee or promise that a particular evaluation
46
+ fits. Check catalog availability and coverage. Managed routes and checked
47
+ `connection:<id>` endpoints have distinct billing bases. A quote does not execute
48
+ work. Reuse an idempotency key for retries of the same authorized submission.
49
+
50
+ For explicit quote review, create a documented `NewQuote` JSON object in
51
+ `quote.json`, then use the returned quote ID:
52
+
53
+ ```sh
54
+ evalrouter catalog --json
55
+ evalrouter catalog --models --json
56
+ evalrouter quote --config quote.json --json
57
+ evalrouter run --quote YOUR_QUOTE_ID --idempotency-key my-durable-request --wait --json
58
+ evalrouter results YOUR_RUN_ID --json
59
+ evalrouter export YOUR_RUN_ID --format json --output result.json
60
+ ```
61
+
62
+ The supported CLI commands are `catalog`, `quote`, `run`, `status`, `wait`,
63
+ `cancel`, `results`, `export`, and `connections list/create/check/update/disable`.
64
+ Run a command with `--help` for flags. Connection credentials are read through
65
+ `--key-env`, never a credential argument. JSON input can use `--config -` for
66
+ stdin. `--json` prints one result on stdout; progress is on stderr. Exit status
67
+ 0 means success, 1 an API/transport or failed-run error, 2 invalid input or a
68
+ rejected request, and 4 a partial/cancelled waited run. Interrupting a local wait
69
+ does not cancel server work. Use `cancel` explicitly.
70
+
71
+ The Python API exposes `Client` (`EvalRouter` alias), `catalog`, `evals`,
72
+ `connections`, `quotes`, `evaluations`, `runs`, their request/response types, and
73
+ bounded wait/export helpers. `catalog.evals`/`catalog.eval` retain the discovery
74
+ aliases. Errors are `ClientError`, `APIError`, `RequestTimeout` and `WaitCancelled`.
75
+ Use `RequestOptions` for request deadlines and local cancellation. JSON/CSV/HTML
76
+ exports require an explicit destination and refuse overwrite unless requested.
77
+ Account setup and key creation are separate web steps; the CLI uses an existing
78
+ workspace API key.
@@ -0,0 +1,68 @@
1
+ # EvalRouter Python SDK and CLI
2
+
3
+ Python SDK and CLI for Python 3.12–3.13, distributed under the proprietary
4
+ [Kimpton EvalRouter SDK License](LICENSE). Discover evaluations and models,
5
+ submit bounded evaluation runs, inspect results, and export reports.
6
+
7
+ Install with `python -m pip install kimpton-evalrouter-sdk==0.1.0`. The same
8
+ installation supplies `kimpton_evalrouter` and the `evalrouter` console command.
9
+ No repository checkout or editable installation is required.
10
+
11
+ Set `EVALROUTER_BASE_URL` to the configured API origin, `EVALROUTER_API_KEY` to a
12
+ workspace API key, and `EVALROUTER_WORKSPACE_ID` to its workspace. The public API
13
+ origin is `https://api.evalrouter.ai`. Package download does not grant service
14
+ access. While website access is restricted, obtain an invitation from your
15
+ EvalRouter operator, then create and fund the account in the web application.
16
+ Never paste credentials into command arguments, source files, logs, or browser
17
+ code. HTTPS is required outside explicit loopback development (`--allow-http`).
18
+
19
+ ```python
20
+ from kimpton_evalrouter import Client
21
+
22
+ with Client() as client:
23
+ available = client.evals.list()
24
+ # Choose an admitted exact eval reference and compatible model from discovery.
25
+ run = client.run(
26
+ model="YOUR_MANAGED_MODEL",
27
+ eval="YOUR_EXACT_EVAL_REFERENCE",
28
+ budget_usd="1.00",
29
+ idempotency_key="my-durable-evaluation-request",
30
+ )
31
+ result = client.runs.results(run["id"])
32
+ client.runs.export_to_file(run["id"], "result.json", params={"format": "json"})
33
+ ```
34
+
35
+ The budget is a cap, not a price guarantee or promise that a particular evaluation
36
+ fits. Check catalog availability and coverage. Managed routes and checked
37
+ `connection:<id>` endpoints have distinct billing bases. A quote does not execute
38
+ work. Reuse an idempotency key for retries of the same authorized submission.
39
+
40
+ For explicit quote review, create a documented `NewQuote` JSON object in
41
+ `quote.json`, then use the returned quote ID:
42
+
43
+ ```sh
44
+ evalrouter catalog --json
45
+ evalrouter catalog --models --json
46
+ evalrouter quote --config quote.json --json
47
+ evalrouter run --quote YOUR_QUOTE_ID --idempotency-key my-durable-request --wait --json
48
+ evalrouter results YOUR_RUN_ID --json
49
+ evalrouter export YOUR_RUN_ID --format json --output result.json
50
+ ```
51
+
52
+ The supported CLI commands are `catalog`, `quote`, `run`, `status`, `wait`,
53
+ `cancel`, `results`, `export`, and `connections list/create/check/update/disable`.
54
+ Run a command with `--help` for flags. Connection credentials are read through
55
+ `--key-env`, never a credential argument. JSON input can use `--config -` for
56
+ stdin. `--json` prints one result on stdout; progress is on stderr. Exit status
57
+ 0 means success, 1 an API/transport or failed-run error, 2 invalid input or a
58
+ rejected request, and 4 a partial/cancelled waited run. Interrupting a local wait
59
+ does not cancel server work. Use `cancel` explicitly.
60
+
61
+ The Python API exposes `Client` (`EvalRouter` alias), `catalog`, `evals`,
62
+ `connections`, `quotes`, `evaluations`, `runs`, their request/response types, and
63
+ bounded wait/export helpers. `catalog.evals`/`catalog.eval` retain the discovery
64
+ aliases. Errors are `ClientError`, `APIError`, `RequestTimeout` and `WaitCancelled`.
65
+ Use `RequestOptions` for request deadlines and local cancellation. JSON/CSV/HTML
66
+ exports require an explicit destination and refuse overwrite unless requested.
67
+ Account setup and key creation are separate web steps; the CLI uses an existing
68
+ workspace API key.
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kimpton-evalrouter-sdk"
7
+ version = "0.1.0"
8
+ description = "Typed Kimpton evaluation client and command-line interface"
9
+ readme = "README.md"
10
+ license = "LicenseRef-Kimpton-EvalRouter-SDK"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.12,<3.14"
13
+ dependencies = ["httpx==0.28.1"]
14
+
15
+ [project.scripts]
16
+ evalrouter = "kimpton_evalrouter.cli:main"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["src/kimpton_evalrouter"]
@@ -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)