Skip to content
Dashboard

Using Jev in TanStack Start with TanStack AI

Content Engineer

Use Jev in TanStack Start by calling TanStack AI's decide() from a server route. The @tanstack/ai-vercel-gateway adapter sends the evaluation through AI Gateway. Your route supplies the evidence and questions, then returns a decision that the interface can display or the application can act on.

Copy link to headingWhat does each part of the integration do?

Component

Responsibility in a support form

TanStack Start

Receive the form submission and return a response

TanStack AI

Express the routing question and expose a typed result

Vercel Gateway adapter

Send the evaluation request through AI Gateway

Jev

Select from the queue options you supply

Application code

Accept the proposed queue or require review

The Vercel Gateway adapter exposes createVercelGatewayDecider for evaluation with an explicit credential. Pair it with decide() and the model ID typesafe-ai/jev. Jev returns constrained decisions that the form can display once the evaluation completes.

Copy link to headingHow should you define the routing decision?

Suppose a customer submits a message about an unexpected invoice. The application needs to choose a support queue before anyone investigates the charge. Give Jev the subject and message as evidence, then define the destinations in terms of the work each team owns.

Billing handles charges and invoice questions, while technical support investigates product failures. The review option covers requests with unclear or overlapping responsibilities, such as a message that reports a payment error and asks for help restoring product access.

Ask which team should handle the request first. The assignment gives the support team a place to begin investigating; it does not establish the cause of the problem or confirm that the customer's account has been checked.

Copy link to headingHow do you connect Jev to a TanStack Start application?

Copy link to heading1. Install TanStack AI and the Gateway adapter

In an existing TanStack Start React project, install the evaluation packages, the Vercel OIDC helper, and Zod for request validation:

npm install @tanstack/ai @tanstack/ai-vercel-gateway @vercel/oidc zod

The core package provides decide() and the choice() helper used below. The Gateway adapter handles authentication and the evaluation request, so this integration does not require a separate TypeSafe API key.

Copy link to heading2. Configure AI Gateway authentication

Vercel deployments can use OIDC authentication without managing an API key.

For local development, link the project and pull an OIDC token into its environment file:

vercel link
vercel env pull .env.local

Local tokens expire after 12 hours, so refresh the file when the token expires. Alternatively, create an AI Gateway API key and set AI_GATEWAY_API_KEY in your application's environment. An existing API key takes precedence over OIDC; updating the token will not fix a request still using an invalid key.

The adapter's vercelGatewayDecider convenience factory reads OIDC tokens only from the environment, which can retain an expired token in a long-running Node.js process. The example instead calls getVercelOidcToken() within each request to obtain the current token from Vercel's request context, falling back to VERCEL_OIDC_TOKEN for local development. It passes the resolved credential to createVercelGatewayDecider.

Keep the evaluation in the server route. The browser submits the ticket and receives the result without receiving either credential.

Copy link to heading3. Define the question and the routing rule

Create src/lib/route-ticket.server.ts for the model call. TanStack AI's evaluation API takes an adapter, shared state, and named questions. Each question name becomes a property on the returned result.

src/lib/route-ticket.server.ts
import { choice, decide } from '@tanstack/ai';
import { createVercelGatewayDecider } from '@tanstack/ai-vercel-gateway';
import { getVercelOidcToken } from '@vercel/oidc';
export type Ticket = {
subject: string;
body: string;
};
export async function routeTicket(ticket: Ticket) {
const credential =
process.env.AI_GATEWAY_API_KEY || (await getVercelOidcToken());
const result = await decide({
adapter: createVercelGatewayDecider('typesafe-ai/jev', credential),
state: ticket,
questions: {
queue: choice({
instructions: 'Which team should handle this request first?',
options: {
billing: 'Invoice amounts, charges, and payment questions',
technical: 'Product errors, outages, and integration failures',
needs_review: 'Unclear requests or overlapping responsibilities',
},
}),
},
abortSignal: AbortSignal.timeout(10_000),
});
const { value, probability } = result.queue;
const reviewRequired = value === 'needs_review' || probability < 0.85;
return {
proposedQueue: value,
selectedProbability: probability,
destination: reviewRequired ? 'needs_review' : value,
reviewRequired,
};
}

Here, result.queue.value is the selected option key. Its probability describes that selected option. The code requires review when Jev selects the review category or when the selected queue falls below the example threshold.

The 0.85 cutoff and ten-second deadline are application choices. Establish your threshold with labeled requests and choose a deadline that fits the form's expected response time. Even a high probability for needs_review sends the ticket to review, which is why the explicit category check remains separate from the cutoff.

The result preserves both the proposed queue and the destination chosen by application code. That distinction lets a support agent see when the model proposed billing but the application required review because the probability was below the threshold.

Copy link to heading4. Add a validated server route

Create src/routes/api.triage.ts. The TanStack Start server-route convention exposes this file at /api/triage and lets the handler return a standard Response.

src/routes/api.triage.ts
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { routeTicket } from '../lib/route-ticket.server';
const ticketSchema = z.object({
subject: z.string().trim().min(1).max(200),
body: z.string().trim().min(1).max(5_000),
});
export const Route = createFileRoute('/api/triage')({
server: {
handlers: {
POST: async ({ request }) => {
let input: unknown;
try {
input = await request.json();
} catch {
return Response.json({ error: 'Expected a JSON request.' }, { status: 400 });
}
const parsed = ticketSchema.safeParse(input);
if (!parsed.success) {
return Response.json({ error: 'Enter a subject and message within the limits.' }, { status: 400 });
}
try {
return Response.json(await routeTicket(parsed.data));
} catch {
return Response.json({ error: 'Routing is unavailable. Try again.' }, { status: 503 });
}
},
},
},
});

