classifyPost function that returns an exclusive category, its confidence, and a probability per tag for one item, then run it over a batch and choose thresholds and a budget from measured results.
Outcome: Your app labels items with a category and a set of tags at a threshold you chose from data, stays inside the provider’s rate limits, and logs cost per 1,000 items from usage.cost.
1. Build the label set as Jev questions
The first thing to do after defining the label set is turn those labels into questions. Jev answers questions about the item you put instate. First is the single choice category question: categories that never overlap go into one choice question that picks exactly one from the list.
The rest are tags that may be present or absent. Each of those gets its own noul question that is answered with a probability between 0 and 1. The odds that one tag applies are judged independently of the others. Writing your labels this way lets one request return the category and full tag probabilities at once.
Write each
noul instruction as a yes-or-no question about the item and put the category definitions in the choice question’s criteria rather than in its instruction text. Jev reads the item from state, so the question refers to the post and never repeats the text. If your categories might not cover every item, add an other entry to both CATEGORIES and criteria, since a choice always picks one of the options you list and the step 2 schema accepts only values from CATEGORIES. The six TweetTopic categories above cover every item in that dataset, so the captured run omits it.2. Classify one item with one Decisions request
A per-item classification calls the Decisions endpoint, passes it the post and the questions, checks that its response looks right before trusting it, and returns the category, its confidence, the tag probabilities, and the usage figures. Rate limit, server error, or dropped connection throws aRetryableError so step 3 can back off and try again. Anything else throws a plain error so the item fails instead of repeating.
The Decisions schema marks confidence and usage.cost as optional. Missing confidence stays undefined, and step 4 will route those items to review. If there’s no usage.cost, then the response is rejected rather than being counted as free.
3. Run the Jev batch concurrently within rate limits
Take a few thousand items and run them a single request at a time and you’ll be waiting minutes. Obviously, you’ll want to run your requests in parallel. You could start them all at once, but you’ll get rate limited.runBatch solves this with a fixed number of workers that take items from the list one at a time.
withRetry detects transient errors from step 2 and waits a while so they can get resolved. All workers wait together, and they’ll all resume at the same time when one request is told to slow down. On a terminal failure, other workers stop taking new items and runBatch rejects once all in-flight requests are done. Your results come back in the same order as your input list.
withRetry understands a Retry-After header that comes back in the response. It will use the numeric value as the wait and ignore an HTTP-date value. Otherwise, it’ll back off from 1 second with some jitter until it hits a 30-second cap.
Start with 8 workers, which is what the captured run used with no 429s. Raise the worker count while a full batch completes with no 429s, and halve it when retries show up in your logs.
4. Pick tag thresholds from a human-labeled sample
Anoul probability only becomes a tag once it crosses a threshold, and the threshold level differs for each tag. Here’s a procedure for learning appropriate thresholds for each tag. Label 100 to 200 representative items by hand, and use runBatch to classify them in the same order that you used when labeling them. Then compute precision and recall for the different tags at each of several candidate thresholds. Raising the threshold increases precision at the expense of recall. If you can afford to miss a few items that should have the tag, set the lowest threshold where you are satisfied with the precision. If you can’t afford to miss the items, set the highest threshold whose recall you can accept.
precisionRecall returns undefined instead of a score when a threshold predicts no positives or when the sample contains no positive examples for a tag. In that case you’ll want to label more items rather than pick a threshold from an undefined score.
The choice answer needs no threshold since its confidence tells you which items to hand to a person. Group the sample by confidence band, compare the category with the human label in each band, and send to review anything below the band that meets your accuracy bar. Run this calibration once, separately from the production batch in step 3, since both are paid.
When a tag’s precision stays low at every threshold, fix the question rather than the threshold. In the captured run,
celebrity never passed 0.41 precision because its instruction, Is the post about a celebrity or pop culture?, overlaps with music and film_tv. Tighten the instruction or the criteria and re-run the sample before shipping that tag.5. Compute Jev cost per 1,000 items from returned usage
Budgeting a larger batch means summing upusage.cost for each result, and scaling to 1,000 items, all plain arithmetic on real numbers. And you keep the tokens per item next to the scaled cost so that you can predict the bill for more text of the same shape before actually running it.
Worked example
Captured output. The code above ran unchanged on 2026-09-21 against thetest_2021 split of TweetTopic, a public dataset of tweets with human-assigned topic labels (Antypas et al., COLING 2022). The six TAGS map to six of its nineteen multi-label topics, and CATEGORIES map to the six exclusive classes of its single-label companion set. Items 1 to 150 were the calibration sample and items 151 to 550 were the batch, with concurrency of 8. Usernames and URLs appear as {@name@}, {{USERNAME}}, and {{URL}} because the dataset masks them.
Below is one batch item with human labels (category pop_culture, tags film_tv and music) and with output reduced to the fields classifyPost returns.
precision/recall at different thresholds, with true positives, false positives, and false negatives as tp/fp/fn in parentheses. sports was clean at any threshold. music traded 12 recall points for 26 precision points between threshold 0.5 and 0.8. news and film_tv could not reach high recall at any threshold with these instructions. celebrity could not reach high precision.
choice answer matched the human category on 127 of 150 calibration items. Confidence splits here are at 0.8 or above: 114 of 122 matched; at 0.5 to below 0.8: 9 of 18; below 0.5: 4 of 10. A review threshold of 0.8 on confidence would have sent the 28 items below it to a person. On the 400-item batch it got 350 of 400 correct.
The cost and timing from costPerThousand on that batch run are shown below. 400 requests took 8.6 seconds at 8 workers with no retries. The whole 550-item run cost $0.0141.
output_tokens were not billed. Longer items or more tags raise tokensPerItem, so measure on your own sample before quoting a budget.
Check your work
The following are things to check about the behavior you’ve implemented.- Decisions response.
answers.category.typeequals'choice'andanswers.category.choiceis one of the sixCATEGORIES. Each tag key hastypeequal to'noul'andnoulbetween 0 and 1.usage.costis a number in USD andusage.output_tokensadds nothing to it. - Endpoint and model. Requests reach
https://openrouter.ai/api/alpha/decisionswithmodelset totypesafe/jev-1.13, and the responsemodelnames a concrete Jev version. - Jev batch.
runBatchnever has more thanconcurrencyrequests in flight, a429,5xx, or in-flight-budget402response pauses every worker and is retried with backoff, and a400or any other402fails the item without a retry. - Threshold tuning. Raising a tag’s threshold in
sweepThresholdsnever raises itstporfpcounts, so recall falls or stays flat while fewer false positives pass. - Jev cost.
costPerThousand(results).costequals the sum ofusage.costover the batch andperThousandequals that sum divided by the item count times 1,000.
Next steps
Here are a few things you can try next:- Gate Tool Calls with Jev to apply the same
noulthresholds to agent actions - Cut LLM Cost with a Jev-Verified Cascade to verify generated answers with a
choicequestion - TypeSafe SDK guide to call Jev through the TypeSafe JS or Python SDK instead of raw
fetch - Decisions API reference