Skip to main content

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:

  1. clean commercial license;
  2. an adapter whose contract we can implement from a real export;
  3. a real ONNX export on the Hub;
  4. measured parity against the original on a fixed sample;
  5. 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 fp32 gets 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)

Source distribution for decision-toolkit 0.1.0
File Size Uploaded Provenance
decision_toolkit-0.1.0.tar.gz 134.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for decision-toolkit 0.1.0
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page