Skip to Content

Knowledge Base AI SDK

How to classify, route, and score with Jev and AI SDK

Use Jev from TypeSafe AI with AI SDK's experimental evaluate API to classify, route, score, and verify inside your application. Jev returns typed choices, scores, and boolean probabilities through AI Gateway.

Content Engineer

Routing a support ticket, assessing an agent’s proposed action, or rating a request’s urgency calls for a decision your code can act on. Language models can return structured decisions, but still produce them through text generation.

Jev, a System One model from TypeSafe AI, evaluates supplied state against typed questions and returns choices, scores, and boolean probabilities without generating prose. The AI SDK’s experimental_evaluate API makes these answers available directly in TypeScript via Vercel AI Gateway.

Your application decides what happens next: a department choice can select a support queue, a severity score can influence priority, and an uncertain result can trigger review. Keeping those rules in code lets you change how the application responds without redefining what you ask the model to assess.


Overview

In this guide, you'll learn how to:

  • Ask a single yes-or-no question about a piece of state
  • Answer several typed questions in one request, including choice, score, and boolean questions against structured state
  • Branch on probabilities and confidence so clear cases route automatically and uncertain cases go to review
  • Unit-test that branching with a mock evaluation model
  • Call Jev through the Gateway provider instance when your application needs an explicit provider object


Prerequisites

Before you begin, make sure you have:


How Jev differs from a language model

Jev is a probabilistic decision model, not a chat model. Language models can generate structured answers, but any probability included in that output is itself a generated estimate.

Jev evaluates each question independently against the same state, returning typed answers with probabilities over the defined outcomes. Your schema constrains those answers, but doesn’t guarantee they’re correct.

Jev

Language model


Output

Probabilities

Free-form text or structured output

Prompted estimates, when available at all

Typed choice, score, and boolean answers with probabilities

Native to every answer, with a full distribution for choice and score

Questions per request

Text generation

Answered together in one generation

Yes

Evaluated independently, so adding a question doesn't change others

No

Jev on AI Gateway at a glance

Property

Model ID


Model type

Context window

Evaluation

None (input tokens only)

typesafe-ai/jev

64,000 tokens per request and 32,000 for state

Pricing

Observability

Output token charge

Data controls

Zero Data Retention and No Training, per request

Appears in AI Gateway logs, custom reporting, and budgets

Three question types

AI SDK exposes three question types, each mapped to a TypeSafe AI primitive:


type

Array of level descriptions, lowest to highest

What it does

Picks one option from a named set

Up to 255 options
criteria
Map of option keys to descriptions
Limits

score

Grades the state against an ordered rubric

choice, probabilities
Answer fields
2 to 10 levels
score, probabilities

boolean

Estimates whether a statement is true

choice
None
Optional true and false descriptions
probability

Every answer keeps its question ID and matches the question’s type. For Boolean answers, probability estimates how likely the statement is to be true:

  • 0.98 indicates a strong yes.
  • 0.02 indicates a strong no.
  • 0.5 indicates uncertainty between the two outcomes.


Steps


1. Install the AI SDK

Install AI SDK 7.0.105 or later, which adds experimental_evaluate:

2. Ask one boolean question


pnpm

pnpm i ai

yarn

Link your local directory to its Vercel project and pull your environment variables. This writes a VERCEL_OIDC_TOKEN to your environment file.

bun

When you pass a model ID as a plain string (e.g., typesafe-ai/jev), AI SDK routes the call through AI Gateway and authenticates with the OIDC token.

vercel link

Deployments on Vercel automatically receive the token. Locally, the token expires after 12 hours, so re-run vercel env pull when a request returns a 401.

vercel env pull

npm

Start with a single yes-or-no decision. The state is whatever you want the model to look at, and each key in questions becomes a key in answers.

Our Services

import{ experimental_evaluate as evaluate }from'ai';

model:'typesafe-ai/jev',

state: transcript,

questions:{
const result =awaitevaluate({
},

type:'boolean',

exportasyncfunctionwasRefunded(transcript:string){

refunded:{
criteria:{
true:'The agent confirmed that money was returned to the customer.',
false:'No refund was issued, or the refund was declined.',

},

instructions:'Was a refund issued to the customer?',

});
},
return result.answers.refunded.probability;
}

Calling wasRefunded('The support agent issued a full refund to the customer.') returns a number close to 1.

One example of result.answers:


{

"refunded":{"type":"boolean","probability":0.99}

}


The criteria on a boolean question are optional, but they sharpen the decision by telling the model exactly what counts as true and false.


3. Answer several questions against structured state in one request

Jev evaluates all questions in a request in parallel, so adding questions barely changes latency. You can mix all three question types, and state accepts a JSON object or array as well as a string, which means you can pass a record or a message history without serializing it yourself.

The following route handler triages a support ticket into a department, a severity level, and a refund flag in one round trip.

