Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Persistence (Advanced)
Embeddings
Reranking
Evaluate
Sandboxes (Advanced)
Class References
Function References
Interface References
Type Alias References
Variable References
Tutorials

Evaluate a ticket

You have support tickets and you need to route them in code. This tutorial builds a server route that returns a queue, an urgency, and a refund flag.

1. Install

sh
npm i @tanstack/ai @tanstack/ai-typesafe
npm i @tanstack/ai @tanstack/ai-typesafe

2. Set your key

Get a key from TypeSafe. Then set it in the environment:

sh
TYPESAFE_API_KEY=your-typesafe-api-key
TYPESAFE_API_KEY=your-typesafe-api-key

Keep the key on the server. Do not send it to the browser.

3. Call decide() on the server

Add a POST handler. Create the adapter once. Then ask three questions about the ticket.

typescript
import { decide, choice, score, boolean } from '@tanstack/ai'
import { typesafeDecider } from '@tanstack/ai-typesafe'

type Ticket = {
  subject: string
  body: string
}

const ADAPTER = typesafeDecider('jev-latest')

export async function POST(request: Request) {
  const body = (await request.json()) as { ticket: Ticket }
  const ticket = body.ticket

  const result = await decide({
    adapter: ADAPTER,
    state: ticket,
    questions: {
      queue: choice({
        instructions: 'Which team should handle this ticket?',
        options: {
          billing: 'Payments, invoices, refunds',
          tech: 'Bugs, outages, integrations',
          sales: 'Pricing, upgrades, new accounts',
        },
      }),
      urgency: score({
        instructions: 'How urgent is this ticket?',
        levels: ['low', 'medium', 'high'],
      }),
      refund: boolean({
        instructions: 'Is the customer asking for a refund?',
      }),
    },
  })

  return Response.json(result)
}
import { decide, choice, score, boolean } from '@tanstack/ai'
import { typesafeDecider } from '@tanstack/ai-typesafe'

type Ticket = {
  subject: string
  body: string
}

const ADAPTER = typesafeDecider('jev-latest')

export async function POST(request: Request) {
  const body = (await request.json()) as { ticket: Ticket }
  const ticket = body.ticket

  const result = await decide({
    adapter: ADAPTER,
    state: ticket,
    questions: {
      queue: choice({
        instructions: 'Which team should handle this ticket?',
        options: {
          billing: 'Payments, invoices, refunds',
          tech: 'Bugs, outages, integrations',
          sales: 'Pricing, upgrades, new accounts',
        },
      }),
      urgency: score({
        instructions: 'How urgent is this ticket?',
        levels: ['low', 'medium', 'high'],
      }),
      refund: boolean({
        instructions: 'Is the customer asking for a refund?',
      }),
    },
  })

  return Response.json(result)
}

In TanStack Start, put this in src/routes/api.evaluate.ts. Start maps that file to /api/evaluate.

4. Read the answers

Call the route. Then branch on the typed fields.

typescript
type Ticket = {
  subject: string
  body: string
}

async function evaluateTicket(ticket: Ticket) {
  const res = await fetch('/api/evaluate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ticket }),
  })
  return res.json() as Promise<{
    queue: { value: string; probability: number; confidence: number }
    urgency: { value: string; probability: number; score: number }
    refund: { value: boolean; probability: number }
  }>
}

const result = await evaluateTicket({
  subject: 'Charged twice for the same invoice',
  body: 'Please refund the extra payment.',
})

if (result.refund.value) {
  console.log('refund path', result.queue.value, result.urgency.value)
} else {
  console.log('route to', result.queue.value, result.urgency.value)
}
type Ticket = {
  subject: string
  body: string
}

async function evaluateTicket(ticket: Ticket) {
  const res = await fetch('/api/evaluate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ticket }),
  })
  return res.json() as Promise<{
    queue: { value: string; probability: number; confidence: number }
    urgency: { value: string; probability: number; score: number }
    refund: { value: boolean; probability: number }
  }>
}

const result = await evaluateTicket({
  subject: 'Charged twice for the same invoice',
  body: 'Please refund the extra payment.',
})

if (result.refund.value) {
  console.log('refund path', result.queue.value, result.urgency.value)
} else {
  console.log('route to', result.queue.value, result.urgency.value)
}

You now have a queue, an urgency, and a refund flag. Try a POST with that ticket.

5. Swap the adapter

The decide() call stays the same. Only the adapter changes.

  • OpenRouter: openRouterDecider('~typesafe/jev-latest') and OPENROUTER_API_KEY
  • Vercel AI Gateway: vercelGatewayDecider('typesafe-ai/jev') and AI_GATEWAY_API_KEY
  • Cloudflare: cloudflareDecider('typesafe/jev') and a Worker binding, or CLOUDFLARE_ACCOUNT_ID plus CLOUDFLARE_API_TOKEN

6. Try it

Run the app. Paste a ticket. Pick a provider. Click Submit.

The same app is on the Examples tab at /ai/latest/docs/framework/react/examples/evaluate.

The full example is on GitHub: TanStack/ai examples/react/evaluate.

See the Evaluate guide for abort, middleware, and the full result shape.