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..
Examples: docs/EXAMPLES.md covers the library, REST and
CLI. dtk --help and every subcommand help also include runnable examples.
Installation
pip install decision-toolkit # library + CLI (once v0.1.0 is on PyPI)
pip install "decision-toolkit[onnx]" # + onnxruntime, tokenizers (the model runtime)
pip install "decision-toolkit[server]" # + REST server (FastAPI)
Pre-release: the artifact is built and verified locally, but not uploaded yet. 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 --adapter --quant # catalog, variants and cache state
dtk models variants gliner2.5-multi --json # real variants (quant, size, role)
dtk models download gliner2.5-multi --dry-run # show the download first
dtk models use gliner2.5-multi --variant fp16
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)
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 | default variant |
|---|---|---|---|
gliner2.5-multi |
gliner2 |
m0 — verified end-to-end (9/15, 353 ms) | fp16 (577 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) |
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, with its real repo, files and role:
gliner2.5-multi:
fp16: encoder_fp16.onnx + classifier_fp32.onnx # 577 MB default
fp32: encoder_fp32.onnx + classifier_fp32.onnx # 1132 MB quality
dtk models download <id>gets the default variant;--variant fp32gets another.- 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 variants are never default. No silent fallback.
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
PYTHONPATH=src 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.
There is no wheel or PyPI release yet; packaging is a later milestone. 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
python -m venv .venv && . .venv/bin/activate
pip install pytest ruff PyYAML huggingface_hub fastapi httpx pydantic numpy
PYTHONPATH=src pytest -q # fast suite (no model downloads)
PYTHONPATH=src pytest -m integration -o addopts="" # real model, opt-in
No wheel or editable install is required while the project is pre-distribution. CI runs the same source-tree commands on Python 3.12/3.13. See CONTRIBUTING.md.
License
MIT — see LICENSE.
Metadata
Release files for decision-toolkit 0.1.0
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.0.tar.gz | 134.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| decision_toolkit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 229.5 kB
Release files / decision_toolkit-0.1.0.tar.gz
| Download URL | decision_toolkit-0.1.0.tar.gz |
|---|---|
| Size | 134.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
914b2b50fa3672bca938f8d1f4b7f12f2b5125ca4d2494d2c13a60a6906de24b
|
|
BLAKE2b-256 checksum How to use checksums |
92fb023b70d9cfedf1a4fd9551144025f63b77abc66f4d000e1e3ae7d46424b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|
Release files / decision_toolkit-0.1.0-py3-none-any.whl
| Download URL | decision_toolkit-0.1.0-py3-none-any.whl |
|---|---|
| Size | 94.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ecf5128c777a134bcbb6b2536d69c935993d4c4510233617fb4dd369482fb30e
|
|
BLAKE2b-256 checksum How to use checksums |
c1e486b7d7003be85e08432db49be55cc2e34f91e334d98de5e65934265bce58
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|
Security for decision-toolkit 0.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.