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_decide with a Jev model through AI Gateway:

import { experimental_decide as decide } from 'ai';
const result = await decide({
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]);

AI SDK now calls this operation a decision. Its older experimental_evaluate export remains a deprecated alias. 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 reviews using Sol or Astra, see Using GPT-6.1 Sol to evaluate application data with AI SDK. Those examples use Responses structured output. AI SDK's native OpenAI decision integration uses Luna and returns native distributions. Review the API and answer fields when changing providers, as generated probability estimates and native decision probabilities need separate threshold testing.

Copy link to headingHow can you use GPT-6.1 Sol or GPT-6 Luna after routing?

Use Jev to propose a support queue, then add GPT-6.1 Sol or GPT-6 Luna when the workflow needs an investigation or a written reply. The example's routeTicket() function returns routing information. A subsequent server operation can pass the accepted destination and original ticket to a generative model, along with any account records or diagnostic evidence it needs.

GPT-6.1 Sol suits investigations with dependent steps, such as reviewing an integration failure and explaining a fix from the available logs. Luna is a candidate for focused work, such as summarizing the ticket for a support agent or drafting a reply from an established resolution. When comparing Sol and Luna, test the resulting replies for factual support and measure the cost of completing the task, including retries and review.

Keep the generation call in server code and give it the original subject and message. Passing only billing or technical would discard the details needed to help the customer. Also preserve the distinction between the two outcomes in your interface. An accepted queue means the request has a proposed destination; it doesn't mean a model has investigated the issue or that a reply is ready to send.

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.

Copy link to headingCan I replace Jev with GPT-6.1 Sol or GPT-6 Luna by changing the model ID?

Changing the model ID alone is not enough for this example. The TanStack adapter and result shape differ from AI SDK's native OpenAI decision integration, which uses GPT-6 Luna and returns category distributions. Sol can assess tickets through Responses structured output, but that requires a response schema and different result handling. With either approach, test the acceptance rule against labeled tickets before replacing Jev.

Copy link to headingCan Sol or Luna handle tickets marked needs_review?

Sol or Luna can help prepare a summary for the reviewer, but this example uses needs_review to request a person's judgment. Pass the original ticket and routing result to that reviewer so the summary doesn't become their only evidence. If you want another model to make the routing decision instead, define and test that separate review process before changing what the interface promises.

More Decision models articles

Ready to deploy?