The schema restricts the model's input to the two fields the question needs. The character limits belong to this form design and do not describe Jev's context capacity. Keep the queue definitions and threshold in application code so a submission cannot replace them.

Malformed input returns 400, and an evaluation failure returns 503. Completed evaluations return 200, including those with reviewRequired: true, so the interface can distinguish a request that needs a person's judgment from one that failed to run.

This route proposes a destination without saving the ticket. When connecting it to your support system, apply the existing access controls and persist the submission before telling the customer it has been received. If routing is unavailable, that saved ticket can wait for review without forcing the customer to re-enter their message.

Copy link to heading5. Call the route from a form

The interface only needs to send the input and display the returned destination. This component can sit inside an existing TanStack Start page:

src/components/TicketForm.tsx
import { useState, type FormEvent } from 'react';
export function TicketForm() {
const [pending, setPending] = useState(false);
const [message, setMessage] = useState('');
async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = new FormData(event.currentTarget);
setPending(true);
setMessage('');
try {
const response = await fetch('/api/triage', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
subject: form.get('subject'),
body: form.get('body'),
}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error ?? 'Routing failed.');
setMessage(
result.reviewRequired
? 'This request needs a person to choose the right team.'
: `Suggested team: ${result.destination}`,
);
} catch (error) {
setMessage(error instanceof Error ? error.message : 'Routing failed.');
} finally {
setPending(false);
}
}
return (
<form onSubmit={submit}>
<label>Subject <input name="subject" required maxLength={200} /></label>
<label>Message <textarea name="body" required maxLength={5_000} /></label>
<button disabled={pending} type="submit">
{pending ? 'Checking…' : 'Suggest a team'}
</button>
<p role="status">{message}</p>
</form>
);
}

The interface says which team is suggested and keeps routing errors visible. It does not claim that the issue has been resolved or that the support system has accepted the submission. After adding persistence, update the confirmation to reflect the completed operation.

Copy link to headingHow should you interpret Jev's other answer fields?

Choice answers also include the full distribution in probabilities and a separate confidence statistic. Probability refers to the selected option; confidence describes the distribution's concentration. Neither establishes that an individual assignment is correct, and the example routing rule uses only the selected-option probability.

TanStack AI also provides score() for an ordered rubric and boolean() for a yes-or-no statement. Boolean probability always describes the probability that the statement is true, even when its value is false. For Score answers, the fractional score and its nearest level label describe a position on your rubric.

The product-review moderation guide combines all three question types and shows how to test the application rules without calling Jev. Use that guide when your decision requires several assessments, such as a topic selection alongside content flags.

Copy link to headingWhat should you test before routing real requests?

Test the routing rule at the threshold and on either side of it. Include an explicit needs-review answer with a high probability to verify that it still reaches review. Invalid input and provider failures should follow their error paths without producing a queue assignment.

Then assess Jev's selections against tickets your team has labeled. Include messages that mention more than one issue and tickets whose correct destination depends on your team's responsibilities. If reviewers cannot agree on the category, clarify the definitions before changing the probability threshold.

Keep the original ticket and routing result available so support agents can correct assignments. Record those corrections with the question definitions used at the time to identify recurring gaps in the categories or evaluation.

Copy link to headingCan I use AI SDK with Jev instead?

Yes. TanStack Start can run AI SDK calls in its server routes. Install ai to use this alternative, then call experimental_evaluate with a Jev model through AI Gateway:

import { experimental_evaluate as evaluate } from 'ai';
const result = await evaluate({
model: 'typesafe-ai/jev',
state: { subject: 'Unexpected charge', body: 'My invoice includes an extra seat.' },
questions: {
queue: {
type: 'choice',
instructions: 'Which team should handle this request first?',
criteria: {
billing: 'Invoice amounts, charges, and payment questions',
technical: 'Product errors, outages, and integration failures',
needs_review: 'Unclear requests or overlapping responsibilities',
},
},
},
});
const answer = result.answers.queue;
console.log(answer.choice, answer.probabilities?.[answer.choice]);

This example uses AI SDK's default Gateway provider, which resolves OIDC automatically when AI_GATEWAY_API_KEY is unset. AI SDK places the selection in answers.queue.choice; TanStack AI exposes it as queue.value. Update the code that reads the answer to match the library you choose.

Our guide to classifying, routing, and scoring with Jev and AI SDK covers the question types and testing in detail. The Jev form-router guide adds a deployable example with a fallback model for uncertain or failed evaluations.

For evaluations using OpenAI models, see the companion article Using GPT-6 Sol with AI SDK evaluation, which includes Astra and Luna examples. Those models use AI SDK's OpenAI evaluation adapter and have different probability semantics from Jev, so a provider change also requires reviewing the application's acceptance rules.

Copy link to headingFrequently asked questions

Copy link to headingCan I call Jev directly from the browser in this integration?

Keep the credential and evaluation call in a TanStack Start server route. The browser can submit the form with fetch and receive the routing result without having access to the Gateway credential.

Copy link to headingDo I need a TypeSafe API key when using the Vercel Gateway adapter?

No. The Gateway adapter authenticates with AI_GATEWAY_API_KEY or a Vercel OIDC token. Direct TypeSafe adapters use their own provider credentials.

Copy link to headingDoes TanStack AI stream evaluation answers?

No. The decide function returns one completed result containing the named answers and metadata. The form can show a pending state while it waits for that result.

Copy link to headingDoes using TanStack Start require choosing TanStack AI over AI SDK?

No. TanStack Start can host server code that uses either library. Choose the evaluation interface that fits your application and account for their different answer shapes.

More Build with AI articles

Ready to deploy?