decision-toolkit 0.1.7__tar.gz → 0.2.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.
Files changed (90) hide show
  1. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/CHANGELOG.md +26 -2
  2. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/PKG-INFO +11 -7
  3. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/README.md +10 -6
  4. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/CORE.md +1 -1
  5. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/EXAMPLES.md +4 -0
  6. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/RELEASE.md +10 -8
  7. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/REST.md +90 -66
  8. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/VARIANTS.md +7 -5
  9. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/index.md +1 -1
  10. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/pyproject.toml +1 -1
  11. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/__init__.py +1 -1
  12. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/contracts/__init__.py +2 -2
  13. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/contracts/mapping.py +36 -12
  14. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/contracts/openai_decisions_beta.py +4 -8
  15. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/contracts/systemone_v1.py +43 -27
  16. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/schema.py +1 -1
  17. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/server.py +46 -4
  18. decision_toolkit-0.2.0/tests/test_contracts_conformance.py +109 -0
  19. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_fuzz_rest.py +7 -3
  20. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_operations.py +1 -1
  21. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_schema.py +1 -2
  22. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_server.py +89 -23
  23. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/uv.lock +1 -1
  24. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/.github/workflows/ci.yml +0 -0
  25. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/.github/workflows/docs.yml +0 -0
  26. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/.github/workflows/release.yml +0 -0
  27. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/.gitignore +0 -0
  28. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/CONTRIBUTING.md +0 -0
  29. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/LICENSE +0 -0
  30. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/SECURITY.md +0 -0
  31. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/THIRD_PARTY_NOTICES.md +0 -0
  32. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/README.md +0 -0
  33. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/__init__.py +0 -0
  34. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/load.py +0 -0
  35. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/results/2026-10-20-calibration.json +0 -0
  36. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/results/2026-10-20-load.json +0 -0
  37. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/results/2026-10-20-smoke.json +0 -0
  38. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/run.py +0 -0
  39. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/suites/calibration.jsonl +0 -0
  40. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/bench/suites/smoke.jsonl +0 -0
  41. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/BENCH.md +0 -0
  42. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/CLI.md +0 -0
  43. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/OPERATIONS.md +0 -0
  44. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/ROADMAP.md +0 -0
  45. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/models/README.md +0 -0
  46. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/models/gliner2.5-decide.md +0 -0
  47. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/models/gliner2.5-multi.md +0 -0
  48. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/models/julia-1.md +0 -0
  49. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/models/laya.md +0 -0
  50. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/docs/models/opendecider-nano.md +0 -0
  51. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/__main__.py +0 -0
  52. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/__init__.py +0 -0
  53. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/base.py +0 -0
  54. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/gliner2.py +0 -0
  55. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/gliner2_decide.py +0 -0
  56. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/julia.py +0 -0
  57. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/laya.py +0 -0
  58. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/adapters/opendecider.py +0 -0
  59. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/calibration.py +0 -0
  60. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/catalog.py +0 -0
  61. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/cli.py +0 -0
  62. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/config.py +0 -0
  63. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/contracts/common.py +0 -0
  64. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/decision_model.py +0 -0
  65. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/engine/__init__.py +0 -0
  66. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/errors.py +0 -0
  67. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/limits.py +0 -0
  68. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/metrics.py +0 -0
  69. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/models.py +0 -0
  70. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/observability.py +0 -0
  71. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/py.typed +0 -0
  72. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/registry.py +0 -0
  73. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/results.py +0 -0
  74. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/src/decision_toolkit/tracing.py +0 -0
  75. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/__init__.py +0 -0
  76. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/conftest.py +0 -0
  77. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_adapters_gliner2.py +0 -0
  78. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_adapters_gliner2_decide.py +0 -0
  79. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_adapters_julia.py +0 -0
  80. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_adapters_laya.py +0 -0
  81. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_adapters_opendecider.py +0 -0
  82. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_bench.py +0 -0
  83. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_catalog.py +0 -0
  84. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_cli.py +0 -0
  85. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_config.py +0 -0
  86. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_core_phase1.py +0 -0
  87. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_decision_model.py +0 -0
  88. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_integration_gliner2.py +0 -0
  89. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_models.py +0 -0
  90. {decision_toolkit-0.1.7 → decision_toolkit-0.2.0}/tests/test_results.py +0 -0
@@ -1,8 +1,31 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project are documented here. Version `0.1.7` is
3
+ All notable changes to this project are documented here. Version `0.2.0` is
4
4
  published on PyPI.
5
5
 
