ZeroPoint: energy-based System 1 decision engine
🎯 A decision engine that picks an option when none is right is hallucinating
In a single forward pass, ZeroPoint turns a text into a choice among a list of options, with none of them as one of the possible answers.
Results on CLINC150:
| ZeroPoint | Laya | Jev | Similarity | |
|---|---|---|---|---|
| Latency per decision | 19 ms on a 2-core CPU | 33 ms on a T4 GPU; 193–464 ms on CPU | 70–500 ms | 15 ms on a 2-core CPU |
| Picks the right option when it is on the list | 97% | 95% | not public | 80% |
| Out-of-scope requests still acted on, keeping 95% of the answerable ones | 10% | 32% | not public | 52% |
1,600 CLINC150 cases never seen in training (800 answerable requests and 800 out-of-scope requests, 20 options per case), the same cases for every system measured. Similarity uses the same encoder as ZeroPoint. Latency: ZeroPoint and similarity were measured with the benchmark in this repository; Laya and Jev as published by their authors (Laya, Jev). Full results are in BENCHMARK.md.
Decision engines pick from a list: the support queue for a ticket, the intent behind a request, the route
for a document. A classifier always picks something, even when the right option is not on the list or the
text has nothing to do with the task. ZeroPoint returns the best option or None, and the probability of
none comes out of the same calculation as the probability of each option.
Quick start
pip install zeropoint
ZeroPoint answers one question: which of these options fits this text, if any. It decides with an energy landscape that knows a set of options and is calibrated on real texts. Two landscapes come with the package.
The first call downloads the encoder from the Hugging Face Hub (about 130 MB) and takes a few seconds; later calls take milliseconds. The full documentation is at javihaus.github.io/zeropoint.
Default energy landscape
ZeroPoint.load() loads the default landscape, everyday: 150 kinds of everyday requests to an assistant.
Case 1: one of the options is right.
from zeropoint import ZeroPoint
zp = ZeroPoint.load()
zp.decide("Can you move 200 euros from my savings to my checking account?",
options=["transfer", "balance", "pay bill"])
Output:
transfer 0.98 ███████████████████▋
balance 0.01 ▏
pay bill 0.00
none 0.01 ▏
→ transfer · 9 ms
Case 2: none of the options is right.
zp.decide("Set an alarm for 6:30 tomorrow morning",
options=["transfer", "balance", "pay bill"])
Output:
pay bill 0.00
transfer 0.00
balance 0.00
none 1.00 ███████████████████▉
→ none · 8 ms
Support energy landscape
ZeroPoint.load("support") loads a landscape calibrated on customer support tickets. It routes each ticket to
one of 10 queues.
Case 1: three options, and it chooses one.
zp = ZeroPoint.load("support")
zp.decide("Subject: Charged twice for my subscription\n"
"Hello, my credit card was charged twice for the March invoice. Could you refund the duplicate payment?",
options=["Billing and Payments", "Technical Support", "Human Resources"])
Output:
Billing and Payments 0.91 ██████████████████▏
Human Resources 0.00
Technical Support 0.00
none 0.09 █▊
→ Billing and Payments · 14 ms
Case 2: no options given.
When no list is passed, ZeroPoint chooses among all the options the landscape knows, here its 10 queues
(listed in zp.labels). The output shows the five most probable.
zp.decide("Subject: Service down since this morning\n"
"Our dashboard has been unreachable since 8am and all requests time out. "
"Is there an outage or scheduled maintenance?")
Output:
Service Outages and Maintenance 0.83 ████████████████▋
IT Support 0.05 █
Technical Support 0.03 ▌
Human Resources 0.01 ▎
Product Support 0.01 ▏
(+5 more options)
none 0.06 █▏
→ Service Outages and Maintenance · 12 ms
The support landscape knows tickets written in the style of its dataset (a subject line and a body) and treats very different writing as outside what it knows. For your own tickets, calibrate a landscape on your own data.
Energy-based models
ZeroPoint is an energy-based model (LeCun et al., 2006). Instead of scoring options against each other, it gives every option an energy and compares it with a reference state whose energy is fixed. In physics, energy is always measured relative to a reference level, just as a mountain's height is measured from sea level. ZeroPoint fixes that level once: an option counts only if the text sits below it, in that option's valley. A useful image is a ball on a floor with holes: it either drops into a hole (an option) or stays on the flat floor (none of the options).
The step-by-step explanation, with the equations, is in the README on GitHub: the text, the options, the vacuum, Boltzmann's rule, and the edge of the data.
Choosing the energy landscape
A landscape is what ZeroPoint knows: one valley for each option it can choose, and flat ground at energy zero that means none of these. It is calibrated from labeled texts. Energy-based models explains how it decides.
import zeropoint
zeropoint.landscapes() # lists the landscapes that ship with ZeroPoint
Output:
everyday 150 options everyday requests to an assistant: banking, travel, home, work, small talk (default)
support 10 options customer support tickets, routed to a queue
| Landscape | What it decides | Calibrated on |
|---|---|---|
everyday (default) |
150 kinds of everyday requests | CLINC150, 15,000 requests written by people |
support |
10 support queues | a public dataset of 20,000 support tickets |
The
supportlandscape chooses poorly between queues that overlap (38% right on the 2,000-case benchmark). For your own tickets, calibrate a landscape on your data.
Each landscape comes with two encoders:
encoder="small"(default, faster) andencoder="base"(more accurate, slower). A landscape calibrated on your own data loads the same way:ZeroPoint.load(path).
Setting a threshold
Acting on a wrong answer has a cost. A ticket sent to the wrong team waits for days; a request handled by the wrong service delays the answer the customer needs. By default, ZeroPoint returns its most probable answer, even when that answer is only 28% likely. A threshold makes it act only when it is at least that confident and return none otherwise, so that the case can go to a reviewer instead.
zp = ZeroPoint.load()
zp.decide("Write me a poem about the sea", min_confidence=0.5)
Output:
timezone 0.28 █████▋
time 0.05 ▉
text 0.05 ▉
weather 0.04 ▉
what song 0.04 ▊
(+145 more options)
none 0.16 ███▏
→ none · 7 ms
The right threshold depends on the cost of a wrong action. notebooks/calibrate_your_own.ipynb shows how to choose it from your own data.
Calibration
To calibrate your own landscape, you need texts with their right label, a few hundred per label to start. Examples of none are not needed.
pip install "zeropoint[train]" # adds PyTorch, only needed to calibrate
zp = ZeroPoint.fit(texts, labels,
descriptions={"BIL": "billing and invoices"}, # optional, helps with terse labels
catch_all=["Other"]) # optional, labels that fit anything
zp.save("my_model.safetensors")
ZeroPoint.load("my_model.safetensors").decide("...")
The encoder stays frozen and its embeddings are computed once. If the model does not beat chance on held-out
texts by ten percentage points, fit stops with an error instead of returning a model that has not learned.
To monitor training, install
zeropoint[monitor]and passmonitor=True. The ndt library tracks how many dimensions the learned space actually uses; a collapse shows up there even when the loss looks normal.
A complete walk-through, with a held-out check and a table for choosing your threshold, is in notebooks/calibrate_your_own.ipynb.
Accuracy
AUROC measures how well ZeroPoint separates cases that have a right option from cases that have none, on texts it never saw (1 is perfect, 0.5 is chance). Small encoder, default settings:
| Test | ZeroPoint | Similarity, same encoder |
|---|---|---|
| Support tickets vs unrelated texts (reviews, forum posts) | 1.00 | 0.99 |
| Support tickets whose right queue was removed from the list | 0.71 | 0.59 |
| Everyday requests vs out-of-scope requests (CLINC150) | 0.98 | 0.90 |
| Everyday requests whose right intent was swapped for a wrong one | 0.97 | 0.78 |
When the right option is on the list, ZeroPoint chooses about as well as a classifier trained on the same data: 97% on CLINC150 with 20 options per request.
On a second benchmark of 2,000 new cases, against Laya, Semantic Router and a zero-shot NLI model, ZeroPoint
again separates cases with and without a right option best (AUROC 0.89, FPR95 0.44). Laya chooses better when
the right option is on the list (88% against 77%), and ZeroPoint's support landscape chooses the right queue
in only 38% of tickets. Details in BENCHMARK.md.
Latency
On a 2-core cloud CPU, one decision takes about 19 ms with the default settings (small encoder, 8-bit). The base encoder takes about 30 ms. No GPU is needed, and a recent laptop is faster.
The engine itself takes a millisecond or less. Almost all the time goes to the encoder, which runs with
ONNX Runtime in 8-bit precision by default (precision="fp32" for full
precision). Details are in BENCHMARK.md.
Using ZeroPoint in your code
A common application is to route the case automatically when ZeroPoint is confident, and to pass it to a person when it is not:
d = zp.decide(text, options=["transfer", "balance", "pay bill"])
if d.choice is None:
send_to_person(text) # none of the options fits, or the confidence is below the threshold
else:
route(text, d.choice) # e.g. "transfer"
| Field | What it holds | Use it to |
|---|---|---|
choice |
the chosen option, or None |
act, or hand the case to a person |
confidence |
probability of choice |
log it, or set your own rule |
p_none |
probability that none of the options fits | find requests your system does not cover |
probabilities |
one probability per option | show the runner-up, or rank the options |
beyond_edge |
how far the text is from anything the landscape has seen (zero or below: familiar) | detect unusual texts, or a change in the kind of requests you receive |
latency_ms |
time the decision took | monitor speed in production |
unseen_options |
options the landscape was not calibrated on | treat their probabilities with more care |
truncated |
True if the text was longer than 512 tokens and its end was ignored |
split long texts, or summarize them first |
energies |
energy of the text in each option's valley | inspect how the decision was made |
For many texts at once, use decide_many. It takes the same arguments, plus one list of options per text if
they differ, and returns one decision per text:
decisions = zp.decide_many(texts, options=["transfer", "balance", "pay bill"])
References
- LeCun, Y., Chopra, S., Hadsell, R., Ranzato, M., & Huang, F. (2006). A tutorial on energy-based learning. Predicting Structured Data, 1(0).
- Lee, K., Lee, K., Lee, H., & Shin, J. (2018). A simple unified framework for detecting out-of-distribution samples and adversarial attacks. Advances in Neural Information Processing Systems, 31.
- Mahalanobis, P. C. (1936). On the generalised distance in statistics. Reprinted in Sankhya A, 80 (Suppl 1), 1–7 (2018). https://doi.org/10.1007/s13171-019-00164-5
- van Amersfoort, J., Smith, L., Teh, Y. W., & Gal, Y. (2020). Uncertainty estimation using a single deep deterministic neural network. In International Conference on Machine Learning (pp. 9690–9700). PMLR.
- Yalovetzky, R., Balestriero, R., & LeCun, Y. (2026). LeHami: Symplectic latent dynamics for world models. arXiv preprint arXiv:2610.15267.
Citation
@software{marin_zeropoint_2026,
author = {Marín, Javier},
title = {ZeroPoint: an energy-based decision engine},
year = {2026},
url = {https://github.com/Javihaus/zeropoint}
}
License
Apache 2.0. The encoders are downloaded from the Hugging Face Hub at first use and keep their own MIT license.
Copyright Javier Marín, 2026
Metadata
Release files for zeropoint 0.2.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 | Actions |
|---|---|---|---|---|
| zeropoint-0.2.0.tar.gz | 25.7 MB | With provenance | Details for zeropoint-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| zeropoint-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for zeropoint-0.2.0-py3-none-any.whl |
Total release size: 51.3 MB
Release files / zeropoint-0.2.0.tar.gz
| Download URL | zeropoint-0.2.0.tar.gz |
|---|---|
| Size | 25.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7a20e746ef07d95e3b97c2a54c717f0b444a978a69212f10349883076edde363
|
|
BLAKE2b-256 checksum How to use checksums |
cae266f0054f4fa4dc53b54f19a1f88a1e4b553f6d66c6d52fffbede19312e2f
|
| 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 Nov 2, 2026.
Transparency logRelease files / zeropoint-0.2.0-py3-none-any.whl
| Download URL | zeropoint-0.2.0-py3-none-any.whl |
|---|---|
| Size | 25.7 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
acbbef90a7044cdb2e173e9a3b22bb52c986e9ea20312f28ec8e78a6342ec242
|
|
BLAKE2b-256 checksum How to use checksums |
fa7b3e12c72c393e390212c097083ba930483a2a86b2c9b90d86613a9d0e9c96
|
| 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 Nov 2, 2026.
Transparency logSecurity for zeropoint 0.2.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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for zeropoint-0.2.0.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 Nov 2, 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.