What is Jev?
Let's build a contact form that uses Jev to route customer requests to the right team. We'll start with a working template, change its routing rules, and try different messages to see how the answers change.
Alex writes, “I need my invoices, but I lost my phone. Help me get into my account first.” Our app needs to choose the team that can help. Sending the message to billing would leave Alex stuck at the sign-in screen.
Jev is an AI model from TypeSafe that answers specific questions for your application. We give it Alex's message and describe what each team handles. It chooses a team, and our code receives that team's ID.
The template calls each possible team a destination. For example, support_access is the ID for Support / Account access. The code uses the ID; the form displays the label.
For Alex, this is the decision we want:
Customer asks: Help me get into my account first.
Our rule: Account access handles sign-in problems.
Expected answer: support_accessThis is useful whenever a decision depends on what someone means. “I lost my authentication device” and “I can't get past two-factor authentication” can both belong to account access, even though they use different words. We describe the team's responsibility so the model can evaluate each request against it.
How is Jev different from a chat model?
A lot of the large language models (LLMs) we're used to working with respond to prompts with text. We give the model a request, such as “write a reply to Alex,” and it generates that reply.
Jev evaluates the questions we define and returns decisions directly, without composing a text response. Its training focuses on choosing answers and estimating their probabilities: numbers that describe how likely each answer is. For our form, we supply the possible teams and receive a choice plus probabilities for those teams. Our code can use that information to decide whether to accept the answer or ask another model.
General-purpose models can make routing decisions too. Structured output can constrain their response to a format such as JSON containing a team ID. Jev's focus is making those decisions and estimating their probabilities. A general-purpose model can then use the selected team to help draft Alex's reply.
Why are developers trying it?
Jev attracted rapid early adoption. On September 18, 2026, Vercel reported that it reached more than twice as many paid teams in its first 24 hours as any previous AI Gateway model launch.
The practical appeal is the chance to make frequent decisions with less waiting and lower cost. TypeSafe reports speed and cost gains in its own workflow tests. The gains for an app depend on its requests and the model it replaces. In our form, we'll focus on the decision itself: whether the selected team follows the rules we supplied.
We'll start with the question: which team should handle this request?
Outcome
Submit Alex's request and trace how Jev's answer identifies a team.
Hands-on exercise
Why a keyword check falls short
A keyword check could look for “invoices” and send matching requests to billing:
const message =
"I need my invoices, but I lost my phone. Help me get into my account first.";
const destination = message.includes("invoices")
? "billing_invoices"
: "contact_triage";
console.log(destination);This code selects billing_invoices because the message contains “invoices.” Alex asks for account recovery first, so the keyword check sends the request to the wrong team. Let's deploy the app and see how Jev handles the full message.
Deploy the template
Click Deploy to Vercel to create your own copy of the app:
Sign in to Vercel, choose your GitHub account, and create the repository. A repository stores the project's code; this step creates your own copy on GitHub. Keep the suggested name jev-form-router, then click Deploy to put the app online.
When deployment finishes, open the app and add /contact to its URL. The home page opens the leads example; we'll use the contact form.
The app reaches its models through AI Gateway, Vercel's service for connecting applications to AI models. Gateway needs to verify which Vercel project is making each request.
For our deployed app, Vercel supplies an OIDC token. OIDC stands for OpenID Connect, a standard for verifying identity. The token is a short-lived, signed credential that identifies our project and team. AI SDK, the library making our model calls, automatically uses it to authenticate with Gateway.
Vercel manages the token for the deployed app, so we don't need to create an API key or copy a token into environment variables. In the next lesson, we'll pull a token for running the same app on our computer.
Submit Alex's request
Under Load a sample on /contact, click Overlapping needs. This fills in the form with Alex's request. Read the fields, then click Route submission to send them to the model. If Email the receiving team is visible, leave it unchecked.
The result shows a team and specialty. We expect Support / Account access because Alex asks to recover access first.
Also read Selected by. This app can ask a second model, Luna, when it cannot use Jev's answer. Luna is the app's fallback, or backup model. The label tells us which model supplied the displayed team.
Follow the evaluation call
Open your new repository on GitHub, open the lib folder, and select router.ts. We're reading the existing code here. It imports experimental_evaluate as evaluate from ai. Inside routeSubmission, this call sends the question and customer information to Jev:
const result = await evaluate({
abortSignal: AbortSignal.timeout(JEV_TIMEOUT_MS),
maxRetries: 1,
model: models.jev ?? "typesafe-ai/jev",
questions,
state,
});
const answer = result.answers.destination;AI SDK supplies evaluate, and AI Gateway connects that call to Jev. The two inputs to understand are state and questions.
Just above the call, the app builds those inputs from the submitted form and the team definitions:
const criteria = Object.fromEntries(
example.destinations.map((destination) => [
destination.id,
destination.criteria,
])
);
const questions = {
destination: { criteria, instructions, type: "choice" as const },
};
const state = { example: example.id, submission };State contains the form fields, including Alex's message and account context. The choice question, written as type: "choice", asks Jev to select one of the allowed destinations.
The criteria describe which requests belong to each team. map creates pairs of IDs and descriptions; Object.fromEntries turns those pairs into the criteria object. The separate instructions explain how to choose when a request mentions more than one need.
Follow the name destination. We define it under questions, then read it under result.answers. The chosen ID is answer.choice; the template checks that it exists in its list of destinations before using it. The question's name connects our request to the answer we read.
For Alex's request, we expect answer.choice to be support_access. The app uses this ID to look up the team label shown in the form.
Try It
In your text editor, create a file called routing-notes.md and save it on your computer. Record Alex's message, the returned team, and Selected by. If the team differs from Support / Account access, keep the returned answer so we can investigate it.
Open lib/router.ts. Find where the form fields enter state, then follow questions.destination to result.answers.destination.choice. That is the path from the customer's words to the team ID.
Add a sentence to your notes: why would we try Jev to choose Alex's team, and what would we use to write Alex a reply?
If something goes wrong
The form loads but routing fails: open AI Gateway in the Vercel team where you deployed the app. Check available credits and access to typesafe-ai/jev and openai/gpt-5.6-luna-fast. Complete any account-verification step shown there, then submit again. The deployment handles authentication automatically.
The previous result disappears: editing a field or loading a sample clears it. Save the answer before changing the form.
Checkpoint
Save Alex's result in routing-notes.md.
Done-When
- You can explain what Jev receives and what it returns.
- You can explain why we'd try Jev for routing and use a general-purpose model to draft a reply.
- Your deployed
/contactpage routes Alex's request, and you recorded the team and deciding model. - You can locate
stateandresult.answers.destination.choicein the router.
Solution
The expected team is Support / Account access, whose ID is support_access. Alex mentions invoices but explicitly asks to recover access first. The keyword rule misses that distinction.
Jev receives the form fields in state. The destination question describes the available teams and their responsibilities. Its answer contains the selected team's ID, which the app checks against its registered destinations.
Next, we'll give Alex a cancellation request and decide who should handle it.
Was this helpful?