Docs
nirnaya takes one state and any number of typed questions, and answers all of them together, in one go. The request and response shapes are the same as Jev's /v1/systemone, so anything written against that API works against the server unchanged.
The three primitives
| type | ask | criteria | you get |
|---|---|---|---|
| choice | pick one option | object: key → description | choice, probabilities, confidence |
| score | pick a level on an ordered rubric | array of level descriptions, low to high | score (expected level), probabilities, confidence |
| noul | is this statement true? | optional {false, true} descriptions | noul = P(true), probabilities, confidence |
Write descriptions the way you would brief a new colleague. The model reads them; it has no other idea what escalate_to_human means. Keep each question atomic and compose in your own code: five small questions beat one question with twelve options.
Request
{
"state": { "thread": [ { "role": "customer", "text": "I was charged twice on the 4th." } ] },
"questions": {
"category": {
"type": "choice",
"instructions": "What is this conversation primarily about?",
"criteria": { "billing": "A charge or payment problem.", "technical": "Something is broken.", "account": "Login or plan changes." }
},
"urgency": {
"type": "score",
"instructions": "How time-sensitive is this?",
"criteria": [ "Not urgent.", "Routine.", "This week.", "Today." ]
},
"refund_owed": { "type": "noul", "instructions": "The customer is owed a refund." }
}
}Response
{
"answers": {
"category": { "type": "choice", "choice": "billing", "confidence": 0.91,
"probabilities": { "billing": 0.91, "technical": 0.05, "account": 0.04 } },
"urgency": { "type": "score", "score": 2.31, "confidence": 0.52,
"probabilities": { "0": 0.03, "1": 0.12, "2": 0.36, "3": 0.49 } },
"refund_owed": { "type": "noul", "noul": 0.78, "confidence": 0.78,
"probabilities": { "false": 0.22, "true": 0.78 } }
}
}confidence is the top probability after temperature scaling. It is the best routing signal we measured (see the model page for the alternatives and why they lost). Treat it as a ranking signal you threshold on your own traffic, not as a guarantee.
Where it runs
// drop the nirnaya bundle into your site, then:
import { loadEngine } from "nirnaya";
const engine = await loadEngine(status => console.log(status));
const answers = await engine.decide(state, questions);
// answers[i] = { qid, answer, confidence, p: number[], options: [{key, text}] }In the browser, nirnaya loads once and your browser keeps it, so every later visit starts instantly. It reads the state a single time and reuses that reading for every question you asked, which is why adding a fifth question costs almost nothing. It uses the graphics card when one is available and falls back automatically when it is not. The server path runs the full-precision model behind your own endpoint: same shapes, and the same answers on all but the closest calls, where the compact browser build can land on the neighbouring option.
Getting the best out of it
- Put the important part first. Very long states are trimmed, so lead with the thing the decision turns on rather than boilerplate.
- Two to ten options per question. That is the sweet spot. If you have thirty categories, ask a coarse question first and a narrow one second - it is faster and sharper than one huge list.
- Many small questions beat one big one. The state is read once and reused, so five focused questions cost barely more than one. Compose the answers in your own code.
- Write the option descriptions properly. They are what it reads. A sentence explaining what
escalate_to_humanactually means is worth more than any amount of prompt tuning. - It judges; it does not deduce. For multi-step reasoning, use it as the fast gate in front of a model that reasons - that is where the cost savings are anyway.
- It is fitted per workflow. The ones in the cookbooks work today; a new one is tuned from your own labelled decisions.