SB Decision Model (base)
What it is. A 151M-parameter decision model by Sarathi Balakrishnan, trained, calibrated, compressed to 159 MB and
packaged to run inside a browser tab. Give it a text and your own list of options, each written as
name: when to choose it, and it returns a calibrated probability per option in one pass. Nothing is retrained per
use case: the option definitions are the policy. A reserved ask option is trained to win when the deciding fact is
absent from the text, so irreversible actions can be routed to a person instead of guessed.
What it is not. Not an LLM: it never generates text. No multi-step reasoning, arithmetic, counting, or world knowledge beyond what the text says. English only. It is the router / guardrail / triage layer in front of an LLM, not a replacement for one.
How it runs. In the browser tab: downloaded once and cached, GPU when the browser has one, CPU otherwise, inference off the main thread, zero network requests after load. The browser build is verified against the original model on every release. The same weights serve Node, Python, and an HTTP endpoint through one API.
Get it
| Python | pip install sb-decision · https://pypi.org/project/sb-decision/ |
| JavaScript, React, Node | npm install sb-decision · https://www.npmjs.com/package/sb-decision |
| Weights | this repository, public: SBDecision.load() fetches them with no token |
| Author | https://huggingface.co/sarathi-balakrishnan |
from sb_decision import SBDecision
m = SBDecision.load() # sarathi-balakrishnan/browser-decision-model-base, no token needed
print(m.decide("Package arrived broken, second time this month. I want a manager.",
["refund: customer wants money back for a damaged, wrong, or missing item",
"replace: customer wants the product again",
"escalate: angry, threatens to leave, or asks for a manager"]))
# {'decision': 'escalate', 'band': 'auto', 'scores': {'escalate': 0.90, 'ask': 0.09, 'refund': 0.01, 'replace': 0.01}}
import { SBDecision } from "sb-decision";
const m = await SBDecision.load(); // browser (cached after the first load) or Node, no token needed
const r = await m.decide("Package arrived broken, second time this month. I want a manager.", [
"refund: customer wants money back for a damaged, wrong, or missing item",
"replace: customer wants the product again",
"escalate: angry, threatens to leave, or asks for a manager",
]);
// { decision: "escalate", band: "auto", scores: { escalate: 0.90, ask: 0.09, refund: 0.01, replace: 0.01 }, ms: 24 }
band is auto at or above 0.7, review at or above 0.4, otherwise abstain; if ask wins it is always
abstain. Both thresholds are arguments, not model constants. Independent tags: mode="multi". Many texts in one
pass: scores_many / scoresMany.
Results
| split | n | top1_agreement | auroc_macro | ece | brier | ask_recall | false_ask_rate |
|---|---|---|---|---|---|---|---|
| val | 6738 | 0.826 | 0.979 | 0.035 | 0.034 | 0.725 | 0.010 |
| heldout | 4745 | 0.765 | 0.947 | 0.030 | 0.054 | 0.752 | 0.024 |
Per held-out domain:
| domain | n | top1_agreement | auroc_macro | ece | brier | ask_recall | false_ask_rate |
|---|---|---|---|---|---|---|---|
| airline-disruptions | 952 | 0.844 | 0.974 | 0.032 | 0.040 | 0.709 | 0.006 |
| open-source-issue-triage | 936 | 0.745 | 0.953 | 0.035 | 0.057 | 0.829 | 0.034 |
| real-estate-lead-routing | 950 | 0.730 | 0.925 | 0.066 | 0.060 | 0.619 | 0.017 |
| restaurant-reservations | 952 | 0.747 | 0.953 | 0.070 | 0.059 | 0.798 | 0.028 |
| veterinary-intake | 955 | 0.759 | 0.935 | 0.043 | 0.057 | 0.808 | 0.036 |
Held-out domains (never in training or validation): veterinary-intake, real-estate-lead-routing, airline-disruptions, open-source-issue-triage, restaurant-reservations. Re-measured by an independent validation pass.
Latency on an M3 laptop: 38 ms per short decision in the browser (88 ms at 512 tokens), 80 ms p50 on the HTTP endpoint for an 80-word request.
Targets not met by this release, reported rather than hidden: held-out top-1 0.765 against a 0.80 target (the reference labels agree with themselves 0.883 of the time, so 0.765 is 87% of what the metric can measure); option-order invariance max |dp| 0.23 against 0.01 (mean 0.015). Met: calibration error 0.030 against 0.05, ask recall 0.752 against 0.75, false ask 0.024 against 0.10.
Variants
| model | params | held-out top-1 | where it runs |
|---|---|---|---|
| SB Decision Model base | 151M | 0.765 | browser and server |
| SB Decision Model large | 399M | 0.796 | server |
Same input format and API; the large model is the one to use when download size does not matter.
Known weaknesses
- Decides from the text, not the world; cannot look anything up.
- Weak at math, counting, date arithmetic, multi-hop conditions.
- Options are matched by the meaning of the definition; a vague definition gives a vague score. Describe what the text says, not what the agent should do.
askmeans "not enough information to choose between these options". It does not check whether the chosen action has its parameters: "Send the updated contract to the client" is a confident send even with no client named. Keep the option namedaskwith its definition "not enough information to decide".- Single-choice mode is overconfident when two options genuinely apply; use independent mode when options are not mutually exclusive.
- English only in this release.
Data
No real people, companies, emails, or phone numbers appear in the training data.
License
Apache-2.0. See NOTICE.
Citation
@software{sbdm2026, author={Balakrishnan, Sarathi}, title={SB Decision Model: a decision model with user-supplied options that runs in the browser}, year={2026}, url={https://huggingface.co/sarathi-balakrishnan/browser-decision-model-base}}
- Downloads last month
- 12