---
title: "Meet Jev"
description: "Learn what Jev does, run the contact form, and trace one request from the submitted fields to a team ID."
canonical_url: "https://vercel.com/academy/make-decisions-with-jev/run-the-template"
md_url: "https://vercel.com/academy/make-decisions-with-jev/run-the-template.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-09-23T22:41:10.486Z"
content_type: "lesson"
course: "make-decisions-with-jev"
course_title: "Make Decisions with Jev"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Meet Jev

# What is Jev?

Let's build a contact form that uses [Jev](https://docs.typesafe.ai/introduction) 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:

```text
Customer asks: Help me get into my account first.
Our rule: Account access handles sign-in problems.
Expected answer: support_access
```

This 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](https://vercel.com/blog/ai-gateway-jev-model-launch) 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:

```js title="A keyword rule for Alex's message"
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:

[![Deploy to Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fjev-ai-sdk-form-router%2Ftree%2F05cf6680ed3c85bbbb4311c2fa1a7418e8d65d46\&project-name=jev-form-router\&repository-name=jev-form-router)

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:

```ts title="lib/router.ts: existing evaluation call"
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:

```ts title="lib/router.ts: existing inputs"
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 `/contact` page routes Alex's request, and you recorded the team and deciding model.
- [ ] You can locate `state` and `result.answers.destination.choice` in 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.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