6
+ ## [0.2.0] — 2026-10-20
7
+
8
+ REST conformance correction. The previous documentation called the TypeSafe
9
+ contract "System One 1.14"; that version was not real. The public TypeSafe API
10
+ schema is `0.2.0`. This release aligns the request/response shapes and error
11
+ behavior with the published schemas, documents unsupported vision/assets as an
12
+ explicit text-only limitation, and removes invented OpenAI response fields.
13
+
14
+ ### Fixed
15
+
16
+ - System One is now pinned to the published TypeSafe API schema `0.2.0`, not
17
+ the invented `1.14` label. `/v1/info` reports `0.2.0`.
18
+ - System One accepts rich instructions/criteria, the standard `assets` field,
19
+ one-level scores and non-negative `output_tokens`; schema validation returns
20
+ `422` with a FastAPI-style `detail` array. Assets/images return `501` because
21
+ the catalog is text-only.
22
+ - OpenAI Decisions responses now expose the public `model`, `answers` and
23
+ `usage` fields (including usage details), without invented `id`, `object`,
24
+ `created_at`, `status` or `safety_identifier` fields. Toolkit metadata remains
25
+ namespaced under `x_decision_toolkit`.
26
+ - `variant` is explicitly documented as a local extension, not a TypeSafe or
27
+ OpenAI field. Standard clients select a model and omit it.
28
+
6
29
  ## [0.1.7] — 2026-10-20
7
30
 
8
31
  Variant consistency release. The selected quantization now agrees across the
@@ -128,7 +151,7 @@ README correction. No code or model behavior changed.
128
151
 
129
152
  ### Phase 2 — REST
130
153
 
131
- - Versioned System One 1.14 and OpenAI Decisions beta projections.
154
+ - Versioned System One and OpenAI Decisions beta projections.
132
155
  - Bearer auth, per-request model/variant, usage, standard error envelope.
133
156
  - OpenAI-shaped `/v1/models`, rich `/v1/decision-models`, readiness `503`.
134
157
  - Request ids, access logs, body limits, CORS and timeouts.
@@ -138,6 +161,7 @@ README correction. No code or model behavior changed.
138
161
  - Typed `DecisionModel` API with lifecycle, batching, async, usage, warnings,
139
162
  normalized confidence, capabilities, structured events and atomic model store.
140
163
 
164
+ [0.2.0]: https://pypi.org/project/decision-toolkit/0.2.0/
141
165
  [0.1.7]: https://pypi.org/project/decision-toolkit/0.1.7/
142
166
  [0.1.6]: https://pypi.org/project/decision-toolkit/0.1.6/
143
167
  [0.1.5]: https://pypi.org/project/decision-toolkit/0.1.5/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: decision-toolkit
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: Local-first toolkit for open-weights decision models: one ONNX engine, one adapter per model contract, typed choice/yes-no/score decisions with probabilities on CPU.
5
5
  Project-URL: Homepage, https://github.com/gia-uh/decision-toolkit
6
6
  Project-URL: Repository, https://github.com/gia-uh/decision-toolkit
@@ -59,7 +59,7 @@ and CLI. `dtk --help` and every subcommand help also include runnable examples.
59
59
  ## Installation
60
60
 
61
61
  ```bash
62
- uv pip install decision-toolkit # library + CLI (0.1.7 on PyPI)
62
+ uv pip install decision-toolkit # library + CLI (0.2.0 on PyPI)
63
63
  uv pip install "decision-toolkit[onnx]" # + onnxruntime, tokenizers (the model runtime)
64
64
  uv pip install "decision-toolkit[server]" # + REST server (FastAPI)
65
65
  ```