import{ experimental_evaluate as evaluate }from'ai';

state:{

instructions:'Which team should handle this ticket?',

const ticket =await request.json();
department:{
exportasyncfunctionPOST(request: Request){
plan: ticket.plan,

},

questions:{

const result =awaitevaluate({
model:'typesafe-ai/jev',
subject: ticket.subject,
previousTickets: ticket.previousTickets,

criteria:{

other:'Anything that does not fit the other teams',

type:'choice',
message: ticket.message,
billing:'Charges, invoices, and refunds',
account:'Login, permissions, and profile changes',

pnpm

pnpm dev

bun

Start the dev server and send a ticket:

-d'{

-H"Content-Type: application/json"\

curl-X POST http://localhost:3000/api/triage \

"plan": "pro",

"previousTickets": 2

"subject": "Stripe sync broken",

}'

{

},

"type":"choice",

"choice":"technical",

"department":{

"probabilities":{"billing":0.08,"technical":0.91,"account":0.01,"other":0

},

}

"type":"score",

"score":2.86,

"probabilities":{"0":0,"1":0.02,"2":0.1,"3":0.88}

"requestsRefund":{"type":"boolean","probability":0.97}

Reading the response:

  • department.choice is inferred as a union of your option keys ( 'billing' | 'technical' | 'account' | 'other'), so TypeScript flags a typo like choice === 'tech' at compile time.
  • department.probabilities covers every option, and the selected choice always has the highest value.
  • severity.score is the probability-weighted mean across the rubric levels, indexed from zero. 2.86 sits between "Blocking with no workaround" and "Blocking and causing financial or data loss".
  • severity.probabilities uses string keys for the level indices, so "3" is the fourth rubric entry.
  • requestsRefund.probability is the estimated probability that the customer wants money back, not a confidence in the answer.

The providerOptions.gateway object is optional. Jev supports Zero Data Retention and No Training per request, and evaluation calls appear in AI Gateway logs and count toward budgets like any other model call.


4. Branch on probabilities and confidence

Use the returned probabilities to decide when your application should act automatically or request review, setting stricter thresholds where an incorrect decision would have greater consequences.

TypeSafe AI also returns a separate confidence statistic for choice and score answers in result.providerMetadata.typesafe.confidence, keyed by question ID. Confidence summarizes how concentrated the probability distribution is, from 0 (spread evenly across options) to 1 (all on one option). It differs from the selected option's probability, and it isn't returned for boolean answers.

The routing logic below uses two paths:



Condition

What your code does

Department confidence is 0.6 or higher and the selected option's probability is 0.7 or higher

Assign the ticket to that queue

Either value is below its floor

Send the ticket to a human

Move the evaluate call into a function that returns a decision, keeping the questions from step three. Its model parameter defaults to Jev and lets the tests in step five substitute a mock.

import{

}from'ai';

previousTickets:number;

typeExperimental_EvaluationModelas EvaluationModel,

subject:string;
){
};

type:'choice',

model: EvaluationModel ='typesafe-ai/jev',

message:string;
ticket: Ticket,
plan:string;
const result =awaitevaluate({

model,

instructions:'Which team should handle this ticket?',

criteria:{
state: ticket,
department:{
questions:{

Keep two distinctions in mind when applying this pattern:


}

"action":"assign",

}

const ticket =(await request.json())as Ticket;

"queue":"technical",

import{ routeTicket,typeTicket}from'@/lib/route-ticket';

{

"severity":2.86,

"refundRequested":true

return Response.json(awaitrouteTicket(ticket));

exportasyncfunctionPOST(request: Request){

Sending the same curl request from step three now returns a decision:

Treat the example thresholds as starting points. Calibration describes how predicted probabilities match observed outcomes across many examples; it doesn’t guarantee an individual answer is correct. Evaluate labeled tickets using the same questions, then choose cutoffs based on the errors your workflow can tolerate.


5. Test the routing logic without calling Jev

The thresholds in routeTicket are application logic, so test them like any other function. The AI SDK ships Experimental_EvaluationMockModelV4 in ai/test, which returns whatever answers you give it. Pass it as the model argument to check each branch with fixed inputs and no network call.

Add Vitest as a development dependency:

pnpmadd-D vitest


The mockJev helper derives its answers type from the mock’s doEvaluate return type, letting TypeScript catch incompatible answer shapes at compile time.

import{ routeTicket,typeTicket}from'./route-ticket';

const ticket: Ticket ={

import{ Experimental_EvaluationMockModelV4 as MockEvaluationModel }from'ai/test';

import{ describe, expect, it }from'vitest';
){
answers: EvaluationResult['answers'],
plan:'pro',

};

functionmockJev(

subject:'Stripe sync broken',
previousTickets:2,
warnings:[],
confidence?: Record,

answers,

message:'My Stripe connection has failed for three days.',

}),
returnnewMockEvaluationModel({
doEvaluate:async()=>({
...(confidence &&{ providerMetadata:{ typesafe:{ confidence }}}),

Run the tests:

Testing the thresholds this way is separate from checking whether the thresholds are right for your data. For that, run real tickets with known outcomes through Jev and compare its probabilities to what actually happened.

npm

Tests 4 passed (4)
yarn
pnpm vitest run lib/route-ticket.test.ts
pnpm

Expected output:

Use @ai-sdk/gateway when you need an explicit provider object or want to configure custom headers, a custom fetch, or a different Gateway base URL.

✓ lib/route-ticket.test.ts (4 tests)
bun
npm
Test Files 1 passed (1)

6. Use the Gateway provider instance

pnpm
yarn
pnpm i @ai-sdk/gateway
bun

type:'boolean',

questions:{

model: gateway.evaluationModel('typesafe-ai/jev'),

import{ experimental_evaluate as evaluate }from'ai';

const result =awaitevaluate({

state:'I was charged twice. Please refund the duplicate.',

},

});

requestsRefund:{

console.log(result.answers.requestsRefund.probability);

import{ gateway }from'@ai-sdk/gateway';

instructions:'Is the customer requesting money back?',

Best practices

Jev works best when each question asks one well-scoped thing that a knowledgeable person could answer in a few seconds. If a question would require extended reasoning or weighs several independent factors, split it into one question per factor and combine the answers with your own logic.

Ask atomic questions and combine them in code

The string and provider-instance forms are interchangeable.


Instead of

"Rate this pull request"

Ask

Three score questions: test coverage, documentation, description clarity

Then

Weight the three scores by importance in code

"Is this ticket a priority?"

"Should the agent run this command?"

A boolean for urgency, a score for business impact, a choice for customer tier

A boolean for destructive intent, a boolean for touching production, a choice for command category

Compute ticket priority from all three answers

Require confirmation when any risk flag exceeds your threshold

Describe options and levels instead of labeling them

Choice criteria are a map of option keys to descriptions, and score criteria are ordered descriptions from lowest to highest.

Writing 'Blocking with no workaround' gives the model far more to match against than 'high'. Descriptions can be strings, JSON objects, or arrays, so you can pass a list of example phrases for each option.


Keep state focused

Input tokens are the only thing you pay for, so pass the fields the decision depends on rather than an entire record. Adding questions to a request doesn't degrade the answers to existing ones, because each question is evaluated independently.

Set thresholds per action, not per model

Read-only actions like showing a screen can tolerate a wrong guess, so a probability of 0.7 might be enough. Destructive actions need a higher bar, closer to 0.9 or above, and a confirmation step below it. Encode that risk tolerance in your code, and keep a review path for anything under your floor.

Keep classification separate from authorization. Jev can tell you that a customer asked for a refund or that a command looks destructive. Whether to grant the refund or run the command depends on rules Jev doesn't see, such as account status, policy, and permissions, so make that a second check in your code.


Read distributions defensively

TypeSafe AI rounds probabilities and scores to two decimal places, and result.rounding reports that precision. Because of this rounding, a choice distribution may sum to 0.99 rather than exactly 1. The AI SDK accounts for this during validation, so don't renormalize the values yourself.


Troubleshooting


Authentication errors ( 401 or 403)

Expired local credentials or an unlinked project can prevent authentication. Run vercel env pull to refresh your credentials, using vercel link first if the directory isn’t linked to a project. If a 403 persists, check your access to the linked project and AI Gateway.

Unsupported question type

The evaluation model doesn’t support one of the requested question types. Check the model’s supported types and either adjust the questions or select a model that supports them. Jev supports Choice, Score, and Boolean questions.



NoSuchModelError

When modelType is 'evaluationModel', the provider couldn’t resolve the requested evaluation model or doesn’t support evaluation. Check that the model ID is typesafe-ai/jev and that the call resolves through AI Gateway or another evaluation-capable provider.

InvalidResponseDataError

The provider returned an invalid answer, such as a distribution with a missing option or a score outside the rubric range. Retry the request. If the error persists, report the failing questions and response to the provider.

Evaluation is unavailable through an OpenAI-compatible client

AI Gateway exposes evaluation through the AI SDK, rather than its compatibility endpoints. Call experimental_evaluate from the ai package.


Answers seem overconfident on your data

OpenAI-compatible endpoints don't support Jev evaluation. Use experimental_evaluate from the ai package for the examples in this guide. For other integrations, AI Gateway supports a native HTTP evaluation API and a TypeSafe-compatible API for existing TypeSafe clients.

Run labeled examples through the same questions and compare predicted probabilities with observed outcomes. Inspect where errors cluster, revise unclear criteria, and evaluate the updated questions before choosing new thresholds.

Next steps


Related documentation