zentest-sdk 0.4.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.
- zentest_sdk-0.4.0/CHANGELOG.md +109 -0
- zentest_sdk-0.4.0/MANIFEST.in +6 -0
- zentest_sdk-0.4.0/PKG-INFO +243 -0
- zentest_sdk-0.4.0/README.md +200 -0
- zentest_sdk-0.4.0/RELEASING.md +118 -0
- zentest_sdk-0.4.0/pyproject.toml +66 -0
- zentest_sdk-0.4.0/setup.cfg +4 -0
- zentest_sdk-0.4.0/zentest/__init__.py +36 -0
- zentest_sdk-0.4.0/zentest/_checks.py +1275 -0
- zentest_sdk-0.4.0/zentest/_version.py +3 -0
- zentest_sdk-0.4.0/zentest/adapters.py +86 -0
- zentest_sdk-0.4.0/zentest/assertions.py +159 -0
- zentest_sdk-0.4.0/zentest/cli.py +580 -0
- zentest_sdk-0.4.0/zentest/client.py +375 -0
- zentest_sdk-0.4.0/zentest/commands.py +269 -0
- zentest_sdk-0.4.0/zentest/declarative.py +249 -0
- zentest_sdk-0.4.0/zentest/errors.py +60 -0
- zentest_sdk-0.4.0/zentest/guard.py +237 -0
- zentest_sdk-0.4.0/zentest/judges.py +313 -0
- zentest_sdk-0.4.0/zentest/metrics/__init__.py +28 -0
- zentest_sdk-0.4.0/zentest/metrics/base.py +85 -0
- zentest_sdk-0.4.0/zentest/metrics/deterministic.py +192 -0
- zentest_sdk-0.4.0/zentest/metrics/judged.py +118 -0
- zentest_sdk-0.4.0/zentest/metrics/retrieval.py +149 -0
- zentest_sdk-0.4.0/zentest/offline.py +279 -0
- zentest_sdk-0.4.0/zentest/otel.py +162 -0
- zentest_sdk-0.4.0/zentest/py.typed +0 -0
- zentest_sdk-0.4.0/zentest/pytest_plugin.py +97 -0
- zentest_sdk-0.4.0/zentest/redteam.py +87 -0
- zentest_sdk-0.4.0/zentest/runner.py +315 -0
- zentest_sdk-0.4.0/zentest/simulate.py +170 -0
- zentest_sdk-0.4.0/zentest/sync.py +66 -0
- zentest_sdk-0.4.0/zentest/tracing.py +423 -0
- zentest_sdk-0.4.0/zentest/types.py +112 -0
- zentest_sdk-0.4.0/zentest_sdk.egg-info/PKG-INFO +243 -0
- zentest_sdk-0.4.0/zentest_sdk.egg-info/SOURCES.txt +38 -0
- zentest_sdk-0.4.0/zentest_sdk.egg-info/dependency_links.txt +1 -0
- zentest_sdk-0.4.0/zentest_sdk.egg-info/entry_points.txt +5 -0
- zentest_sdk-0.4.0/zentest_sdk.egg-info/requires.txt +22 -0
- zentest_sdk-0.4.0/zentest_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `zentest-sdk`, the Python SDK for the Zentest platform. The SDK implements the
|
|
4
|
+
[SDK contract v1](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md); every release so far is
|
|
5
|
+
additive within v1, so code written against an earlier release keeps working.
|
|
6
|
+
|
|
7
|
+
Versions follow [Semantic Versioning](https://semver.org/). Until 1.0, a minor release may add to the API but does not
|
|
8
|
+
remove or change what the contract marks as public (the adapter protocol, the `Verdict` shape and runner parity).
|
|
9
|
+
Release steps are in [RELEASING.md](https://github.com/ITHyd/Zentest/blob/main/sdk/RELEASING.md).
|
|
10
|
+
|
|
11
|
+
## 0.4.0 (2026-10-19)
|
|
12
|
+
|
|
13
|
+
The first release published to PyPI (`pip install zentest-sdk`), built, tested and uploaded by an Azure Pipelines
|
|
14
|
+
release pipeline. Local evaluation: the SDK becomes a testing library
|
|
15
|
+
you can use without the platform, and that records on it when you want. Everything in 0.3.0 is unchanged.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `zentest.metrics`: a metrics library. Deterministic checks (`PIILeak`, `Forbidden`, `Contains`, `Regex`, `ExactMatch`,
|
|
19
|
+
`Format`, `ReadingLevel`, `Disclaimer`, `Escalation`, `ToolCorrectness`, `JudgeGaming`, and any platform check through
|
|
20
|
+
`Check`), deterministic retrieval metrics (`Groundedness`, `ContextPrecision`, `ContextRecall`) and judge metrics
|
|
21
|
+
(`Faithfulness`, `AnswerRelevancy`, `ContextRelevance` (the RAG triad), `Hallucination`, `Toxicity`, `Bias`, `GEval`).
|
|
22
|
+
Every score records `model_snapshot`, `prompt_hash` and `judge_version`.
|
|
23
|
+
- `zentest.judges`: the judge for judge metrics is pluggable. `PlatformJudge` scores through the platform's gateway
|
|
24
|
+
(`POST /api/sdk/evaluate`), `LocalJudge` calls Claude (`pip install "zentest-sdk[judge]"`) or an OpenRouter model with
|
|
25
|
+
your key, and `HeuristicJudge` makes no model call. Pick one with `judge=` or `ZENTEST_JUDGE`.
|
|
26
|
+
- `assert_test`, `evaluate` and `EvalReport`, plus a pytest plugin (auto-registered): a summary table,
|
|
27
|
+
`--zentest-report` JSON, `--zentest-judge`, and `--zentest-sync` to record the session as a platform run.
|
|
28
|
+
- `zentest test FILE`: declarative test files (cases, assertions, a Python or HTTP target, `repeats`, `pass_rate`),
|
|
29
|
+
with `--junit`, `--json-file`, `--baseline` (a case that passed before and fails now exits 1) and `--sync`.
|
|
30
|
+
`zentest init` scaffolds a test file and a pytest file.
|
|
31
|
+
- `zt.redteam.scan` and `zentest redteam`: generate the product's red-team suite from the platform's attack catalogue,
|
|
32
|
+
run it against your agent, and get the per-class and OWASP report.
|
|
33
|
+
- `zentest.guard`: runtime guardrails (prompt injection, PII in the input, topic denylist and any deterministic metric
|
|
34
|
+
on the output) with `block`, `redact`, `raise` or `log`, recorded as `guardrail` spans.
|
|
35
|
+
- `zentest.simulate`: seeded multi-turn conversations with a persona and a goal, scored per turn (the platform's
|
|
36
|
+
simulator script).
|
|
37
|
+
- `zentest.sync`: record local evaluations as a platform run (`POST /api/sdk/suites` finds or creates the suite).
|
|
38
|
+
|
|
39
|
+
## 0.3.0 (2026-10-17)
|
|
40
|
+
|
|
41
|
+
Prepared for PyPI but not published; 0.1.0 to 0.3.0 were installed from the repository.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- `gates.run` takes the run options `start` already had: `connection_id` (the HTTP connection an endpoint run calls when
|
|
45
|
+
the environment has several), `corpus` with `corpus_data_source` (UK-31: the retrieval corpus, scanned when the run
|
|
46
|
+
starts so contaminated cases are skipped), `osa_scope` (UK-10) and `trigger` (`"api"` or `"ci"`). `connection_id`
|
|
47
|
+
with an in-process `agent` raises `ValueError` before anything is created.
|
|
48
|
+
- `start` gained `connection_id` (several HTTP connections per environment; the run pins the one it calls) and `corpus`
|
|
49
|
+
with `corpus_data_source`.
|
|
50
|
+
- `Verdict.contamination` (UK-31): the cases found in the app's retrieval corpus, which were excluded, kept or skipped.
|
|
51
|
+
- The SDK runner also submits results on a time interval, not only every `batch_size` trials: `flush_every_s` on
|
|
52
|
+
`gates.run` and `Zentest(...)`, or `ZENTEST_FLUSH_EVERY_S` (default 30 s; `0` sends full batches only), so a slow
|
|
53
|
+
app's run shows progress in the console as it goes (SDK-07).
|
|
54
|
+
- CLI: `zentest run --junit PATH` writes the result as JUnit XML for CI test reports; a suite file can pin a
|
|
55
|
+
`connection`; a metric with no counted trials prints "no counted trials" instead of 0%.
|
|
56
|
+
- Packaging: published metadata (readme, licence, authors, classifiers, project URLs), a PEP 561 `py.typed` marker, this
|
|
57
|
+
changelog and a release workflow with PyPI trusted publishing.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- The version is written once, in `zentest/_version.py`; `pyproject.toml`, `zentest.__version__`, the `User-Agent` and
|
|
61
|
+
the SDK-19 minimum-version check all read it.
|
|
62
|
+
- The offline check library (`zentest/_checks.py`, a copy of the platform's) follows the platform: it gains the
|
|
63
|
+
special-category data check, and the platform-only RAG, language and attack checks are refused offline as a
|
|
64
|
+
configuration error instead of failing to import.
|
|
65
|
+
|
|
66
|
+
## 0.2.0 (2026-10-15)
|
|
67
|
+
|
|
68
|
+
Completes contract v1 (SDK-01 to SDK-20). Installed from the repository (`pip install -e sdk`).
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
- Tracing (SDK-15): `@zt.traced` and `with zt.span(...)` record zentest.span.v1 spans, one trace per trial, uploaded and
|
|
72
|
+
linked to the trial's result. A run whose agent records no spans warns when it starts (`gates.last_run.traced`).
|
|
73
|
+
- OpenTelemetry (SDK-16): `zentest.otel` (extra `otel`) exports existing GenAI or OpenInference spans and stamps them
|
|
74
|
+
with the trial's ids.
|
|
75
|
+
- Offline runs (SDK-08): `zentest export` and `zentest run --offline` score deterministic metrics with the platform's
|
|
76
|
+
own check library and decision rule; skipped judge metrics make the result `incomplete` (exit 2) unless
|
|
77
|
+
`--allow-incomplete`.
|
|
78
|
+
- The zentest CLI (SDK-08, SDK-09): `zentest run gate.zt` gates a CI pipeline on a platform verdict (exit 0 passed,
|
|
79
|
+
1 not passed, 2 no verdict), with `--json`, `--json-file`, `--summary`, `zentest cancel`, the REQ-077 override chain
|
|
80
|
+
and OSA scope conditions (UK-10); a replayed idempotency key for another gate exits 2.
|
|
81
|
+
- Minimum-version check (SDK-19): one `GET /api/version` before the first call; `VersionError` with the upgrade command.
|
|
82
|
+
- Typed errors for every refusal: `ResidencyError`, `IdempotencyConflictError`, `MissingInputsError` (`.missing`),
|
|
83
|
+
`VersionError`, alongside the earlier ones.
|
|
84
|
+
- Per-trial `usage` and `metadata` (SDK-04), cut down rather than refused when too large; `packs` recorded on the run
|
|
85
|
+
and returned as `Verdict.packs` (SDK-10); `cancel` returns a `CancelResult` (SDK-18).
|
|
86
|
+
- Version attribution (AI-49): `AgentResponse(model=..., prompt_version=..., config_version=...)` and
|
|
87
|
+
`Verdict.attribution`.
|
|
88
|
+
- Client methods for the catalogue and case authoring (`products`, `versions`, `suites`, `suite_cases`, `add_cases`,
|
|
89
|
+
`update_case`), `results`, `power`, `override`, `span_schema`, `upload_spans`, `instrumentation`, `export_suite`,
|
|
90
|
+
`submit_corpus` and `submit_session`.
|
|
91
|
+
- Extras `otel`, `yaml` and `schema`; the core dependency stays `httpx` alone (SDK-20).
|
|
92
|
+
- Examples: `quickstart.py` and `mock_app.py` report attribution; `http_app_gate.py` wraps a login-protected chat API.
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
- `zt.wait()` accepts a `RunHandle` as well as a run id; `zt.cancel(run)` and `RunCancelledError` (SDK-17, SDK-18).
|
|
96
|
+
|
|
97
|
+
## 0.1.0 (2026-10-13)
|
|
98
|
+
|
|
99
|
+
First version, installed from the repository.
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
- `Zentest` client: API token and pinned residency region on every call, retries on 5xx and connection errors.
|
|
103
|
+
- Adapter protocol (SDK-02 to SDK-05): the agent's shape is detected from its type hint (`str`, `list[dict]` or
|
|
104
|
+
`AgentInput`), sync or async.
|
|
105
|
+
- The in-process SDK runner with batched, idempotent result submission, and the manual loop (`start`, `cases`,
|
|
106
|
+
`submit`, `finish`).
|
|
107
|
+
- `gates.run` with required `repeats` and the stated `deterministic` mode (SDK-10 to SDK-13), the typed `Verdict` and
|
|
108
|
+
the contract errors.
|
|
109
|
+
- Examples: `mock_app.py` and `quickstart.py` (both runners).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# The sdist is public (PyPI): the package (from pyproject.toml), the README (the PyPI page), the changelog and the
|
|
2
|
+
# release notes. The tests and examples stay in the repository: they name non-public apps and deployments, and the
|
|
3
|
+
# README links the examples on GitHub.
|
|
4
|
+
include CHANGELOG.md RELEASING.md
|
|
5
|
+
prune tests
|
|
6
|
+
prune examples
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zentest-sdk
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Zentest SDK: test your AI app in-process and gate releases on a judged verdict (SDK contract v1)
|
|
5
|
+
Author: Nxzen
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/ITHyd/Zentest
|
|
8
|
+
Project-URL: Repository, https://github.com/ITHyd/Zentest
|
|
9
|
+
Project-URL: Documentation, https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md
|
|
10
|
+
Project-URL: Changelog, https://github.com/ITHyd/Zentest/blob/main/sdk/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/ITHyd/Zentest/issues
|
|
12
|
+
Keywords: llm,evaluation,testing,ai,release-gate,rag,opentelemetry
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Provides-Extra: otel
|
|
28
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == "otel"
|
|
29
|
+
Provides-Extra: yaml
|
|
30
|
+
Requires-Dist: pyyaml>=6; extra == "yaml"
|
|
31
|
+
Provides-Extra: schema
|
|
32
|
+
Requires-Dist: jsonschema>=4; extra == "schema"
|
|
33
|
+
Provides-Extra: judge
|
|
34
|
+
Requires-Dist: anthropic>=0.100; extra == "judge"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
37
|
+
Requires-Dist: pyyaml>=6; extra == "dev"
|
|
38
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == "dev"
|
|
39
|
+
Requires-Dist: jsonschema>=4; extra == "dev"
|
|
40
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
41
|
+
Requires-Dist: twine>=6.1; extra == "dev"
|
|
42
|
+
Requires-Dist: packaging>=23; extra == "dev"
|
|
43
|
+
|
|
44
|
+
# zentest-sdk
|
|
45
|
+
|
|
46
|
+
The Python SDK for [Zentest](https://github.com/ITHyd/Zentest), Nxzen's platform for testing AI apps (chatbots, RAG,
|
|
47
|
+
voice agents). Run your app's test suite in your own process, or let the platform call its HTTP endpoint, and gate the
|
|
48
|
+
release on a judged verdict with a confidence interval. The behaviour is fixed by the
|
|
49
|
+
[SDK contract v1](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md) (SDK-01 to SDK-20).
|
|
50
|
+
A TypeScript SDK for Node 18+ implements the same contract in [`sdk-ts/`](https://github.com/ITHyd/Zentest/tree/main/sdk-ts)
|
|
51
|
+
(contract §2b); offline runs and the CLI are Python only.
|
|
52
|
+
You need a Zentest workspace and an API token (console: Settings → API tokens).
|
|
53
|
+
|
|
54
|
+
**Links:** [contract](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md) ·
|
|
55
|
+
[changelog](https://github.com/ITHyd/Zentest/blob/main/sdk/CHANGELOG.md) ·
|
|
56
|
+
[CI gate guide](https://github.com/ITHyd/Zentest/blob/main/docs/specs/ci-gate.md) ·
|
|
57
|
+
[span format](https://github.com/ITHyd/Zentest/blob/main/docs/specs/zentest-span-v1.md) ·
|
|
58
|
+
[examples](https://github.com/ITHyd/Zentest/tree/main/sdk/examples)
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
Python 3.11 or later.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install zentest-sdk # core: httpx only
|
|
66
|
+
pip install "zentest-sdk[otel]" # + OpenTelemetry exporter (zentest.otel)
|
|
67
|
+
pip install "zentest-sdk[yaml]" # + YAML suite files for the CLI
|
|
68
|
+
pip install "zentest-sdk[schema]" # + offline runs of a format metric with a JSON Schema
|
|
69
|
+
pip install "zentest-sdk[judge]" # + the local Claude judge (open-core mode, no platform)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Install it into a virtual environment, then check it: `zentest --version` prints the installed version. Add only
|
|
73
|
+
the extras you need.
|
|
74
|
+
|
|
75
|
+
Working on the SDK itself? Install it from a checkout of the repository instead: `pip install -e sdk` (extras as
|
|
76
|
+
`pip install -e "sdk[otel,yaml]"`, and `sdk[dev]` for the tests).
|
|
77
|
+
|
|
78
|
+
**Dependencies (SDK-20).** The client is installed into your application's environment, so the floor is thin: `httpx`
|
|
79
|
+
and nothing else. Evaluation libraries stay on the platform; the deterministic checks used by offline runs are standard
|
|
80
|
+
library only. Extras: `otel` (`opentelemetry-sdk`), `yaml` (`pyyaml`), `schema` (`jsonschema`). The package ships type
|
|
81
|
+
hints (`py.typed`).
|
|
82
|
+
|
|
83
|
+
## Evaluate in your own tests (0.4.0)
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from zentest import LLMTestCase, assert_test
|
|
87
|
+
from zentest.metrics import PIILeak, Faithfulness, AnswerRelevancy, GEval
|
|
88
|
+
|
|
89
|
+
def test_burst_pipe():
|
|
90
|
+
case = LLMTestCase(input="Is a burst pipe covered?", actual_output=app(q), retrieval_context=chunks)
|
|
91
|
+
assert_test(case, [PIILeak(), Faithfulness(threshold=0.8), AnswerRelevancy(),
|
|
92
|
+
GEval("Plain English", "Short answer a customer understands")])
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- `pytest` prints a Zentest summary table; `--zentest-sync --zentest-product P --zentest-version V` records the session
|
|
96
|
+
as a platform run. Deterministic metrics always run before judge metrics.
|
|
97
|
+
- Judges: `ZENTEST_JUDGE=platform` (the platform's calibrated judge, needs `ZENTEST_API_KEY`), `local` (Claude with
|
|
98
|
+
`ANTHROPIC_API_KEY`, or OpenRouter with `OPENROUTER_API_KEY`) or `heuristic` (no model call). A local judge is not
|
|
99
|
+
calibrated: gate releases on the platform.
|
|
100
|
+
- `zentest init` then `zentest test zentest.yaml`: the same checks as a YAML file in Git (`--baseline`, `--junit`,
|
|
101
|
+
`--sync`).
|
|
102
|
+
- `zentest redteam --product P --version V --agent app:answer`: the product's red-team suite against your agent.
|
|
103
|
+
- `zentest.guard.Guard(...).protect(answer)`: block prompt injection and personal data in production.
|
|
104
|
+
- `zentest.simulate(answer, persona=..., goal=...)`: a seeded multi-turn conversation, scored per turn.
|
|
105
|
+
|
|
106
|
+
A worked example on a real app lives in the repository under `sdk/examples` (it runs with no network in mock mode).
|
|
107
|
+
|
|
108
|
+
## Quick start (release gate)
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from zentest import Zentest, AgentInput, AgentResponse
|
|
112
|
+
|
|
113
|
+
def agent(x: AgentInput) -> AgentResponse: # or (user_message: str) -> str, or (messages: list[dict]) -> str
|
|
114
|
+
reply, chunks = my_app.answer(x.messages[-1]["content"])
|
|
115
|
+
return AgentResponse(output=reply, retrieval_context=chunks,
|
|
116
|
+
model="claude-sonnet-4.5", prompt_version="r12", config_version="v6")
|
|
117
|
+
|
|
118
|
+
with Zentest(api_key="zt_live_…", base_url="http://localhost:8620", region="uk-south") as zt:
|
|
119
|
+
v = zt.gates.run("Claims Copilot", "v6", "Claims FAQ Golden Set", agent, repeats=3, deterministic=True)
|
|
120
|
+
print(v.passed, v.reason, v.interval)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Arguments**
|
|
124
|
+
- `repeats`: required, with no default. A single run gives no interval.
|
|
125
|
+
- `deterministic=True`: you are stating that the app runs with temperature 0 and a fixed seed. Without it the platform
|
|
126
|
+
refuses (`PrerequisiteError`).
|
|
127
|
+
- `agent=None`: the platform calls the product's HTTP connection instead of your code (the endpoint runner, below).
|
|
128
|
+
- `batch_size=50`, `flush_every_s=None`: results go to the platform every `batch_size` trials, or sooner once
|
|
129
|
+
`flush_every_s` seconds pass without a submission, so a slow app's run shows progress and time left in the console
|
|
130
|
+
as it goes. `None` takes the client's (`Zentest(flush_every_s=...)`, else `ZENTEST_FLUSH_EVERY_S`, else 30 s); `0`
|
|
131
|
+
sends full batches only.
|
|
132
|
+
- `packs=[...]`: compliance pack ids from the workspace's catalogue (`eu-ai-act`, `owasp-llm`, …; any other id is a
|
|
133
|
+
422), recorded on the run and returned as `v.packs`.
|
|
134
|
+
- `connection_id="…"` (with `agent=None`): the HTTP connection the platform calls when the environment has several;
|
|
135
|
+
without it, the environment's default connection, else its only one.
|
|
136
|
+
- `corpus=[...]`, `corpus_data_source="synthetic" | "masked_production" | "production"`: your app's retrieval corpus
|
|
137
|
+
(chunks as strings or `{"text", "id"}`), scanned when the run starts. A test case found in it is skipped and reported on
|
|
138
|
+
`v.contamination`; too many and the run is refused (UK-31).
|
|
139
|
+
- `osa_scope={...}`: the three Online Safety Act scope conditions for this release; a change comes back as
|
|
140
|
+
`zt.gates.last_run.scope_alert` (UK-10). `trigger="ci"` marks a run started from a pipeline.
|
|
141
|
+
- `timeout=600`: seconds to wait for the verdict before `ZentestTimeout`.
|
|
142
|
+
|
|
143
|
+
**Version attribution ([contract §2a](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md)):** return `AgentResponse(output=…, model=…, prompt_version=…, config_version=…)`
|
|
144
|
+
from a shape C agent so every trial says what produced it. The values must equal what is registered on the version
|
|
145
|
+
under test. Attribution is required by default: on a product created from P2a on, a trial that reports nothing does not
|
|
146
|
+
count, and a run where nothing is reported does not pass (the verdict's reason says what to report). Products that
|
|
147
|
+
predate it keep counting such trials, as *declared*, until the owner turns attribution required on;
|
|
148
|
+
`v.attribution["coverage"]` shows the share the app reported. `usage` and `metadata` on the response are stored with
|
|
149
|
+
each trial too (credential-named keys such as `api_key`, `accessToken` or `clientSecret` as `[REDACTED]`, personal data
|
|
150
|
+
redacted like any output). They are optional and never fail a trial: nested usage (OpenAI's `prompt_tokens_details`)
|
|
151
|
+
is sent flat as `prompt_tokens_details.cached_tokens`, at most 32 values, and metadata over 16 KB as JSON is trimmed
|
|
152
|
+
key by key; each cut is a warning on `zt.gates.last_run.warnings`.
|
|
153
|
+
|
|
154
|
+
**Tracing (SDK-15):** `@zt.traced` / `with zt.span(name, kind=...)` record steps (tool calls, retrievals, LLM calls,
|
|
155
|
+
planning) as zentest.span.v1 spans. The SDK runner uploads one trace per trial and links it to the trial's result
|
|
156
|
+
(`zt.results(run)` gives `trace_id` and `session_id`; a trial whose spans the platform did not store has no
|
|
157
|
+
`trace_id`, and the run handle says which). Without spans the run still works, but the SDK warns when the run starts
|
|
158
|
+
that trajectory and tool-choice metrics are unavailable, and `zt.gates.last_run.traced` is `False`.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
@zt.traced(kind="tool", name="lookup_claim")
|
|
162
|
+
def lookup(claim_id: str) -> dict: ...
|
|
163
|
+
|
|
164
|
+
with zt.span("answer", kind="llm", region="uk-south") as s:
|
|
165
|
+
s.set_usage(input_tokens=120, output_tokens=30, provider="anthropic")
|
|
166
|
+
s.set_output(text)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Already on OpenTelemetry (SDK-16):** keep your instrumentation (GenAI `gen_ai.*` or OpenInference) and add
|
|
170
|
+
`zentest.otel.install(zt, product="Claims Copilot", version="v6")`. Spans started inside a trial are stamped with the
|
|
171
|
+
trial's ids. A collector can instead export to `POST /v1/traces` with the API token and `X-Zentest-Product` (or the
|
|
172
|
+
resource attribute `zentest.product`), as **OTLP/JSON only**: set `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json`
|
|
173
|
+
(protobuf, the exporters' default, gets 415). `/v1/traces` is outside `/api`, so a proxy or ingress needs a route for
|
|
174
|
+
it; see [zentest-span-v1 §4a](https://github.com/ITHyd/Zentest/blob/main/docs/specs/zentest-span-v1.md).
|
|
175
|
+
|
|
176
|
+
**Endpoint runner (no code on your side):** the platform calls the product's HTTP connection (Connections in the
|
|
177
|
+
console, per environment). For the mock app:
|
|
178
|
+
|
|
179
|
+
| Field | Value | Why |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| endpoint | `http://127.0.0.1:8100/chat` | plain http only to localhost |
|
|
182
|
+
| secret_ref | `env://MOCK_APP_KEY` (dev) or `kv://<vault>/<name>` | the platform stores a reference, never the key |
|
|
183
|
+
| request_template | `{"message": "{{input}}", "session": "zt"}` | |
|
|
184
|
+
| output_path | `$.reply.text` | the answer |
|
|
185
|
+
| context_path | `$.sources[*].text` | **required** when the suite has a context-grounded judge metric (Faithfulness): without it the run is refused before it starts (NEW-43, `MissingInputsError`) |
|
|
186
|
+
| model_path, prompt_version_path, config_version_path | `$.meta.model`, `$.meta.prompt_version`, `$.meta.config_version` | attribution (§2a); a new product does not count unattributed trials |
|
|
187
|
+
|
|
188
|
+
**Manual loop:** `run = zt.start(...)`, then `zt.submit(run, case, output)` for each case in `zt.cases(run)`, then
|
|
189
|
+
`zt.finish(run)`. `zt.cancel(run)` stops a run and returns what completed (`trials_received`, kept).
|
|
190
|
+
|
|
191
|
+
**Catalogue and authoring:** `zt.products()`, `zt.versions(product)`, `zt.suites(product)`, `zt.suite_cases(suite_id)`,
|
|
192
|
+
`zt.add_cases(suite_id, [...])`, `zt.update_case(case_id, input=…)`, `zt.results(run)`, `zt.span_schema()`,
|
|
193
|
+
`zt.upload_spans(product, spans, otlp=…)`, `zt.instrumentation(product)`, `zt.export_suite(product, version, suite)`,
|
|
194
|
+
`zt.submit_corpus(run, chunks, data_source=…)` (UK-31 corpus scan before gating) and
|
|
195
|
+
`zt.submit_session(product, session_id, input, transcript=…)` (REQ-066 production sessions, a `sessions:write` token).
|
|
196
|
+
|
|
197
|
+
**Errors:** `PrerequisiteError`, `CalibrationError`, `ResidencyError`, `IdempotencyConflictError`,
|
|
198
|
+
`MissingInputsError` (`.missing` lists each gap), `VersionError`, `RunFailedError`, `RunCancelledError`, `AuthError`,
|
|
199
|
+
`NotFoundError`, `ZentestTimeout`; all are `ZentestError`.
|
|
200
|
+
|
|
201
|
+
**Minimum version (SDK-19):** the client reads `GET /api/version` once before its first call and raises `VersionError`
|
|
202
|
+
with the upgrade command when it is older than the platform's `min_sdk_version`.
|
|
203
|
+
|
|
204
|
+
**CLI** (`zentest`, [CI gate guide](https://github.com/ITHyd/Zentest/blob/main/docs/specs/ci-gate.md)): `zentest run gate.zt` (a platform gate in CI), `zentest cancel RUN_ID`, and
|
|
205
|
+
offline (SDK-08):
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
zentest export --product "Claims Copilot" --version v6 --suite "Claims FAQ Golden Set" -o claims.ztx
|
|
209
|
+
zentest run --offline claims.ztx --agent my_app.gate:agent --repeats 3 # exit 0 passed, 1 not passed, 2 error
|
|
210
|
+
# or incomplete
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The export holds cases, metric configs and rubrics, never a connection or secret. It does hold every case input, and
|
|
214
|
+
once written it is outside the platform's retention and erasure: keep it where the suite's data may live, delete it
|
|
215
|
+
after the job, and export again after an erasure. Offline, deterministic metrics are scored with the platform's own
|
|
216
|
+
check library and the platform's decision rule. **Offline mode cannot** score judge metrics (they need the platform's
|
|
217
|
+
judge gateway: calibration, family guard, residency), apply the rubric guards, span-level checks, the power check or
|
|
218
|
+
the override chain, or produce an evidence pack; nothing is uploaded.
|
|
219
|
+
|
|
220
|
+
**Incomplete runs exit 2 by default.** When judge metrics were skipped the result is `status: "incomplete"` (exit 2):
|
|
221
|
+
a deterministic-only pass is not a release verdict. A definite not-pass still exits 1 (blocked by a gating
|
|
222
|
+
deterministic metric, or not passed with every trial scored). `--allow-incomplete` accepts the deterministic-only
|
|
223
|
+
result (exit 0/1). A trial with no metric scorable offline is not passed, and a suite with no deterministic metric has
|
|
224
|
+
no offline verdict (`passed: null`, exit 2 even with `--allow-incomplete`). `--strict` is the default and still
|
|
225
|
+
accepted.
|
|
226
|
+
|
|
227
|
+
**Examples**
|
|
228
|
+
- [`examples/mock_app.py`](https://github.com/ITHyd/Zentest/blob/main/sdk/examples/mock_app.py): a local stand-in app that reports attribution (`meta`).
|
|
229
|
+
- [`examples/quickstart.py`](https://github.com/ITHyd/Zentest/blob/main/sdk/examples/quickstart.py): runs it through both runners, with tracing.
|
|
230
|
+
- [`examples/http_app_gate.py`](https://github.com/ITHyd/Zentest/blob/main/sdk/examples/http_app_gate.py): wraps any login-protected chat API as an SDK agent from environment variables (logs in,
|
|
231
|
+
caches and refreshes the JWT, threads conversation ids, reads answer and context with JSONPaths, reports attribution).
|
|
232
|
+
`python sdk/examples/http_app_gate.py --smoke "question"` checks the configuration with one call.
|
|
233
|
+
|
|
234
|
+
**Tests** (from a checkout of the repository): `cd sdk && python3 -m pytest -q tests` (no network:
|
|
235
|
+
httpx.MockTransport; the span and OTel tests also run the platform's own validator from `backend/` when it is
|
|
236
|
+
importable). `pip install -e "sdk[dev]"` adds `build` and `twine`, which the packaging tests use to build and check the
|
|
237
|
+
release artifacts.
|
|
238
|
+
|
|
239
|
+
## Versions and licence
|
|
240
|
+
|
|
241
|
+
`zentest.__version__` is the installed version; [CHANGELOG.md](https://github.com/ITHyd/Zentest/blob/main/sdk/CHANGELOG.md)
|
|
242
|
+
lists what each release changed. Releases are additive within contract v1, and the platform tells an SDK that is too
|
|
243
|
+
old to upgrade (`VersionError`, above). Copyright Nxzen; proprietary licence (see the package metadata).
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# zentest-sdk
|
|
2
|
+
|
|
3
|
+
The Python SDK for [Zentest](https://github.com/ITHyd/Zentest), Nxzen's platform for testing AI apps (chatbots, RAG,
|
|
4
|
+
voice agents). Run your app's test suite in your own process, or let the platform call its HTTP endpoint, and gate the
|
|
5
|
+
release on a judged verdict with a confidence interval. The behaviour is fixed by the
|
|
6
|
+
[SDK contract v1](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md) (SDK-01 to SDK-20).
|
|
7
|
+
A TypeScript SDK for Node 18+ implements the same contract in [`sdk-ts/`](https://github.com/ITHyd/Zentest/tree/main/sdk-ts)
|
|
8
|
+
(contract §2b); offline runs and the CLI are Python only.
|
|
9
|
+
You need a Zentest workspace and an API token (console: Settings → API tokens).
|
|
10
|
+
|
|
11
|
+
**Links:** [contract](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md) ·
|
|
12
|
+
[changelog](https://github.com/ITHyd/Zentest/blob/main/sdk/CHANGELOG.md) ·
|
|
13
|
+
[CI gate guide](https://github.com/ITHyd/Zentest/blob/main/docs/specs/ci-gate.md) ·
|
|
14
|
+
[span format](https://github.com/ITHyd/Zentest/blob/main/docs/specs/zentest-span-v1.md) ·
|
|
15
|
+
[examples](https://github.com/ITHyd/Zentest/tree/main/sdk/examples)
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Python 3.11 or later.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install zentest-sdk # core: httpx only
|
|
23
|
+
pip install "zentest-sdk[otel]" # + OpenTelemetry exporter (zentest.otel)
|
|
24
|
+
pip install "zentest-sdk[yaml]" # + YAML suite files for the CLI
|
|
25
|
+
pip install "zentest-sdk[schema]" # + offline runs of a format metric with a JSON Schema
|
|
26
|
+
pip install "zentest-sdk[judge]" # + the local Claude judge (open-core mode, no platform)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Install it into a virtual environment, then check it: `zentest --version` prints the installed version. Add only
|
|
30
|
+
the extras you need.
|
|
31
|
+
|
|
32
|
+
Working on the SDK itself? Install it from a checkout of the repository instead: `pip install -e sdk` (extras as
|
|
33
|
+
`pip install -e "sdk[otel,yaml]"`, and `sdk[dev]` for the tests).
|
|
34
|
+
|
|
35
|
+
**Dependencies (SDK-20).** The client is installed into your application's environment, so the floor is thin: `httpx`
|
|
36
|
+
and nothing else. Evaluation libraries stay on the platform; the deterministic checks used by offline runs are standard
|
|
37
|
+
library only. Extras: `otel` (`opentelemetry-sdk`), `yaml` (`pyyaml`), `schema` (`jsonschema`). The package ships type
|
|
38
|
+
hints (`py.typed`).
|
|
39
|
+
|
|
40
|
+
## Evaluate in your own tests (0.4.0)
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from zentest import LLMTestCase, assert_test
|
|
44
|
+
from zentest.metrics import PIILeak, Faithfulness, AnswerRelevancy, GEval
|
|
45
|
+
|
|
46
|
+
def test_burst_pipe():
|
|
47
|
+
case = LLMTestCase(input="Is a burst pipe covered?", actual_output=app(q), retrieval_context=chunks)
|
|
48
|
+
assert_test(case, [PIILeak(), Faithfulness(threshold=0.8), AnswerRelevancy(),
|
|
49
|
+
GEval("Plain English", "Short answer a customer understands")])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `pytest` prints a Zentest summary table; `--zentest-sync --zentest-product P --zentest-version V` records the session
|
|
53
|
+
as a platform run. Deterministic metrics always run before judge metrics.
|
|
54
|
+
- Judges: `ZENTEST_JUDGE=platform` (the platform's calibrated judge, needs `ZENTEST_API_KEY`), `local` (Claude with
|
|
55
|
+
`ANTHROPIC_API_KEY`, or OpenRouter with `OPENROUTER_API_KEY`) or `heuristic` (no model call). A local judge is not
|
|
56
|
+
calibrated: gate releases on the platform.
|
|
57
|
+
- `zentest init` then `zentest test zentest.yaml`: the same checks as a YAML file in Git (`--baseline`, `--junit`,
|
|
58
|
+
`--sync`).
|
|
59
|
+
- `zentest redteam --product P --version V --agent app:answer`: the product's red-team suite against your agent.
|
|
60
|
+
- `zentest.guard.Guard(...).protect(answer)`: block prompt injection and personal data in production.
|
|
61
|
+
- `zentest.simulate(answer, persona=..., goal=...)`: a seeded multi-turn conversation, scored per turn.
|
|
62
|
+
|
|
63
|
+
A worked example on a real app lives in the repository under `sdk/examples` (it runs with no network in mock mode).
|
|
64
|
+
|
|
65
|
+
## Quick start (release gate)
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from zentest import Zentest, AgentInput, AgentResponse
|
|
69
|
+
|
|
70
|
+
def agent(x: AgentInput) -> AgentResponse: # or (user_message: str) -> str, or (messages: list[dict]) -> str
|
|
71
|
+
reply, chunks = my_app.answer(x.messages[-1]["content"])
|
|
72
|
+
return AgentResponse(output=reply, retrieval_context=chunks,
|
|
73
|
+
model="claude-sonnet-4.5", prompt_version="r12", config_version="v6")
|
|
74
|
+
|
|
75
|
+
with Zentest(api_key="zt_live_…", base_url="http://localhost:8620", region="uk-south") as zt:
|
|
76
|
+
v = zt.gates.run("Claims Copilot", "v6", "Claims FAQ Golden Set", agent, repeats=3, deterministic=True)
|
|
77
|
+
print(v.passed, v.reason, v.interval)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Arguments**
|
|
81
|
+
- `repeats`: required, with no default. A single run gives no interval.
|
|
82
|
+
- `deterministic=True`: you are stating that the app runs with temperature 0 and a fixed seed. Without it the platform
|
|
83
|
+
refuses (`PrerequisiteError`).
|
|
84
|
+
- `agent=None`: the platform calls the product's HTTP connection instead of your code (the endpoint runner, below).
|
|
85
|
+
- `batch_size=50`, `flush_every_s=None`: results go to the platform every `batch_size` trials, or sooner once
|
|
86
|
+
`flush_every_s` seconds pass without a submission, so a slow app's run shows progress and time left in the console
|
|
87
|
+
as it goes. `None` takes the client's (`Zentest(flush_every_s=...)`, else `ZENTEST_FLUSH_EVERY_S`, else 30 s); `0`
|
|
88
|
+
sends full batches only.
|
|
89
|
+
- `packs=[...]`: compliance pack ids from the workspace's catalogue (`eu-ai-act`, `owasp-llm`, …; any other id is a
|
|
90
|
+
422), recorded on the run and returned as `v.packs`.
|
|
91
|
+
- `connection_id="…"` (with `agent=None`): the HTTP connection the platform calls when the environment has several;
|
|
92
|
+
without it, the environment's default connection, else its only one.
|
|
93
|
+
- `corpus=[...]`, `corpus_data_source="synthetic" | "masked_production" | "production"`: your app's retrieval corpus
|
|
94
|
+
(chunks as strings or `{"text", "id"}`), scanned when the run starts. A test case found in it is skipped and reported on
|
|
95
|
+
`v.contamination`; too many and the run is refused (UK-31).
|
|
96
|
+
- `osa_scope={...}`: the three Online Safety Act scope conditions for this release; a change comes back as
|
|
97
|
+
`zt.gates.last_run.scope_alert` (UK-10). `trigger="ci"` marks a run started from a pipeline.
|
|
98
|
+
- `timeout=600`: seconds to wait for the verdict before `ZentestTimeout`.
|
|
99
|
+
|
|
100
|
+
**Version attribution ([contract §2a](https://github.com/ITHyd/Zentest/blob/main/docs/specs/sdk-contract.md)):** return `AgentResponse(output=…, model=…, prompt_version=…, config_version=…)`
|
|
101
|
+
from a shape C agent so every trial says what produced it. The values must equal what is registered on the version
|
|
102
|
+
under test. Attribution is required by default: on a product created from P2a on, a trial that reports nothing does not
|
|
103
|
+
count, and a run where nothing is reported does not pass (the verdict's reason says what to report). Products that
|
|
104
|
+
predate it keep counting such trials, as *declared*, until the owner turns attribution required on;
|
|
105
|
+
`v.attribution["coverage"]` shows the share the app reported. `usage` and `metadata` on the response are stored with
|
|
106
|
+
each trial too (credential-named keys such as `api_key`, `accessToken` or `clientSecret` as `[REDACTED]`, personal data
|
|
107
|
+
redacted like any output). They are optional and never fail a trial: nested usage (OpenAI's `prompt_tokens_details`)
|
|
108
|
+
is sent flat as `prompt_tokens_details.cached_tokens`, at most 32 values, and metadata over 16 KB as JSON is trimmed
|
|
109
|
+
key by key; each cut is a warning on `zt.gates.last_run.warnings`.
|
|
110
|
+
|
|
111
|
+
**Tracing (SDK-15):** `@zt.traced` / `with zt.span(name, kind=...)` record steps (tool calls, retrievals, LLM calls,
|
|
112
|
+
planning) as zentest.span.v1 spans. The SDK runner uploads one trace per trial and links it to the trial's result
|
|
113
|
+
(`zt.results(run)` gives `trace_id` and `session_id`; a trial whose spans the platform did not store has no
|
|
114
|
+
`trace_id`, and the run handle says which). Without spans the run still works, but the SDK warns when the run starts
|
|
115
|
+
that trajectory and tool-choice metrics are unavailable, and `zt.gates.last_run.traced` is `False`.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
@zt.traced(kind="tool", name="lookup_claim")
|
|
119
|
+
def lookup(claim_id: str) -> dict: ...
|
|
120
|
+
|
|
121
|
+
with zt.span("answer", kind="llm", region="uk-south") as s:
|
|
122
|
+
s.set_usage(input_tokens=120, output_tokens=30, provider="anthropic")
|
|
123
|
+
s.set_output(text)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Already on OpenTelemetry (SDK-16):** keep your instrumentation (GenAI `gen_ai.*` or OpenInference) and add
|
|
127
|
+
`zentest.otel.install(zt, product="Claims Copilot", version="v6")`. Spans started inside a trial are stamped with the
|
|
128
|
+
trial's ids. A collector can instead export to `POST /v1/traces` with the API token and `X-Zentest-Product` (or the
|
|
129
|
+
resource attribute `zentest.product`), as **OTLP/JSON only**: set `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json`
|
|
130
|
+
(protobuf, the exporters' default, gets 415). `/v1/traces` is outside `/api`, so a proxy or ingress needs a route for
|
|
131
|
+
it; see [zentest-span-v1 §4a](https://github.com/ITHyd/Zentest/blob/main/docs/specs/zentest-span-v1.md).
|
|
132
|
+
|
|
133
|
+
**Endpoint runner (no code on your side):** the platform calls the product's HTTP connection (Connections in the
|
|
134
|
+
console, per environment). For the mock app:
|
|
135
|
+
|
|
136
|
+
| Field | Value | Why |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| endpoint | `http://127.0.0.1:8100/chat` | plain http only to localhost |
|
|
139
|
+
| secret_ref | `env://MOCK_APP_KEY` (dev) or `kv://<vault>/<name>` | the platform stores a reference, never the key |
|
|
140
|
+
| request_template | `{"message": "{{input}}", "session": "zt"}` | |
|
|
141
|
+
| output_path | `$.reply.text` | the answer |
|
|
142
|
+
| context_path | `$.sources[*].text` | **required** when the suite has a context-grounded judge metric (Faithfulness): without it the run is refused before it starts (NEW-43, `MissingInputsError`) |
|
|
143
|
+
| model_path, prompt_version_path, config_version_path | `$.meta.model`, `$.meta.prompt_version`, `$.meta.config_version` | attribution (§2a); a new product does not count unattributed trials |
|
|
144
|
+
|
|
145
|
+
**Manual loop:** `run = zt.start(...)`, then `zt.submit(run, case, output)` for each case in `zt.cases(run)`, then
|
|
146
|
+
`zt.finish(run)`. `zt.cancel(run)` stops a run and returns what completed (`trials_received`, kept).
|
|
147
|
+
|
|
148
|
+
**Catalogue and authoring:** `zt.products()`, `zt.versions(product)`, `zt.suites(product)`, `zt.suite_cases(suite_id)`,
|
|
149
|
+
`zt.add_cases(suite_id, [...])`, `zt.update_case(case_id, input=…)`, `zt.results(run)`, `zt.span_schema()`,
|
|
150
|
+
`zt.upload_spans(product, spans, otlp=…)`, `zt.instrumentation(product)`, `zt.export_suite(product, version, suite)`,
|
|
151
|
+
`zt.submit_corpus(run, chunks, data_source=…)` (UK-31 corpus scan before gating) and
|
|
152
|
+
`zt.submit_session(product, session_id, input, transcript=…)` (REQ-066 production sessions, a `sessions:write` token).
|
|
153
|
+
|
|
154
|
+
**Errors:** `PrerequisiteError`, `CalibrationError`, `ResidencyError`, `IdempotencyConflictError`,
|
|
155
|
+
`MissingInputsError` (`.missing` lists each gap), `VersionError`, `RunFailedError`, `RunCancelledError`, `AuthError`,
|
|
156
|
+
`NotFoundError`, `ZentestTimeout`; all are `ZentestError`.
|
|
157
|
+
|
|
158
|
+
**Minimum version (SDK-19):** the client reads `GET /api/version` once before its first call and raises `VersionError`
|
|
159
|
+
with the upgrade command when it is older than the platform's `min_sdk_version`.
|
|
160
|
+
|
|
161
|
+
**CLI** (`zentest`, [CI gate guide](https://github.com/ITHyd/Zentest/blob/main/docs/specs/ci-gate.md)): `zentest run gate.zt` (a platform gate in CI), `zentest cancel RUN_ID`, and
|
|
162
|
+
offline (SDK-08):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
zentest export --product "Claims Copilot" --version v6 --suite "Claims FAQ Golden Set" -o claims.ztx
|
|
166
|
+
zentest run --offline claims.ztx --agent my_app.gate:agent --repeats 3 # exit 0 passed, 1 not passed, 2 error
|
|
167
|
+
# or incomplete
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The export holds cases, metric configs and rubrics, never a connection or secret. It does hold every case input, and
|
|
171
|
+
once written it is outside the platform's retention and erasure: keep it where the suite's data may live, delete it
|
|
172
|
+
after the job, and export again after an erasure. Offline, deterministic metrics are scored with the platform's own
|
|
173
|
+
check library and the platform's decision rule. **Offline mode cannot** score judge metrics (they need the platform's
|
|
174
|
+
judge gateway: calibration, family guard, residency), apply the rubric guards, span-level checks, the power check or
|
|
175
|
+
the override chain, or produce an evidence pack; nothing is uploaded.
|
|
176
|
+
|
|
177
|
+
**Incomplete runs exit 2 by default.** When judge metrics were skipped the result is `status: "incomplete"` (exit 2):
|
|
178
|
+
a deterministic-only pass is not a release verdict. A definite not-pass still exits 1 (blocked by a gating
|
|
179
|
+
deterministic metric, or not passed with every trial scored). `--allow-incomplete` accepts the deterministic-only
|
|
180
|
+
result (exit 0/1). A trial with no metric scorable offline is not passed, and a suite with no deterministic metric has
|
|
181
|
+
no offline verdict (`passed: null`, exit 2 even with `--allow-incomplete`). `--strict` is the default and still
|
|
182
|
+
accepted.
|
|
183
|
+
|
|
184
|
+
**Examples**
|
|
185
|
+
- [`examples/mock_app.py`](https://github.com/ITHyd/Zentest/blob/main/sdk/examples/mock_app.py): a local stand-in app that reports attribution (`meta`).
|
|
186
|
+
- [`examples/quickstart.py`](https://github.com/ITHyd/Zentest/blob/main/sdk/examples/quickstart.py): runs it through both runners, with tracing.
|
|
187
|
+
- [`examples/http_app_gate.py`](https://github.com/ITHyd/Zentest/blob/main/sdk/examples/http_app_gate.py): wraps any login-protected chat API as an SDK agent from environment variables (logs in,
|
|
188
|
+
caches and refreshes the JWT, threads conversation ids, reads answer and context with JSONPaths, reports attribution).
|
|
189
|
+
`python sdk/examples/http_app_gate.py --smoke "question"` checks the configuration with one call.
|
|
190
|
+
|
|
191
|
+
**Tests** (from a checkout of the repository): `cd sdk && python3 -m pytest -q tests` (no network:
|
|
192
|
+
httpx.MockTransport; the span and OTel tests also run the platform's own validator from `backend/` when it is
|
|
193
|
+
importable). `pip install -e "sdk[dev]"` adds `build` and `twine`, which the packaging tests use to build and check the
|
|
194
|
+
release artifacts.
|
|
195
|
+
|
|
196
|
+
## Versions and licence
|
|
197
|
+
|
|
198
|
+
`zentest.__version__` is the installed version; [CHANGELOG.md](https://github.com/ITHyd/Zentest/blob/main/sdk/CHANGELOG.md)
|
|
199
|
+
lists what each release changed. Releases are additive within contract v1, and the platform tells an SDK that is too
|
|
200
|
+
old to upgrade (`VersionError`, above). Copyright Nxzen; proprietary licence (see the package metadata).
|