@@ -67,7 +67,7 @@ uv pip install "decision-toolkit[server]" # + REST server (FastAPI)
67
67
  The project is managed with [uv](https://docs.astral.sh/uv/) and ships a committed `uv.lock`. Published
68
68
  wheels remain compatible with `pip` for environments that do not use uv.
69
69
 
70
- PyPI: <https://pypi.org/project/decision-toolkit/0.1.7/>. See
70
+ PyPI: <https://pypi.org/project/decision-toolkit/0.2.0/>. See
71
71
  [docs/RELEASE.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/RELEASE.md)
72
72
  for the Trusted Publishing flow and clean-install checks.
73
73
 
@@ -173,12 +173,16 @@ dtk serve --model gliner2.5-multi --warmup \
173
173
  --log-format json
174
174
  ```
175
175
 
176
- Endpoints: `POST /v1/systemone` (System One 1.14), `POST /v1/decisions`
176
+ Endpoints: `POST /v1/systemone` (TypeSafe System One), `POST /v1/decisions`
177
177
  (OpenAI Decisions beta), `GET /v1/models` (OpenAI shape),
178
178
  `GET /v1/decision-models` (rich catalog), `GET /v1/info`, `GET /health`,
179
- `GET /ready`. Requests may select `model` and `variant`; failures use the OpenAI
180
- `{"error": {...}}` envelope and every response carries `x-request-id`.
181
- `TYPESAFE_BASE_URL=http://localhost:8000` makes existing System One clients work.
179
+ `GET /ready`. Requests select `model`; `variant` is a documented local extension,
180
+ not part of either public API. System One request validation uses the TypeSafe
181
+ `422 {"detail": [...]}` shape; OpenAI Decisions uses its
182
+ `{"error": {...}}` envelope. Every response carries `x-request-id`.
183
+ A standard System One client that sends `model`, `state` and `questions` can
184
+ point at `http://localhost:8000`; image/asset and `box` requests are outside the
185
+ current text-only subset.
182
186
  See [docs/REST.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/REST.md) for the pinned contracts, limits, auth and
183
187
  the standard-evolution process.
184
188
 
@@ -21,7 +21,7 @@ and CLI. `dtk --help` and every subcommand help also include runnable examples.
21
21
  ## Installation
22
22
 
23
23
  ```bash
24
- uv pip install decision-toolkit # library + CLI (0.1.7 on PyPI)
24
+ uv pip install decision-toolkit # library + CLI (0.2.0 on PyPI)
25
25
  uv pip install "decision-toolkit[onnx]" # + onnxruntime, tokenizers (the model runtime)
26
26
  uv pip install "decision-toolkit[server]" # + REST server (FastAPI)
27
27
  ```
@@ -29,7 +29,7 @@ uv pip install "decision-toolkit[server]" # + REST server (FastAPI)
29
29
  The project is managed with [uv](https://docs.astral.sh/uv/) and ships a committed `uv.lock`. Published
30
30
  wheels remain compatible with `pip` for environments that do not use uv.
31
31
 
32
- PyPI: <https://pypi.org/project/decision-toolkit/0.1.7/>. See
32
+ PyPI: <https://pypi.org/project/decision-toolkit/0.2.0/>. See
33
33
  [docs/RELEASE.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/RELEASE.md)
34
34
  for the Trusted Publishing flow and clean-install checks.
35
35
 
@@ -135,12 +135,16 @@ dtk serve --model gliner2.5-multi --warmup \
135
135
  --log-format json
136
136
  ```
137
137
 
138
- Endpoints: `POST /v1/systemone` (System One 1.14), `POST /v1/decisions`
138
+ Endpoints: `POST /v1/systemone` (TypeSafe System One), `POST /v1/decisions`
139
139
  (OpenAI Decisions beta), `GET /v1/models` (OpenAI shape),
140
140
  `GET /v1/decision-models` (rich catalog), `GET /v1/info`, `GET /health`,
141
- `GET /ready`. Requests may select `model` and `variant`; failures use the OpenAI
142
- `{"error": {...}}` envelope and every response carries `x-request-id`.
143
- `TYPESAFE_BASE_URL=http://localhost:8000` makes existing System One clients work.
141
+ `GET /ready`. Requests select `model`; `variant` is a documented local extension,
142
+ not part of either public API. System One request validation uses the TypeSafe
143
+ `422 {"detail": [...]}` shape; OpenAI Decisions uses its
144
+ `{"error": {...}}` envelope. Every response carries `x-request-id`.
145
+ A standard System One client that sends `model`, `state` and `questions` can
146
+ point at `http://localhost:8000`; image/asset and `box` requests are outside the
147
+ current text-only subset.
144
148
  See [docs/REST.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/REST.md) for the pinned contracts, limits, auth and
145
149
  the standard-evolution process.
146
150
 
@@ -1,7 +1,7 @@
1
1
  # Core programmatic API (phase 1)
2
2
 
3
3
  The programmatic API is the **superset**: the most expressive layer of the
4
- toolkit. The REST server is a standards-compliant projection of a subset of
4
+ toolkit. The REST server is a documented, conformance-tested projection of a subset of
5
5
  this API, and the CLI is our own operational subset. Nothing in this document
6
6
  belongs to a wire contract.
7
7
 
@@ -132,6 +132,7 @@ dtk serve --model gliner2.5-multi --warmup --port 8000
132
132
  curl -s http://127.0.0.1:8000/v1/systemone \
133
133
  -H "Content-Type: application/json" \
134
134
  -d '{
135
+ "model": "gliner2.5-multi",
135
136
  "state": "Meeting with Acme: 10% discount, sign next week.",
136
137
  "questions": {
137
138
  "cat": {
@@ -150,6 +151,7 @@ curl -s http://127.0.0.1:8000/v1/systemone \
150
151
  curl -s http://127.0.0.1:8000/v1/decisions \
151
152
  -H "Content-Type: application/json" \
152
153
  -d '{
154
+ "model": "gliner2.5-multi",
153
155
  "input": "Please change my seat to an aisle.",
154
156
  "questions": [{
155
157
  "type": "choice",
@@ -163,6 +165,8 @@ curl -s http://127.0.0.1:8000/v1/decisions \
163
165
  }' | jq
164
166
  ```
165
167
 
168
+ `variant` is a decision-toolkit extension; omit it for a standard client.
169
+
166
170
  ### Model per request and auth
167
171
 
168
172
  ```bash
@@ -7,7 +7,8 @@ file was written, so the name was available). The first release was `0.1.0`;
7
7
  package management and builds to uv but was published to TestPyPI only; `0.1.5`
8
8
  fixed TestPyPI verification for uv; `0.1.6` added complete variant visibility
9
9
  and selection in the CLI and documentation; `0.1.7` aligned variant selection
10
- across the library, CLI and REST API.
10
+ across the library, CLI and REST API; `0.2.0` corrected the REST conformance
11
+ claims and wire shapes to the published TypeSafe and OpenAI schemas.
11
12
 
12
13
  ## Local build and verification
13
14
 
@@ -34,7 +35,7 @@ Clean install test (no `PYTHONPATH`):
34
35
  ```bash
35
36
  uv venv --python 3.12 /tmp/dtk-wheel
36
37
  uv pip install --python /tmp/dtk-wheel/bin/python \
37
- dist/decision_toolkit-0.1.7-py3-none-any.whl
38
+ dist/decision_toolkit-0.2.0-py3-none-any.whl
38
39
  uv pip check --python /tmp/dtk-wheel/bin/python
39
40
  /tmp/dtk-wheel/bin/dtk --version
40
41
  /tmp/dtk-wheel/bin/dtk models list --json
@@ -45,7 +46,7 @@ With inference and server extras:
45
46
 
46
47
  ```bash
47
48
  uv pip install --python /tmp/dtk-wheel/bin/python \
48
- "dist/decision_toolkit-0.1.7-py3-none-any.whl[onnx,server,tracing]"
49
+ "dist/decision_toolkit-0.2.0-py3-none-any.whl[onnx,server,tracing]"
49
50
  /tmp/dtk-wheel/bin/dtk decide --model gliner2.5-decide ...
50
51
  /tmp/dtk-wheel/bin/dtk serve --model gliner2.5-decide --warmup --port 8016
51
52
  curl -s http://127.0.0.1:8016/health
@@ -79,8 +80,8 @@ No long-lived PyPI token is needed; the workflow uses GitHub OIDC
79
80
  4. Create and push an annotated tag:
80
81
 
81
82
  ```bash
82
- git tag -a v0.1.7 -m "decision-toolkit 0.1.7"
83
- git push origin v0.1.7
83
+ git tag -a v0.2.0 -m "decision-toolkit 0.2.0"
84
+ git push origin v0.2.0
84
85
  ```
85
86
 
86
87
  5. `.github/workflows/release.yml` builds once, publishes to **TestPyPI**, then
@@ -129,9 +130,9 @@ to TestPyPI without publishing to PyPI or creating a GitHub release.
129
130
 
130
131
  ## Published release
131
132
 
132
- `0.1.7` is published through Trusted Publishing on 2026-10-20:
133
+ `0.2.0` is published through Trusted Publishing on 2026-10-20:
133
134
 
134
- <https://pypi.org/project/decision-toolkit/0.1.7/>
135
+ <https://pypi.org/project/decision-toolkit/0.2.0/>
135
136
 
136
137
  `0.1.0` was the first release; `0.1.1` refreshed the PyPI description; `0.1.2`
137
138
  validated the OIDC TestPyPI → PyPI flow; `0.1.3` added post-publish install
@@ -139,7 +140,8 @@ verification and the GitHub Pages documentation site; `0.1.4` moved the
139
140
  development, build, CI, release and documentation environments to uv but was
140
141
  published to TestPyPI only; `0.1.5` fixed TestPyPI resolution; `0.1.6` added
141
142
  complete variant/quantization visibility and CLI selection; `0.1.7` aligned
142
- selection across library, CLI and REST.
143
+ selection across library, CLI and REST; `0.2.0` corrected the REST conformance
144
+ claims and wire shapes.
143
145
 
144
146
  The tag-triggered workflow remains the preferred path for future releases once
145
147
  the TestPyPI/PyPI trusted publishers and protected environments are configured.
@@ -1,37 +1,42 @@
1
1
  # REST projection (phase 2)
2
2
 
3
- REST is a **standards-compliant projection** of the programmatic core, not a
4
- second implementation. The core remains the superset; the server selects a
5
- subset, maps it to a pinned wire contract, and adds transport concerns (auth,
6
- request ids, limits, CORS, logging).
3
+ REST is a **documented projection** of the programmatic core, not a second
4
+ implementation. The core remains the superset; the server selects a supported
5
+ subset, maps it to the published wire contracts, and adds transport concerns
6
+ (auth, request ids, limits, CORS, logging).
7
7
 
8
8
  ```
9
9
  Programmatic core (superset)
10
- │ pure projection + conformance
10
+ │ pure projection + conformance tests
11
11
  ▼
12
- REST (subset; System One 1.14 + OpenAI Decisions beta)
12
+ REST (supported subset; TypeSafe System One + OpenAI Decisions beta)
13
13
  ```
14
14
 
15
+ Compatibility is tested against the published shapes, not against a local
16
+ invention. The current models are text-only, so image/assets and `box` requests
17
+ are explicitly rejected rather than silently approximated.
18
+
15
19
  The wire models live in `decision_toolkit/contracts/`, the multi-model registry
16
20
  in `decision_toolkit/registry.py`, and `server.py` contains only transport and
17
21
  FastAPI glue.
18
22
 
19
23
  ## Pinned contracts
20
24
 
21
- | Contract | Version | Module |
22
- | --- | --- | --- |
23
- | System One (TypeSafe-compatible) | `1.14` | `contracts/systemone_v1.py` |
24
- | OpenAI Decisions | `beta` | `contracts/openai_decisions_beta.py` |
25
+ | Contract | Version | Reference | Module |
26
+ | --- | --- | --- | --- |
27
+ | TypeSafe System One | API schema `0.2.0` | <https://docs.typesafe.ai/api> | `contracts/systemone_v1.py` |
28
+ | OpenAI Decisions | `beta` | <https://developers.openai.com/api/docs/guides/decisions> | `contracts/openai_decisions_beta.py` |
25
29
 
26
- `GET /v1/info` exposes both versions. The reference for System One is the
27
- TypeSafe 1.14 API as implemented by llama.cpp's `POST /v1/systemone`; the
28
- OpenAI dialect is the beta Decisions shape already translated by the toolkit.
30
+ `GET /v1/info` exposes both versions. "System One 1.14" was an incorrect label
31
+ in earlier documentation; the published TypeSafe API schema is `0.2.0`. The
32
+ implementation targets that schema and the published OpenAI beta shape, with the
33
+ limitations listed below.
29
34
 
30
35
  ## Endpoints
31
36
 
32
37
  | Method | Path | Purpose |
33
38
  | --- | --- | --- |
34
- | `POST` | `/v1/systemone` | System One 1.14 decision. |
39
+ | `POST` | `/v1/systemone` | TypeSafe System One decision. |
35
40
  | `POST` | `/v1/decisions` | OpenAI Decisions beta decision. |
36
41
  | `GET` | `/v1/models` | OpenAI model-list shape. |
37
42
  | `GET` | `/v1/models/{id}` | One model in the OpenAI shape. |
@@ -44,28 +49,33 @@ OpenAI dialect is the beta Decisions shape already translated by the toolkit.
44
49
  Toolkit-only metadata is under `x_decision_toolkit`, never mixed into standard
45
50
  fields. `/v1/decision-models` is the rich, non-standard catalog endpoint.
46
51
 
47
- ## Variants
52
+ ## Variants (local extension)
48
53
 
49
- Both decision endpoints accept an optional `variant` alongside `model`:
54
+ Neither TypeSafe nor OpenAI has a `variant` request field: the public APIs select
55
+ a model by name. Quantization is a **decision-toolkit extension**:
50
56
 
51
- - Request `variant` wins.
57
+ - Request `variant` wins when a local client sends it.
52
58
  - Otherwise, a server started with `dtk serve --variant` uses that default.
53
59
  - Otherwise, the variant selected with `dtk models use` is used when the server
54
60
  is serving the current model.
55
61
  - Otherwise, the catalog default (the `*` variant in `dtk models list`) is used.
56
62
 
57
- The selected variant is echoed in `x_decision_toolkit.variant`.
63
+ Standard clients should omit `variant`; they work with the server default. A
64
+ standard-only integration can run one server per variant, or use the server's
65
+ `--variant` setting. The selected variant is exposed in the namespaced
66
+ `x_decision_toolkit.variant` extension, never in a standard field.
58
67
  `/v1/models`, `/v1/models/{id}`, `/v1/decision-models`, `/v1/info` and `/ready`
59
68
  expose the configured catalog variants, the default, cached variants, and the
60
69
  current selection. See [Variants and quantization](VARIANTS.md) for the catalog
61
70
  and download/select commands.
62
71
 
63
- ## System One 1.14
72
+ ## System One (TypeSafe API schema 0.2.0)
64
73
 
65
- Request:
74
+ Request (standard fields, plus the optional local `model` routing extension):
66
75
 
67
76
  ```json
68
77
  {
78
+ "model": "gliner2.5-multi",
69
79
  "state": "I was charged twice and nobody replied.",
70
80
  "questions": {
71
81
  "route": {
@@ -104,9 +114,9 @@ Response:
104
114
  "confidence": 0.4
105
115
  }
106
116
  },
107
- "usage": {"input_tokens": 239, "output_tokens": 0},
117
+ "usage": {"input_tokens": 239, "output_tokens": 12},
108
118
  "x_decision_toolkit": {
109
- "spec": "1.14",
119
+ "spec": "0.2.0",
110
120
  "request_id": "req-123",
111
121
  "variant": "fp16",
112
122
  "adapter": "gliner2",
@@ -121,16 +131,23 @@ Response:
121
131
 
122
132
  Standard fields:
123
133
 
124
- - `usage.output_tokens` is **always 0** (decisions generate no text).
134
+ - `usage.output_tokens` is a non-negative count. The current core reports the
135
+ tokenizer count of the serialized answers (it may be non-zero); do not assume
136
+ it is always zero.
125
137
  - `usage.input_tokens` comes from the core token accounting.
126
138
  - The full core usage, including the explicit `estimated` flag, is under
127
139
  `x_decision_toolkit.usage`.
128
- - `choice` requires a non-empty criteria object; `score` needs 2–10 levels;
129
- `noul` may omit criteria. Invalid bodies return `400`.
130
- - `files`/`images` accept data URLs only. Text-only models return `501` with
131
- `code: "multimodal_not_supported"`; malformed/non-data URLs return `400`.
132
- - `model` and `variant` are optional routing extensions for a multi-model local
133
- server. An unqualified request uses the configured default model.
140
+ - `choice` requires a non-empty criteria object; System One `score` accepts
141
+ `1`–`10` ordered levels; `noul` may omit criteria.
142
+ - Request-schema and question-validation failures return `422` with the
143
+ FastAPI-style `{"detail": [...]}` body used by the TypeSafe schema. Model
144
+ lookup, capability and transport failures keep their HTTP status and the
145
+ documented error envelope.
146
+ - Standard `assets` are accepted by the schema but return `501` because the
147
+ current catalog is text-only. Legacy `files`/`images` fields are local
148
+ extensions; valid data URLs also return `501`, malformed values return `422`.
149
+ - `model` is optional only as a local convenience; standard clients send it.
150
+ `variant` is a local extension, not part of the TypeSafe schema.
134
151
 
135
152
  ## OpenAI Decisions beta
136
153
 
@@ -146,9 +163,7 @@ Request:
146
163
  {"type": "score", "name": "urgency", "instructions": "How urgent?",
147
164
  "levels": [{"label": "low"}, {"label": "high"}]}
148
165
  ],
149
- "safety_identifier": "user-hash",
150
- "model": "gliner2.5-multi",
151
- "variant": "fp16"
166
+ "model": "gliner2.5-multi"
152
167
  }
153
168
  ```
154
169
 
@@ -156,10 +171,6 @@ Response:
156
171
 
157
172
  ```json
158
173
  {
159
- "id": "req-123",
160
- "object": "response",
161
- "created_at": 1790000000,
162
- "status": "completed",
163
174
  "model": "gliner2.5-multi",
164
175
  "answers": [
165
176
  {"type": "predicate", "name": "refund", "probability": 0.93},
@@ -172,8 +183,13 @@ Response:
172
183
  {"value": 1, "label": "high", "probability": 0.8}],
173
184
  "confidence": 0.8}
174
185
  ],
175
- "usage": {"input_tokens": 92, "output_tokens": 0, "total_tokens": 92},
176
- "safety_identifier": "user-hash",
186
+ "usage": {
187
+ "input_tokens": 92,
188
+ "output_tokens": 12,
189
+ "total_tokens": 104,
190
+ "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0},
191
+ "output_tokens_details": {"reasoning_tokens": 0}
192
+ },
177
193
  "x_decision_toolkit": {
178
194
  "spec": "beta",
179
195
  "request_id": "req-123",
@@ -182,15 +198,20 @@ Response:
182
198
  "latency_ms": 350.1,
183
199
  "truncated": false,
184
200
  "warnings": [],
185
- "usage": {"input_tokens": 92, "output_tokens": 10, "total_tokens": 102, "estimated": true}
201
+ "usage": {"input_tokens": 92, "output_tokens": 12, "total_tokens": 104, "estimated": true}
186
202
  }
187
203
  }
188
204
  ```
189
205
 
190
- - Original choice values (numbers, booleans, strings) are echoed back, not the
191
- internal System One labels.
206
+ - The public beta response fields are `model`, `answers` and `usage`; there is
207
+ no invented `id`, `object`, `created_at`, `status` or `safety_identifier`.
208
+ - Original choice values are echoed back, not the internal System One labels.
209
+ The published beta accepts string/boolean values; the toolkit also accepts
210
+ numbers as a local extension.
192
211
  - Duplicate question names return `400`.
193
- - Images in `input` return `501` (`multimodal_not_supported`).
212
+ - Images in `input` return `501` (`multimodal_not_supported`); the published
213
+ beta supports inline images, but the current catalog has no vision model.
214
+ - `variant` is a local extension; standard clients omit it.
194
215
  - OpenAI SDK clients can list models (`client.models.list()`) and parse the
195
216
  error envelope (`{"error": {message, type, param, code}}`).
196
217
 
@@ -222,9 +243,11 @@ A model is not "ready" merely because it is configured:
222
243
 
223
244
  ## Limits and transport
224
245
 
225
- - 1–128 questions.
226
- - 1–255 choice options; 2–10 score levels.
227
- - 8 files/images maximum (the current catalog supports 0).
246
+ - 1–128 questions (local limit; the TypeSafe schema has no maximum).
247
+ - 1–255 choice options; 1–10 System One score levels; OpenAI score levels
248
+ require at least 2.
249
+ - 8 legacy files/images maximum (the current catalog supports 0); standard
250
+ `assets` and images are rejected with `501`.
228
251
  - Request body limit (`--max-body-mb`, default 16) returns `413`.
229
252
  - Per-decision timeout (`--timeout`, default 120 s) returns `504`.
230
253
  - CORS origins are explicit (`--cors-origin`, repeatable).
@@ -235,22 +258,22 @@ A model is not "ready" merely because it is configured:
235
258
 
236
259
  ## Error mapping
237
260
 
238
- All errors use `{"error": {"message", "type", "param", "code"}}`.
261
+ `/v1/systemone` request-validation failures follow the TypeSafe schema:
262
+ `422` with `{"detail": [...]}`. `/v1/decisions` validation and toolkit domain
263
+ errors use the OpenAI envelope
264
+ `{"error": {"message", "type", "param", "code"}}`.
239
265
 
240
- | Condition | HTTP | code |
266
+ | Condition | System One | OpenAI Decisions |
241
267
  | --- | --- | --- |
242
- | Invalid question/body | `400` | `invalid_question` / `invalid_request` |
243
- | Unsupported capability (`box`, assets) | `400` | `unsupported_capability` |
244
- | Unknown model | `404` | `model_not_found` |
245
- | Model not downloaded | `503` | `model_not_downloaded` |
246
- | Adapter unavailable | `503` | `adapter_not_available` |
247
- | Adapter not implemented | `501` | `adapter_not_implemented` |
248
- | Images/audio unsupported | `501` | `multimodal_not_supported` |
249
- | Truncation in strict mode | `400` | `truncated_input` |
250
- | Body too large | `413` | `request_too_large` |
251
- | Missing/invalid bearer | `401` | `authentication_error` |
252
- | Timeout | `504` | `timeout` |
253
- | Backend failure | `500` | `backend_failure` / `adapter_error` |
268
+ | Request-schema validation | `422` + `detail` | `400` + error envelope |
269
+ | Toolkit question validation | `422` + `detail` | `400` + error envelope |
270
+ | Unknown model | `404` | `404` |
271
+ | Model not downloaded | `503` | `503` |
272
+ | Unsupported `box` / assets / images | `501` | `501` |
273
+ | Truncation in strict mode | `400` | `400` |
274
+ | Body too large | `413` | `413` |
275
+ | Missing/invalid bearer | `401` | `401` |
276
+ | Timeout | `504` | `504` |
254
277
 
255
278
  The CLI/REST never parse error strings to decide behavior; they use the stable
256
279
  `code`.
@@ -277,16 +300,17 @@ Adding a new spec version means adding a new module, not editing the old one.
277
300
 
278
301
  ## Compatibility matrix (current)
279
302
 
280
- | Feature | System One 1.14 | OpenAI Decisions beta |
303
+ | Feature | System One (TypeSafe 0.2.0) | OpenAI Decisions beta |
281
304
  | --- | --- | --- |
282
305
  | `choice` / `noul` / `score` | yes | `choice` / `predicate` / `score` |
283
306
  | `box` | typed by core; rejected by current text models | no |
284
307
  | Text state | yes | `input` string |
285
308
  | JSON state | yes | message/string `input` |
286
- | Images | `501` for current models | `501` for current models |
287
- | Usage | `input_tokens` + `output_tokens=0` | input/output/total |
288
- | Per-request model/variant | extension | `model` + `variant` extension |
289
- | Errors | OpenAI-style envelope | OpenAI envelope |
309
+ | Images/assets | `501` for current models | `501` for current models |
310
+ | Usage | `input_tokens` + non-negative `output_tokens` | input/output/total + details |
311
+ | Per-request model | standard field; optional local default | standard field; optional local default |
312
+ | `variant` | local extension | local extension |
313
+ | Validation errors | `422` + `detail` | `400` + envelope |
290
314
  | Auth / CORS / limits | server-level | server-level |
291
315
 
292
316
  ## Running
@@ -303,8 +327,8 @@ at `http://localhost:8000/v1`.
303
327
 
304
328
  ## Testing
305
329
 
306
- - System One contract tests cover the exact request/response fields, usage
307
- `output_tokens=0`, invalid request matrix and image status codes.
330
+ - System One contract tests cover the published request/response fields,
331
+ non-negative usage, the invalid request matrix and image/asset status codes.
308
332
  - OpenAI contract tests use the official `openai` Python SDK for
309
333
  `models.list()` and error-envelope parsing.
310
334
  - Equivalence tests send the same core decision through HTTP and compare
@@ -46,13 +46,15 @@ precedence is:
46
46
  | --- | --- | --- |
47
47
  | Library | `DecisionModel("gliner2.5-multi", variant="fp32")` | persisted variant for the current model, else catalog default |
48
48
  | CLI | `--variant fp32` | variant selected by `dtk models use`, else catalog default |
49
- | REST | `"variant": "fp32"` in the request body | server `--variant`, else persisted variant for the current model, else catalog default |
49
+ | REST (local extension) | `"variant": "fp32"` in the request body | server `--variant`, else persisted variant for the current model, else catalog default |
50
50
 
51
51
  `DecisionModel.variant` and the CLI report the selected variant (`id@variant` in
52
- `dtk models current`). REST responses expose it under
53
- `x_decision_toolkit.variant`; `/v1/decision-models` includes `current` and
54
- `current_variant`, plus all `variants` and `cached_variants`. A server started
55
- with `--model`/`--variant` reports that default in `/v1/info` and `/ready`.
52
+ `dtk models current`). REST responses expose it under the namespaced
53
+ `x_decision_toolkit.variant` extension; `/v1/decision-models` includes
54
+ `current` and `current_variant`, plus all `variants` and `cached_variants`.
55
+ `variant` is **not** part of TypeSafe or OpenAI; standard clients should omit it
56
+ and rely on the server's `--variant`/persisted default. A server started with
57
+ `--model`/`--variant` reports that default in `/v1/info` and `/ready`.
56
58
 
57
59
  The variant must have been downloaded before a library call or REST request
58
60
  loads it. An unknown variant returns a model-not-found error; a known but
@@ -18,7 +18,7 @@ PyPI: <https://pypi.org/project/decision-toolkit/>
18
18
 
19
19
  - [Examples](EXAMPLES.md) — library, REST and CLI.
20
20
  - [Core API](CORE.md) — the superset used by every surface.
21
- - [REST](REST.md) — System One 1.14 + OpenAI Decisions beta.
21
+ - [REST](REST.md) — TypeSafe System One + OpenAI Decisions beta.
22
22
  - [CLI](CLI.md) — operational commands and batch workflows.
23
23
  - [Variants and quantization](VARIANTS.md) — every catalogued build and how to download/select it.
24
24
  - [Operations](OPERATIONS.md) — rate limits, metrics, tracing and runbook.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "decision-toolkit"
7
- version = "0.1.7"
7
+ version = "0.2.0"
8
8
  description = "Local-first toolkit for open-weights decision models: one ONNX engine, one adapter per model contract, typed choice/yes-no/score decisions with probabilities on CPU."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -7,7 +7,7 @@ questions with a calibrated probability for every option, on CPU.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- __version__ = "0.1.7"
10
+ __version__ = "0.2.0"
11
11
 
12
12
  from .adapters import register_adapter
13
13
  from .calibration import brier_score, expected_calibration_error, threshold_sweep
@@ -1,9 +1,9 @@
1
1
  """Versioned wire contracts for the REST surface.
2
2
 
3
- The programmatic core is the superset. These modules are the standards-compliant
3
+ The programmatic core is the superset. These modules are the conformance-tested
4
4
  projections of a subset of it:
5
5
 
6
- * ``systemone_v1`` — System One 1.14 (TypeSafe compatible).
6
+ * ``systemone_v1`` — TypeSafe System One API (OpenAPI 0.2.0 subset).
7
7
  * ``openai_decisions_beta`` — OpenAI Decisions beta dialect.
8
8
 
9
9
  Each module owns its request/response models. ``mapping`` contains only pure