decision-toolkit 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.
Files changed (85) hide show
  1. decision_toolkit-0.1.0/.github/workflows/ci.yml +56 -0
  2. decision_toolkit-0.1.0/.github/workflows/release.yml +85 -0
  3. decision_toolkit-0.1.0/.gitignore +30 -0
  4. decision_toolkit-0.1.0/CHANGELOG.md +68 -0
  5. decision_toolkit-0.1.0/CONTRIBUTING.md +83 -0
  6. decision_toolkit-0.1.0/LICENSE +21 -0
  7. decision_toolkit-0.1.0/PKG-INFO +306 -0
  8. decision_toolkit-0.1.0/README.md +262 -0
  9. decision_toolkit-0.1.0/SECURITY.md +46 -0
  10. decision_toolkit-0.1.0/THIRD_PARTY_NOTICES.md +72 -0
  11. decision_toolkit-0.1.0/bench/README.md +88 -0
  12. decision_toolkit-0.1.0/bench/__init__.py +1 -0
  13. decision_toolkit-0.1.0/bench/load.py +163 -0
  14. decision_toolkit-0.1.0/bench/results/2026-10-20-calibration.json +311 -0
  15. decision_toolkit-0.1.0/bench/results/2026-10-20-load.json +31 -0
  16. decision_toolkit-0.1.0/bench/results/2026-10-20-smoke.json +246 -0
  17. decision_toolkit-0.1.0/bench/run.py +285 -0
  18. decision_toolkit-0.1.0/bench/suites/calibration.jsonl +10 -0
  19. decision_toolkit-0.1.0/bench/suites/smoke.jsonl +5 -0
  20. decision_toolkit-0.1.0/docs/BENCH.md +66 -0
  21. decision_toolkit-0.1.0/docs/CLI.md +239 -0
  22. decision_toolkit-0.1.0/docs/CORE.md +289 -0
  23. decision_toolkit-0.1.0/docs/EXAMPLES.md +215 -0
  24. decision_toolkit-0.1.0/docs/OPERATIONS.md +178 -0
  25. decision_toolkit-0.1.0/docs/RELEASE.md +116 -0
  26. decision_toolkit-0.1.0/docs/REST.md +307 -0
  27. decision_toolkit-0.1.0/docs/ROADMAP.md +35 -0
  28. decision_toolkit-0.1.0/docs/models/README.md +27 -0
  29. decision_toolkit-0.1.0/docs/models/gliner2.5-decide.md +58 -0
  30. decision_toolkit-0.1.0/docs/models/gliner2.5-multi.md +57 -0
  31. decision_toolkit-0.1.0/docs/models/julia-1.md +58 -0
  32. decision_toolkit-0.1.0/docs/models/laya.md +65 -0
  33. decision_toolkit-0.1.0/docs/models/opendecider-nano.md +58 -0
  34. decision_toolkit-0.1.0/pyproject.toml +101 -0
  35. decision_toolkit-0.1.0/src/decision_toolkit/__init__.py +102 -0
  36. decision_toolkit-0.1.0/src/decision_toolkit/__main__.py +8 -0
  37. decision_toolkit-0.1.0/src/decision_toolkit/adapters/__init__.py +165 -0
  38. decision_toolkit-0.1.0/src/decision_toolkit/adapters/base.py +134 -0
  39. decision_toolkit-0.1.0/src/decision_toolkit/adapters/gliner2.py +144 -0
  40. decision_toolkit-0.1.0/src/decision_toolkit/adapters/gliner2_decide.py +117 -0
  41. decision_toolkit-0.1.0/src/decision_toolkit/adapters/julia.py +168 -0
  42. decision_toolkit-0.1.0/src/decision_toolkit/adapters/laya.py +225 -0
  43. decision_toolkit-0.1.0/src/decision_toolkit/adapters/opendecider.py +144 -0
  44. decision_toolkit-0.1.0/src/decision_toolkit/calibration.py +79 -0
  45. decision_toolkit-0.1.0/src/decision_toolkit/catalog.py +472 -0
  46. decision_toolkit-0.1.0/src/decision_toolkit/cli.py +1277 -0
  47. decision_toolkit-0.1.0/src/decision_toolkit/config.py +90 -0
  48. decision_toolkit-0.1.0/src/decision_toolkit/contracts/__init__.py +58 -0
  49. decision_toolkit-0.1.0/src/decision_toolkit/contracts/common.py +61 -0
  50. decision_toolkit-0.1.0/src/decision_toolkit/contracts/mapping.py +165 -0
  51. decision_toolkit-0.1.0/src/decision_toolkit/contracts/openai_decisions_beta.py +133 -0
  52. decision_toolkit-0.1.0/src/decision_toolkit/contracts/systemone_v1.py +140 -0
  53. decision_toolkit-0.1.0/src/decision_toolkit/decision_model.py +692 -0
  54. decision_toolkit-0.1.0/src/decision_toolkit/engine/__init__.py +111 -0
  55. decision_toolkit-0.1.0/src/decision_toolkit/errors.py +138 -0
  56. decision_toolkit-0.1.0/src/decision_toolkit/limits.py +114 -0
  57. decision_toolkit-0.1.0/src/decision_toolkit/metrics.py +198 -0
  58. decision_toolkit-0.1.0/src/decision_toolkit/models.py +588 -0
  59. decision_toolkit-0.1.0/src/decision_toolkit/observability.py +145 -0
  60. decision_toolkit-0.1.0/src/decision_toolkit/py.typed +0 -0
  61. decision_toolkit-0.1.0/src/decision_toolkit/registry.py +283 -0
  62. decision_toolkit-0.1.0/src/decision_toolkit/results.py +355 -0
  63. decision_toolkit-0.1.0/src/decision_toolkit/schema.py +516 -0
  64. decision_toolkit-0.1.0/src/decision_toolkit/server.py +930 -0
  65. decision_toolkit-0.1.0/src/decision_toolkit/tracing.py +44 -0
  66. decision_toolkit-0.1.0/tests/__init__.py +1 -0
  67. decision_toolkit-0.1.0/tests/conftest.py +117 -0
  68. decision_toolkit-0.1.0/tests/test_adapters_gliner2.py +181 -0
  69. decision_toolkit-0.1.0/tests/test_adapters_gliner2_decide.py +86 -0
  70. decision_toolkit-0.1.0/tests/test_adapters_julia.py +110 -0
  71. decision_toolkit-0.1.0/tests/test_adapters_laya.py +186 -0
  72. decision_toolkit-0.1.0/tests/test_adapters_opendecider.py +104 -0
  73. decision_toolkit-0.1.0/tests/test_bench.py +29 -0
  74. decision_toolkit-0.1.0/tests/test_catalog.py +98 -0
  75. decision_toolkit-0.1.0/tests/test_cli.py +663 -0
  76. decision_toolkit-0.1.0/tests/test_config.py +53 -0
  77. decision_toolkit-0.1.0/tests/test_core_phase1.py +324 -0
  78. decision_toolkit-0.1.0/tests/test_decision_model.py +115 -0
  79. decision_toolkit-0.1.0/tests/test_fuzz_rest.py +112 -0
  80. decision_toolkit-0.1.0/tests/test_integration_gliner2.py +72 -0
  81. decision_toolkit-0.1.0/tests/test_models.py +234 -0
  82. decision_toolkit-0.1.0/tests/test_operations.py +177 -0
  83. decision_toolkit-0.1.0/tests/test_results.py +52 -0
  84. decision_toolkit-0.1.0/tests/test_schema.py +186 -0
  85. decision_toolkit-0.1.0/tests/test_server.py +508 -0
