decision-toolkit
Local-first toolkit around open-weights decision models: typed choice /
yes-no (noul) / score questions over a state (text or JSON), answered with a
calibrated probability for every option — no generated text, no parsing.
One ONNX engine (onnxruntime), in-process, on CPU. One adapter per model
contract (how a model is fed and read). Three thin surfaces over the same API:
library, CLI and REST.
Co-developed by GIA-UH and Syalia S.R.L..
Documentation: https://uhgia.org/decision-toolkit/. Start with
Examples for the library, REST
and CLI. dtk --help and every subcommand help also include runnable examples.
Installation
uv pip install decision-toolkit # library + CLI (0.1.6 on PyPI)
uv pip install "decision-toolkit[onnx]" # + onnxruntime, tokenizers (the model runtime)
uv pip install "decision-toolkit[server]" # + REST server (FastAPI)
The project is managed with uv and ships a committed uv.lock. Published
wheels remain compatible with pip for environments that do not use uv.
PyPI: https://pypi.org/project/decision-toolkit/0.1.6/. See docs/RELEASE.md for the Trusted Publishing flow and clean-install checks.
Three ways to use it
1. As a library (primary interface)
from decision_toolkit import DecisionModel, choice, noul, score
# download once: dtk models download gliner2.5-multi
model = DecisionModel("gliner2.5-multi")
answer = model.decide(
state="Meeting with Acme: we agreed a 10% discount and will sign next week.",
questions={
"cat": choice("What kind of record is this?", {
"meeting": "a meeting or call with topics, decisions and actions",
"tasks": "things to do, with dates or owners",
"contact": "a person's details: name, role, company, phone, email",
"idea": "an idea, reflection, observation or question",
"other": "a greeting, small talk or anything else",
}),
"urgent": noul("Does it need a reply today?"),
},
)
answer["cat"].choice # 'meeting'
answer["cat"].confidence # 0.77
answer["urgent"].noul # probability of "true"
verdict = model.classify(
"Remind me: dentist next Tuesday at 9 am.",
{"reminder": "an event with a date or time",
"task": "a list of things to do",
"idea": "an idea or reflection"},
threshold=0.6,
)
verdict.label, verdict.confidence, verdict.below_threshold
The core is the superset of the surfaces. It also exposes lifecycle, usage, batch/async, capabilities and structured events:
from decision_toolkit import DecisionModel, request_context, noul
with DecisionModel("gliner2.5-multi", strict=True) as model:
model.warmup()
with request_context("req-42"):
decision = model.decide("Customer wants a refund today.",
{"urgent": noul("Does it need a reply today?")})
batch = model.decide_many(["first state", "second state"],
{"urgent": noul("Today?")})
decision["urgent"].confidence # normalized probability
decision.usage.to_dict() # input/output tokens + estimated flag
decision.latency_ms # per-decision latency
decision.request_id # 'req-42'
decision.truncated, decision.warnings
strict=True turns silent truncation into TruncationError. Capabilities are
validated before inference (box/assets are rejected by text-only models).
Custom adapters can be registered with register_adapter; local ONNX models
can be opened with DecisionModel.from_onnx(...). See
docs/CORE.md for the full phase 1 contract, error taxonomy,
logging events and the expressiveness inventory.
2. As a CLI
dtk models list --quant # every variant, default, cache state
dtk models variants gliner2.5-multi --json # real variants (quant, size, role)
dtk models download gliner2.5-multi --variant fp32 # install another quantization
dtk models use gliner2.5-multi --variant fp32 # select it persistently
dtk models current --json # includes the current variant
dtk decide --model gliner2.5-multi \
--state "Meeting with Acme: 10% discount, sign next week." \
--questions-file questions.yaml --json
dtk decide --model gliner2.5-multi \
--input-jsonl states.jsonl --questions-file questions.yaml \
--output-jsonl decisions.jsonl --batch-size 32
dtk classify --model gliner2.5-multi \
--text "Dentist next Tuesday at 9" --labels "reminder,task,idea" \
--threshold 0.6 --json
dtk config list --json
dtk models verify --all --json
dtk has consistent exit codes (0 ok, 1 runtime, 2 usage, 3
verification), --json everywhere, quiet/verbose modes and no tracebacks for
expected failures. See docs/CLI.md for questions files, JSONL
batch, custom --adapter/--files, config and completion.
3. As a REST service (optional extra)
uv pip install "decision-toolkit[server]"
dtk serve --model gliner2.5-multi --warmup \
--api-key "$DECISION_TOOLKIT_API_KEY" \
--log-format json
Endpoints: POST /v1/systemone (System One 1.14), POST /v1/decisions
(OpenAI Decisions beta), GET /v1/models (OpenAI shape),
GET /v1/decision-models (rich catalog), GET /v1/info, GET /health,
GET /ready. Requests may select model and variant; failures use the OpenAI
{"error": {...}} envelope and every response carries x-request-id.
TYPESAFE_BASE_URL=http://localhost:8000 makes existing System One clients work.
See docs/REST.md for the pinned contracts, limits, auth and
the standard-evolution process.
The catalog is curated
The catalog is ours, not a mirror of HuggingFace. A model enters only when it is understood and verified:
- clean commercial license;
- an adapter whose contract we can implement from a real export;
- a real ONNX export on the Hub;
- measured parity against the original on a fixed sample;
- CPU-viable latency.
Every variant points to files that exist. No placeholders, no speculative adapters (there is no generic "any ONNX" adapter).
| id | adapter | status | variants (default *) |
|---|---|---|---|
gliner2.5-multi |
gliner2 |
m0 — verified end-to-end (9/15, 353 ms) | fp16* (577 MB); fp32 (1132 MB) |
gliner2.5-decide |
gliner2-decide |
m0 — fused export verified (refund 0.944, 912 ms; English-only) | q4f16* (524 MB) |
laya |
laya |
m0 — verified against torch (Δp ≤ 0.005) | q8* (528 MB) |
julia-1 |
julia |
m0 — verified: 100/100 argmax parity, 329 ms | fp32* (618 MB) |
opendecider-nano |
opendecider |
m0 — official build; prompt ported from pinned code (530 ms) | q8f16* (469 MB); fp16 (792 MB) |
Only m0 models are in the catalog. Researched-but-unavailable models
(planned, conversion pending, broken exports) are listed in
docs/ROADMAP.md and are not downloadable through the toolkit.
Per-model cards with the contract, measured evidence and known limitations: docs/models.
Variants and quantization
A quantization is a variant of a model: a real, pinned ONNX build with its
own repo files, size and role. A * in the table above marks the default.
dtk models list always shows every catalogued variant and what is cached:
dtk models list # all variants; * = default
dtk models list --quant # add the quantization labels
dtk models variants gliner2.5-multi # full details for one model
dtk models download fetches a variant into the model cache; it does not change
the active selection. Downloading one quantization does not remove the others:
dtk models download gliner2.5-multi # default fp16
dtk models download gliner2.5-multi --variant fp32 # quality build
dtk models download opendecider-nano --variant fp16 # quality build
dtk models download gliner2.5-multi --variant fp32 --dry-run
Select a downloaded variant persistently, or use it for one request:
dtk models use gliner2.5-multi --variant fp32
dtk models current --json # {"current": "...", "variant": "fp32"}
dtk decide --model gliner2.5-multi --variant fp16 --state "..." --q 'cat=choice:a|b'
From Python: DecisionModel("gliner2.5-multi", variant="fp32"). The variant
must already be downloaded. If a variant is omitted, the catalog default is
used unless dtk models use selected another one.
A variant becomes default only after passing the parity gate (top-1 ≥ 99%, mean |Δp| ≤ 0.02, accuracy delta ≤ 1 pp against the fp32 build on a fixed sample) and measuring latency on CPU. Unverified conversions are not catalogued and never become a silent fallback. See Variants and quantization for the complete table, cache behavior and commands.
Operations
The server has bounded inference concurrency (--max-concurrent), an optional
per-key rate limiter (--rate-limit, 429 + Retry-After), Prometheus metrics
at /metrics, and optional OpenTelemetry spans (--tracing). Saturation
returns 503 server_busy instead of building an unbounded queue. State and
question text never become log/metric labels.
dtk serve --model gliner2.5-multi --warmup --max-concurrent 4 \
--rate-limit 120 --rate-window 60
uv run python bench/load.py --base-url http://127.0.0.1:8000 \
--concurrency 10 --requests 30
See docs/OPERATIONS.md for metric names, alerts, the runbook and the multi-worker caveat.
Reproducibility
Every catalog variant is pinned to a real HuggingFace commit SHA and carries
a SHA256 per file, including external .onnx.data files. Downloads reject
mismatches and verification treats the catalog hashes as the source of truth:
dtk models verify --all --json # exit 3 if any file/hash/session fails
Latency/parity/calibration evidence is reproducible with the harness in
bench/ and documented in docs/BENCH.md. CI runs
lint, unit/contract tests and a CLI smoke from source on Python 3.12/3.13.
Wheel, sdist and Trusted Publishing releases are documented in RELEASE.md. See CHANGELOG.md, CONTRIBUTING.md, SECURITY.md and THIRD_PARTY_NOTICES.md.
Custom ONNX (at your own risk)
You can run any ONNX file with an adapter you trust — outside the curated catalog:
model = DecisionModel.from_onnx(
"./my-model",
adapter="gliner2", # the layout you declare
files={"encoder": "encoder.onnx", "classifier": "classifier.onnx",
"tokenizer": "tokenizer.json"},
)
# no parity, no license check, no support
Third-party layouts can be registered in-process:
from decision_toolkit import register_adapter
register_adapter("my-layout", MyAdapter)
Custom models do not appear in dtk models list; the toolkit never guesses a
layout. If a model deserves support, it goes through curation.
Development
uv sync --all-extras # locked dev, ONNX, server and tracing env
uv run pytest -q # fast suite (no model downloads)
uv run pytest -m integration -o addopts="" # real model, opt-in
uv sync installs the project in editable mode and resolves every dependency
from uv.lock. CI runs the same commands on Python 3.12/3.13. See
CONTRIBUTING.md.
License
MIT — see LICENSE.
Metadata
Release files for decision-toolkit 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| decision_toolkit-0.1.6.tar.gz | 277.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| decision_toolkit-0.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 373.8 kB
Release files / decision_toolkit-0.1.6.tar.gz
| Download URL | decision_toolkit-0.1.6.tar.gz |
|---|---|
| Size | 277.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c0a470ec18391f990b637d1820e7dddbfaf2e99283576e801076467284df533
|
|
BLAKE2b-256 checksum How to use checksums |
24ceba0bd3b244ca3ea3a4a4dcbb693ffa182abded929d3e64033b1ed9df9758
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / decision_toolkit-0.1.6-py3-none-any.whl
| Download URL | decision_toolkit-0.1.6-py3-none-any.whl |
|---|---|
| Size | 96.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a8c83df3ef84d52e1485383a8badd0f143671ca6b60ce5ec85a0f5ea9322123
|
|
BLAKE2b-256 checksum How to use checksums |
3738dba78f46724305e340afdbb699a65948f7701ac5f4657881a1d9204da49c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for decision-toolkit 0.1.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for decision_toolkit-0.1.6.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.