Blog
3 min read

The Vercel AI SDK: Streaming Chat and Tool Calls in TypeScript

The AI SDK is a TypeScript toolkit for calling language models from any provider with one API, streaming responses to React with useChat, calling tools, and generating structured data. A working Next.js chat with Claude, tool calling with stopWhen, structured output, provider choice, and production concerns.

The AI SDK (from Vercel, open source) is a TypeScript library for building AI features: one API across providers (Anthropic, OpenAI, Google and others), streaming, tool calling, structured output, and React hooks for chat UIs. It runs in any Node.js environment, not just on Vercel.

Code below is for AI SDK v7. The API has changed between major versions, so check the docs for the version you install.

A streaming chat in Next.js

npm install ai @ai-sdk/react zod

The route

// app/api/chat/route.ts
import {
  streamText,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type UIMessage,
} from 'ai'

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json()

  const result = streamText({
    model: 'anthropic/claude-sonnet-5.5',
    system: 'You are a helpful assistant for an invoicing app. Be concise.',
    messages: await convertToModelMessages(messages),
  })

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  })
}

A model string like 'anthropic/claude-sonnet-5.5' goes through Vercel's AI Gateway. To call Anthropic directly with your own key, use the provider package instead:

import { anthropic } from '@ai-sdk/anthropic'   // reads ANTHROPIC_API_KEY
// model: anthropic('claude-sonnet-5-5')

The page

'use client'
import { useChat } from '@ai-sdk/react'
import { useState } from 'react'

export default function Chat() {
  const [input, setInput] = useState('')
  const { messages, sendMessage, status } = useChat()

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <strong>{m.role === 'user' ? 'You' : 'AI'}:</strong>{' '}
          {m.parts.map((part, i) => part.type === 'text' ? <span key={i}>{part.text}</span> : null)}
        </div>
      ))}
      <form onSubmit={e => { e.preventDefault(); sendMessage({ text: input }); setInput('') }}>
        <input value={input} onChange={e => setInput(e.target.value)} disabled={status !== 'ready'} />
      </form>
    </div>
  )
}

Text streams in as it's generated. (Streaming LLM responses)

Tool calling

import { streamText, tool, isStepCount } from 'ai'
import { z } from 'zod'

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  messages: await convertToModelMessages(messages),
  tools: {
    getInvoice: tool({
      description: 'Look up an invoice for the current user by number',
      inputSchema: z.object({ number: z.string() }),
      execute: async ({ number }) => db.invoices.findForUser(userId, number),
    }),
  },
  stopWhen: isStepCount(5),
})

stopWhen enables multi-step calls: after a tool runs, the SDK sends the result back to the model, until it answers without tools or the condition is met. Tool calls and results arrive as message parts you can render in the UI. (What is function calling?, How to build an AI agent)

Note userId comes from your session, not from the model — tools must enforce permissions themselves. (IDOR explained)

Structured data

import { generateText, Output } from 'ai'

const { output } = await generateText({
  model: 'anthropic/claude-haiku-4.5',
  output: Output.object({
    schema: z.object({
      category: z.enum(['billing', 'bug', 'feature_request']),
      urgency: z.number().min(1).max(5),
    }),
  }),
  prompt: `Classify this support message: ${text}`,
})

Validated, typed output. (Structured output from LLMs)

Why use it (and when not to)

Use it when:

  • you want one API and easy provider switching,
  • you're building React chat UIs — useChat handles streaming, state and message parts,
  • you want tools and structured output with Zod in TypeScript.

Consider a provider's own SDK when:

  • you need provider-specific features on day one (some Claude features like advanced caching controls or new beta tools land in Anthropic's SDK first), (Prompt caching)
  • you're building a long-running autonomous agent that benefits from a full harness — see the Claude Agent SDK.

Production checklist

  • Authenticate the route and rate-limit per user — an open chat endpoint is a free AI API for bots. (Stop bots running up your AI bill)
  • Limit input length and output tokens.
  • Log usage per user for cost tracking.
  • Timeouts: long streams can exceed serverless function limits; a long-running server avoids that. (What is serverless?)
  • Persist conversations in your database if users expect history.

EasySpawn runs your Next.js app as a long-lived server — no function timeouts cutting off long streams — with Postgres for chat history and your API keys kept server-side. See how it works or join the waitlist.

Related: Streaming LLM Responses to the Browser · How to Add an AI Chatbot to Your App · The Claude Agent SDK · OpenAI API vs Claude API

Keep reading