@@ -0,0 +1,56 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ name: python ${{ matrix.python }}
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python: ["3.12", "3.13"]
21
+ env:
22
+ PYTHONPATH: src
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python }}
28
+ cache: pip
29
+ - name: Install test dependencies from source
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ python -m pip install \
33
+ pytest ruff PyYAML huggingface_hub fastapi httpx pydantic numpy openai
34
+ - name: Lint
35
+ run: ruff check src tests bench
36
+ - name: Unit and contract tests (source tree, no wheel)
37
+ run: pytest -q
38
+ - name: CLI smoke from source
39
+ run: |
40
+ python -m decision_toolkit --version
41
+ python -m decision_toolkit --home "$RUNNER_TEMP/dtk-home" doctor --json
42
+
43
+ audit:
44
+ name: dependency audit
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ - uses: actions/setup-python@v5
49
+ with:
50
+ python-version: "3.13"
51
+ - name: Install audit tool and runtime dependencies
52
+ run: |
53
+ python -m pip install --upgrade pip pip-audit
54
+ python -m pip install PyYAML huggingface_hub fastapi httpx pydantic numpy
55
+ - name: pip-audit
56
+ run: pip-audit
@@ -0,0 +1,85 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ target:
10
+ description: "Publish target (tag pushes always publish TestPyPI then PyPI)"
11
+ type: choice
12
+ options: [testpypi]
13
+ default: testpypi
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ build:
20
+ name: build distributions
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.12"
27
+ - name: Build sdist and wheel
28
+ run: |
29
+ python -m pip install --upgrade build twine
30
+ python -m build
31
+ python -m twine check dist/*
32
+ - uses: actions/upload-artifact@v4
33
+ with:
34
+ name: distributions
35
+ path: dist/
36
+
37
+ publish-testpypi:
38
+ name: publish TestPyPI
39
+ if: startsWith(github.ref, 'refs/tags/v') || inputs.target == 'testpypi'
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ environment: testpypi
43
+ permissions:
44
+ id-token: write
45
+ steps:
46
+ - uses: actions/download-artifact@v4
47
+ with:
48
+ name: distributions
49
+ path: dist/
50
+ - uses: pypa/gh-action-pypi-publish@release/v1
51
+ with:
52
+ repository-url: https://test.pypi.org/legacy/
53
+
54
+ publish-pypi:
55
+ name: publish PyPI
56
+ if: startsWith(github.ref, 'refs/tags/v')
57
+ needs: publish-testpypi
58
+ runs-on: ubuntu-latest
59
+ environment: pypi
60
+ permissions:
61
+ id-token: write
62
+ steps:
63
+ - uses: actions/download-artifact@v4
64
+ with:
65
+ name: distributions
66
+ path: dist/
67
+ - uses: pypa/gh-action-pypi-publish@release/v1
68
+
69
+ github-release:
70
+ name: GitHub release
71
+ if: startsWith(github.ref, 'refs/tags/v')
72
+ needs: publish-pypi
73
+ runs-on: ubuntu-latest
74
+ permissions:
75
+ contents: write
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+ - uses: actions/download-artifact@v4
79
+ with:
80
+ name: distributions
81
+ path: dist/
82
+ - name: Create release
83
+ env:
84
+ GH_TOKEN: ${{ github.token }}
85
+ run: gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --generate-notes --verify-tag
@@ -0,0 +1,30 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+
16
+ # Test caches
17
+ .pytest_cache/
18
+ .coverage
19
+ htmlcov/
20
+ .ruff_cache/
21
+
22
+ # Local data
23
+ *.local
24
+ home/
25
+ /models/
26
+
27
+ # OS / editors
28
+ .DS_Store
29
+ .idea/
30
+ .vscode/
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The project has not
4
+ published a distribution artifact yet; versions are repository milestones.
5
+
6
+ ## [Unreleased]
7
+
8
+ ### Phase 6 — packaging
9
+
10
+ - Added `py.typed`, project URLs, co-author metadata and sdist inclusion.
11
+ - Wheel and sdist build successfully; `twine check` passes with current tooling.
12
+ - Wheel installs in a clean venv with `[onnx,server,tracing]`; `dtk` and the
13
+ REST server work from the installed artifact.
14
+ - Added a Trusted Publishing release workflow for TestPyPI → PyPI and GitHub
15
+ Releases.
16
+ - No artifact has been uploaded yet; PyPI/TestPyPI publishing requires the
17
+ configured environments and approval.
18
+ - Added `docs/RELEASE.md`.
19
+
20
+ ### Phase 5 — operations
21
+
22
+ - Per-key rate limiting with `429` + `Retry-After` (disabled by default).
23
+ - Bounded inference concurrency (`--max-concurrent`) with `503 server_busy`
24
+ instead of unbounded queueing.
25
+ - Prometheus `/metrics`: HTTP, decision, token, model-load, rate-limit, busy,
26
+ inflight and model-cache metrics with low-cardinality labels.
27
+ - Optional OpenTelemetry spans (`--tracing`) with a no-op fallback.
28
+ - `bench/load.py` concurrent load probe and a versioned backpressure result.
29
+ - Deterministic REST fuzz/negative corpus and PII sentinel tests.
30
+ - `pip-audit` CI job; `docs/OPERATIONS.md` runbook.
31
+ - Still no wheel/PyPI/tag.
32
+
33
+ ### Phase 4 — reproducibility
34
+
35
+ - Pinned every catalog variant to a HuggingFace commit SHA.
36
+ - Recorded SHA256 for every declared model file and external-data file.
37
+ - `dtk models verify` now treats the catalog hashes as the source of truth and
38
+ also verifies `.onnx.data` files.
39
+ - Model downloads reject files that do not match the pinned hashes.
40
+ - Added a reproducible benchmark/calibration harness in `bench/` with versioned
41
+ results and a synthetic calibration suite.
42
+ - Added source-only CI for Python 3.12/3.13 (lint, tests, CLI smoke; no wheel).
43
+ - Added model-card reproducibility sections, `docs/BENCH.md`, contributing,
44
+ security and third-party notices.
45
+
46
+ ### Phase 3 — CLI
47
+
48
+ - Consistent `--json`, quiet/verbose modes and exit codes (`0/1/2/3/130`).
49
+ - JSON/YAML questions files, JSONL batching, custom `--adapter/--files`.
50
+ - Variant-aware `use/current/delete` and strict `verify --all`.
51
+ - `config get/set/list`, `version`, `completion`.
52
+ - `serve --workers/--reload`, API key, CORS, log format and warmup.
53
+
54
+ ### Phase 2 — REST
55
+
56
+ - Versioned System One 1.14 and OpenAI Decisions beta projections.
57
+ - Bearer auth, per-request model/variant, usage, standard error envelope.
58
+ - OpenAI-shaped `/v1/models`, rich `/v1/decision-models`, readiness `503`.
59
+ - Request ids, access logs, body limits, CORS and timeouts.
60
+
61
+ ### Phase 1 — core
62
+
63
+ - Typed `DecisionModel` API with lifecycle, batching, async, usage, warnings,
64
+ normalized confidence, capabilities, structured events and atomic model store.
65
+
66
+ ## [0.1.0] — unreleased
67
+
68
+ The version is not tagged or packaged yet. Wheel/PyPI construction is deferred.
@@ -0,0 +1,83 @@
1
+ # Contributing
2
+
3
+ decision-toolkit is a curated runtime, not a mirror of model hubs. Changes that
4
+ add a model or an adapter must bring **real evidence**, not a promising README.
5
+
6
+ ## Development setup (source only)
7
+
8
+ There is no wheel/PyPI release yet. Work from the source tree:
9
+
10
+ ```bash
11
+ python -m venv .venv
12
+ . .venv/bin/activate
13
+ pip install pytest ruff pip-audit PyYAML huggingface_hub fastapi httpx pydantic numpy
14
+ # for real inference:
15
+ pip install onnxruntime tokenizers
16
+ PYTHONPATH=src pytest -q
17
+ ```
18
+
19
+ The fast suite must not download models. Integration tests are opt-in:
20
+
21
+ ```bash
22
+ PYTHONPATH=src pytest -m integration -o addopts="" -s
23
+ ```
24
+
25
+ `tests/test_integration_gliner2.py` uses `DECISION_TOOLKIT_GLINER2_DIR` and
26
+ never downloads.
27
+
28
+ ## Code rules
29
+
30
+ - Keep inference logic in adapters; the CLI and REST call the core.
31
+ - The core is the superset. Do not add wire-specific concepts to it.
32
+ - The REST layer may only project core capabilities; if a standard needs
33
+ something, add it to the core first.
34
+ - Every new error gets a stable `code` and a test for its HTTP/CLI mapping.
35
+ - No silent truncation: mark `truncated`/`warnings` or fail in strict mode.
36
+ - Logs carry model/variant/request id, not state/question content by default.
37
+
38
+ ## Model admission
39
+
40
+ A model enters `CATALOG` only when it has:
41
+
42
+ 1. a clean commercial license;
43
+ 2. a real public ONNX export;
44
+ 3. an adapter contract implemented from that export;
45
+ 4. a **pinned commit SHA** and SHA256 for every declared file;
46
+ 5. measured parity against a named baseline;
47
+ 6. CPU latency measured on a known machine;
48
+ 7. a model card and reproducibility commands.
49
+
50
+ Models that fail any gate belong in `docs/ROADMAP.md`, not in the catalog.
51
+
52
+ ## Bench evidence
53
+
54
+ Re-run the harness when a model, adapter or quantized build changes:
55
+
56
+ ```bash
57
+ PYTHONPATH=src python bench/run.py --suite bench/suites/smoke.jsonl \
58
+ --repeats 3 --output bench/results/YYYY-MM-DD-smoke.json
59
+ ```
60
+
61
+ Do not overwrite an old result with a different machine silently; add a new
62
+ dated file and record the environment. Latency is post-warmup and is not the
63
+ same metric as the historical admission values in the model cards.
64
+
65
+ ## Tests and lint
66
+
67
+ ```bash
68
+ ruff check src tests bench
69
+ PYTHONPATH=src pytest -q
70
+ PYTHONPATH=src pytest -q -m fuzz # malformed REST corpus
71
+ python -m decision_toolkit --version
72
+ pip-audit # dependency scan (CI runs this)
73
+ ```
74
+
75
+ CI runs these on Python 3.12 and 3.13 from source, plus `pip-audit`. It does
76
+ **not** build a wheel; packaging is a later milestone. See
77
+ [OPERATIONS.md](docs/OPERATIONS.md) for rate limits, metrics, tracing and the
78
+ load probe.
79
+
80
+ ## Commits
81
+
82
+ Use a short imperative subject. Mention the layer (`core`, `rest`, `cli`,
83
+ `repro`, `docs`) and whether catalog evidence changed.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GIA-UH (Universidad de La Habana) and Syalia S.R.L.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.5
2
+ Name: decision-toolkit
3
+ Version: 0.1.0
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
+ Project-URL: Homepage, https://github.com/gia-uh/decision-toolkit
6
+ Project-URL: Repository, https://github.com/gia-uh/decision-toolkit
7
+ Project-URL: Issues, https://github.com/gia-uh/decision-toolkit/issues
8
+ Project-URL: Documentation, https://github.com/gia-uh/decision-toolkit/tree/main/docs
9
+ Project-URL: Changelog, https://github.com/gia-uh/decision-toolkit/blob/main/CHANGELOG.md
10
+ Author: Syalia S.R.L.
11
+ Author-email: Yudivián Almeida Cruz <yudivian@gmail.com>
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ Keywords: classification,cpu,decision-models,jev,local-first,onnx,routing,system-one
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: huggingface-hub>=0.23
26
+ Requires-Dist: pyyaml>=6.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: httpx>=0.27; extra == 'dev'
29
+ Requires-Dist: openai>=1.0; extra == 'dev'
30
+ Requires-Dist: pip-audit>=2; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Provides-Extra: onnx
34
+ Requires-Dist: numpy>=1.26; extra == 'onnx'
35
+ Requires-Dist: onnxruntime>=1.18; extra == 'onnx'
36
+ Requires-Dist: tokenizers>=0.19; extra == 'onnx'
37
+ Provides-Extra: server
38
+ Requires-Dist: fastapi>=0.115; extra == 'server'
39
+ Requires-Dist: python-multipart>=0.0.9; extra == 'server'
40
+ Requires-Dist: uvicorn[standard]>=0.30; extra == 'server'
41
+ Provides-Extra: tracing
42
+ Requires-Dist: opentelemetry-api>=1.25; extra == 'tracing'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # decision-toolkit
46
+
47
+ Local-first toolkit around **open-weights decision models**: typed **choice /
48
+ yes-no (`noul`) / score** questions over a state (text or JSON), answered with a
49
+ **calibrated probability for every option** — no generated text, no parsing.
50
+
51
+ One **ONNX engine** (`onnxruntime`), in-process, on CPU. One **adapter per model
52
+ contract** (how a model is fed and read). Three thin surfaces over the same API:
53
+ **library**, **CLI** and **REST**.
54
+
55
+ Co-developed by [GIA-UH](https://github.com/gia-uh) and [Syalia S.R.L.](https://github.com/syalia-srl).
56
+
57
+ **Examples:** [docs/EXAMPLES.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/EXAMPLES.md) covers the library, REST and
58
+ CLI. `dtk --help` and every subcommand help also include runnable examples.
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install decision-toolkit # library + CLI (once v0.1.0 is on PyPI)
64
+ pip install "decision-toolkit[onnx]" # + onnxruntime, tokenizers (the model runtime)
65
+ pip install "decision-toolkit[server]" # + REST server (FastAPI)
66
+ ```
67
+
68
+ Pre-release: the artifact is built and verified locally, but not uploaded yet.
69
+ See [docs/RELEASE.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/RELEASE.md) for the Trusted Publishing flow and
70
+ clean-install checks.
71
+
72
+ ## Three ways to use it
73
+
74
+ **1. As a library (primary interface)**
75
+
76
+ ```python
77
+ from decision_toolkit import DecisionModel, choice, noul, score
78
+
79
+ # download once: dtk models download gliner2.5-multi
80
+ model = DecisionModel("gliner2.5-multi")
81
+
82
+ answer = model.decide(
83
+ state="Meeting with Acme: we agreed a 10% discount and will sign next week.",
84
+ questions={
85
+ "cat": choice("What kind of record is this?", {
86
+ "meeting": "a meeting or call with topics, decisions and actions",
87
+ "tasks": "things to do, with dates or owners",
88
+ "contact": "a person's details: name, role, company, phone, email",
89
+ "idea": "an idea, reflection, observation or question",
90
+ "other": "a greeting, small talk or anything else",
91
+ }),
92
+ "urgent": noul("Does it need a reply today?"),
93
+ },
94
+ )
95
+ answer["cat"].choice # 'meeting'
96
+ answer["cat"].confidence # 0.77
97
+ answer["urgent"].noul # probability of "true"
98
+ ```
99
+
100
+ ```python
101
+ verdict = model.classify(
102
+ "Remind me: dentist next Tuesday at 9 am.",
103
+ {"reminder": "an event with a date or time",
104
+ "task": "a list of things to do",
105
+ "idea": "an idea or reflection"},
106
+ threshold=0.6,
107
+ )
108
+ verdict.label, verdict.confidence, verdict.below_threshold
109
+ ```
110
+
111
+ The core is the **superset** of the surfaces. It also exposes lifecycle,
112
+ usage, batch/async, capabilities and structured events:
113
+
114
+ ```python
115
+ from decision_toolkit import DecisionModel, request_context, noul
116
+
117
+ with DecisionModel("gliner2.5-multi", strict=True) as model:
118
+ model.warmup()
119
+ with request_context("req-42"):
120
+ decision = model.decide("Customer wants a refund today.",
121
+ {"urgent": noul("Does it need a reply today?")})
122
+ batch = model.decide_many(["first state", "second state"],
123
+ {"urgent": noul("Today?")})
124
+
125
+ decision["urgent"].confidence # normalized probability
126
+ decision.usage.to_dict() # input/output tokens + estimated flag
127
+ decision.latency_ms # per-decision latency
128
+ decision.request_id # 'req-42'
129
+ decision.truncated, decision.warnings
130
+ ```
131
+
132
+ `strict=True` turns silent truncation into `TruncationError`. Capabilities are
133
+ validated before inference (`box`/assets are rejected by text-only models).
134
+ Custom adapters can be registered with `register_adapter`; local ONNX models
135
+ can be opened with `DecisionModel.from_onnx(...)`. See
136
+ [docs/CORE.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/CORE.md) for the full phase 1 contract, error taxonomy,
137
+ logging events and the expressiveness inventory.
138
+
139
+ **2. As a CLI**
140
+
141
+ ```bash
142
+ dtk models list --adapter --quant # catalog, variants and cache state
143
+ dtk models variants gliner2.5-multi --json # real variants (quant, size, role)
144
+ dtk models download gliner2.5-multi --dry-run # show the download first
145
+ dtk models use gliner2.5-multi --variant fp16
146
+ dtk models current --json # includes the current variant
147
+ dtk decide --model gliner2.5-multi \
148
+ --state "Meeting with Acme: 10% discount, sign next week." \
149
+ --questions-file questions.yaml --json
150
+ dtk decide --model gliner2.5-multi \
151
+ --input-jsonl states.jsonl --questions-file questions.yaml \
152
+ --output-jsonl decisions.jsonl --batch-size 32
153
+ dtk classify --model gliner2.5-multi \
154
+ --text "Dentist next Tuesday at 9" --labels "reminder,task,idea" \
155
+ --threshold 0.6 --json
156
+ dtk config list --json
157
+ dtk models verify --all --json
158
+ ```
159
+
160
+ `dtk` has consistent exit codes (`0` ok, `1` runtime, `2` usage, `3`
161
+ verification), `--json` everywhere, quiet/verbose modes and no tracebacks for
162
+ expected failures. See [docs/CLI.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/CLI.md) for questions files, JSONL
163
+ batch, custom `--adapter/--files`, config and completion.
164
+
165
+ **3. As a REST service (optional extra)**
166
+
167
+ ```bash
168
+ pip install "decision-toolkit[server]"
169
+ dtk serve --model gliner2.5-multi --warmup \
170
+ --api-key "$DECISION_TOOLKIT_API_KEY" \
171
+ --log-format json
172
+ ```
173
+
174
+ Endpoints: `POST /v1/systemone` (System One 1.14), `POST /v1/decisions`
175
+ (OpenAI Decisions beta), `GET /v1/models` (OpenAI shape),
176
+ `GET /v1/decision-models` (rich catalog), `GET /v1/info`, `GET /health`,
177
+ `GET /ready`. Requests may select `model` and `variant`; failures use the OpenAI
178
+ `{"error": {...}}` envelope and every response carries `x-request-id`.
179
+ `TYPESAFE_BASE_URL=http://localhost:8000` makes existing System One clients work.
180
+ See [docs/REST.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/REST.md) for the pinned contracts, limits, auth and
181
+ the standard-evolution process.
182
+
183
+ ## The catalog is curated
184
+
185
+ The catalog is **ours**, not a mirror of HuggingFace. A model enters only when it
186
+ is understood and verified:
187
+
188
+ 1. clean commercial license;
189
+ 2. an adapter whose contract we can implement **from a real export**;
190
+ 3. a real ONNX export on the Hub;
191
+ 4. measured parity against the original on a fixed sample;
192
+ 5. CPU-viable latency.
193
+
194
+ Every variant points to files that exist. No placeholders, no speculative
195
+ adapters (there is no generic "any ONNX" adapter).
196
+
197
+ | id | adapter | status | default variant |
198
+ | --- | --- | --- | --- |
199
+ | `gliner2.5-multi` | `gliner2` | **m0** — verified end-to-end (9/15, 353 ms) | `fp16` (577 MB) |
200
+ | `gliner2.5-decide` | `gliner2-decide` | **m0** — fused export verified (refund 0.944, 912 ms; English-only) | `q4f16` (524 MB) |
201
+ | `laya` | `laya` | **m0** — verified against torch (Δp ≤ 0.005) | `q8` (528 MB) |
202
+ | `julia-1` | `julia` | **m0** — verified: 100/100 argmax parity, 329 ms | `fp32` (618 MB) |
203
+ | `opendecider-nano` | `opendecider` | **m0** — official build; prompt ported from pinned code (530 ms) | `q8f16` (469 MB) |
204
+
205
+ **Only `m0` models are in the catalog.** Researched-but-unavailable models
206
+ (planned, conversion pending, broken exports) are listed in
207
+ [docs/ROADMAP.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/ROADMAP.md) and are not downloadable through the toolkit.
208
+
209
+ Per-model cards with the contract, measured evidence and known limitations:
210
+ [docs/models](https://github.com/gia-uh/decision-toolkit/tree/main/docs/models).
211
+
212
+ ## Variants and quantization
213
+
214
+ A quantization is a **variant** of a model, with its real repo, files and role:
215
+
216
+ ```yaml
217
+ gliner2.5-multi:
218
+ fp16: encoder_fp16.onnx + classifier_fp32.onnx # 577 MB default
219
+ fp32: encoder_fp32.onnx + classifier_fp32.onnx # 1132 MB quality
220
+ ```
221
+
222
+ - `dtk models download <id>` gets the **default** variant; `--variant fp32` gets
223
+ another.
224
+ - A variant becomes default only after passing the parity gate (top-1 ≥ 99%,
225
+ mean |Δp| ≤ 0.02, accuracy delta ≤ 1 pp against the fp32 build on a fixed
226
+ sample) and measuring latency on CPU.
227
+ - Unverified variants are never default. No silent fallback.
228
+
229
+ ## Operations
230
+
231
+ The server has bounded inference concurrency (`--max-concurrent`), an optional
232
+ per-key rate limiter (`--rate-limit`, 429 + `Retry-After`), Prometheus metrics
233
+ at `/metrics`, and optional OpenTelemetry spans (`--tracing`). Saturation
234
+ returns 503 `server_busy` instead of building an unbounded queue. State and
235
+ question text never become log/metric labels.
236
+
237
+ ```bash
238
+ dtk serve --model gliner2.5-multi --warmup --max-concurrent 4 \
239
+ --rate-limit 120 --rate-window 60
240
+ PYTHONPATH=src python bench/load.py --base-url http://127.0.0.1:8000 \
241
+ --concurrency 10 --requests 30
242
+ ```
243
+
244
+ See [docs/OPERATIONS.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/OPERATIONS.md) for metric names, alerts, the
245
+ runbook and the multi-worker caveat.
246
+
247
+ ## Reproducibility
248
+
249
+ Every catalog variant is pinned to a real HuggingFace **commit SHA** and carries
250
+ a **SHA256 per file**, including external `.onnx.data` files. Downloads reject
251
+ mismatches and verification treats the catalog hashes as the source of truth:
252
+
253
+ ```bash
254
+ dtk models verify --all --json # exit 3 if any file/hash/session fails
255
+ ```
256
+
257
+ Latency/parity/calibration evidence is reproducible with the harness in
258
+ [`bench/`](https://github.com/gia-uh/decision-toolkit/tree/main/bench) and documented in [docs/BENCH.md](https://github.com/gia-uh/decision-toolkit/blob/main/docs/BENCH.md). CI runs
259
+ lint, unit/contract tests and a CLI smoke from source on Python 3.12/3.13.
260
+
261
+ There is **no wheel or PyPI release yet**; packaging is a later milestone. See
262
+ [CHANGELOG.md](https://github.com/gia-uh/decision-toolkit/blob/main/CHANGELOG.md), [CONTRIBUTING.md](https://github.com/gia-uh/decision-toolkit/blob/main/CONTRIBUTING.md),
263
+ [SECURITY.md](https://github.com/gia-uh/decision-toolkit/blob/main/SECURITY.md) and
264
+ [THIRD_PARTY_NOTICES.md](https://github.com/gia-uh/decision-toolkit/blob/main/THIRD_PARTY_NOTICES.md).
265
+
266
+ ## Custom ONNX (at your own risk)
267
+
268
+ You can run any ONNX file with an adapter you trust — outside the curated
269
+ catalog:
270
+
271
+ ```python
272
+ model = DecisionModel.from_onnx(
273
+ "./my-model",
274
+ adapter="gliner2", # the layout you declare
275
+ files={"encoder": "encoder.onnx", "classifier": "classifier.onnx",
276
+ "tokenizer": "tokenizer.json"},
277
+ )
278
+ # no parity, no license check, no support
279
+ ```
280
+
281
+ Third-party layouts can be registered in-process:
282
+
283
+ ```python
284
+ from decision_toolkit import register_adapter
285
+ register_adapter("my-layout", MyAdapter)
286
+ ```
287
+
288
+ Custom models do not appear in `dtk models list`; the toolkit never guesses a
289
+ layout. If a model deserves support, it goes through curation.
290
+
291
+ ## Development
292
+
293
+ ```bash
294
+ python -m venv .venv && . .venv/bin/activate
295
+ pip install pytest ruff PyYAML huggingface_hub fastapi httpx pydantic numpy
296
+ PYTHONPATH=src pytest -q # fast suite (no model downloads)
297
+ PYTHONPATH=src pytest -m integration -o addopts="" # real model, opt-in
298
+ ```
299
+
300
+ No wheel or editable install is required while the project is pre-distribution.
301
+ CI runs the same source-tree commands on Python 3.12/3.13. See
302
+ [CONTRIBUTING.md](https://github.com/gia-uh/decision-toolkit/blob/main/CONTRIBUTING.md).
303
+
304
+ ## License
305
+
306
+ MIT — see [LICENSE](https://github.com/gia-uh/decision-toolkit/blob/main/LICENSE).