Skip to main content
ZeroPoint logo

ZeroPoint: energy-based System 1 decision engine


License PyPI Python Docs Tests OpenSSF Best Practices OpenSSF Scorecard


🎯 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


Which team should handle the message: Can I change my delivery address? The options Billing and Technical are energy wells; None of these is the flat ground at energy zero. The text lands on the ground, so ZeroPoint answers none with 0.96, while softmax splits it between billing 0.55 and technical 0.45.

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

Each landscape comes with two encoders: encoder="small" (default, faster) and encoder="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 pass monitor=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:

AUROC against FPR at 95% recall, average of four tests. ZeroPoint sits in the green zone (AUROC 0.92, FPR 0.27); similarity with the same encoder (0.82, 0.58) and Laya (0.78, 0.65) sit in the orange zone, where many cases with no right answer still get an action.
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.


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
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.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 zeropoint 0.1.0
File Size Uploaded Provenance Actions
zeropoint-0.1.0.tar.gz 25.7 MB With provenance Details for zeropoint-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for zeropoint 0.1.0
File Interpreter ABI Platform Actions
zeropoint-0.1.0-py3-none-any.whl Python 3 none any Details for zeropoint-0.1.0-py3-none-any.whl

Total release size: 51.3 MB

Release files / zeropoint-0.1.0.tar.gz

Download URL zeropoint-0.1.0.tar.gz
Size 25.7 MB
Tags Source
SHA-256 checksum
How to use checksums
3ee62ad8ae90c6a9242cbf63072d659eebd01c48500083eb658e192c60a2755d
BLAKE2b-256 checksum
How to use checksums
a0e300e7dfce49c3a0000c907f23196e2ff9b5a2059d97adbbac15adfb1704d2
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 31, 2026.

Transparency log

Release files / zeropoint-0.1.0-py3-none-any.whl

Download URL zeropoint-0.1.0-py3-none-any.whl
Size 25.6 MB
Tags Python 3
SHA-256 checksum
How to use checksums
59d31309cddd737e4c50e245486a0309a551a1396b4690847a4fd30d72ecc6fd
BLAKE2b-256 checksum
How to use checksums
93d5ee59d51c20df34c3783e348821389c9444ce92f6d360bcd70fb78f079b74
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 31, 2026.

Transparency log

Security for zeropoint 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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for zeropoint-0.1.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 Oct 31, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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