IT Infrastructure

The Vercel AI SDK Walkthrough: Building an LLM-Powered App in TypeScript

Warm minimalist illustration of a terminal window with tokens streaming, evoking the Vercel AI SDK

The Vercel AI SDK is a free, open-source TypeScript toolkit for building applications that talk to large language models. It gives you one consistent set of functions for calling a model, streaming its output, wiring up a chat interface, and letting the model run your own code. This walkthrough covers what the toolkit includes, then builds a small LLM-powered app step by step: project setup, a first model call, a streamed response, and a tool call the model can trigger on its own.

Vercel shipped AI SDK 7 on June 25, 2026, and the package, published as ai on npm, passed 16 million weekly downloads around that release (Vercel). The examples below use the stable core functions that have carried across recent major versions, so they hold whether you install version 5, 6, or 7. Confirm the current release before you start: run npm view ai version, or check the official AI SDK documentation. Version numbers move fast in this corner of the ecosystem.

What the Vercel AI SDK actually is

Think of the SDK as a thin, typed layer between your code and whichever model provider you pick. It does not host models and it does not replace your framework. It standardizes the messy parts: request shapes, streaming, tool schemas, and provider differences. The toolkit splits into a few pieces.

  • Core functions: generateText for a single response and streamText for token-by-token output. These are the workhorses.
  • Structured output: generateObject and streamObject return typed JSON that matches a schema you define, instead of a raw string.
  • UI hooks: useChat and useCompletion, shipped in the @ai-sdk/react package, manage chat state and streaming in the browser so you do not hand-roll it.
  • Provider-agnostic model access: separate provider packages such as @ai-sdk/openai and @ai-sdk/anthropic expose the same interface, so switching models is often a one-line change.
  • Tool calling: you describe functions the model can invoke, and the SDK handles the round trip of the model asking, your code running, and the result going back.

That provider-agnostic design is the reason many teams reach for it. You write against one API and keep the option to swap OpenAI for Anthropic or Google later. The SDK is maintained by Vercel, the company behind Next.js, and it runs anywhere JavaScript runs, not only on Vercel’s platform. If you are new to that platform, we cover it in our primer on what Vercel is.

Step 1: Set up the project

Start with any Node.js or TypeScript project. Install the core package, one provider package, and a schema library. Zod is the common choice because the SDK uses it to validate tool inputs and structured output.

# npm, pnpm, or yarn all work
pnpm add ai @ai-sdk/openai zod

Set your provider key as an environment variable so it never lands in source control. The provider packages read the standard variable name automatically.

# .env.local
OPENAI_API_KEY=sk-your-key-here

Keep that key on the server. Model calls should run in a backend route or a serverless function, never in browser code where the key would be exposed.

Step 2: Call a model with generateText

The simplest useful call sends a prompt and waits for the full answer. generateText returns a result object; the text lives on its text property.

import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

const { text } = await generateText({
  model: openai("gpt-5"),
  prompt: "Explain what an API is in two sentences.",
});

console.log(text);

Swapping providers shows the payoff of the shared interface. Change the import and the model line, leave everything else, and the same code runs against a different model.

import { anthropic } from "@ai-sdk/anthropic";

const { text } = await generateText({
  model: anthropic("claude-sonnet-4-6"),
  prompt: "Explain what an API is in two sentences.",
});

Use generateText for work where the user does not watch the response arrive: classification, summarizing a batch of records, or a background job. For anything conversational, streaming feels far better.

Step 3: Stream the response with streamText

Waiting three to five seconds for a full answer feels broken in a chat UI. streamText starts sending text as the model produces it. On the server it exposes an async iterable, textStream, that you can loop over.

import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

const result = streamText({
  model: openai("gpt-5"),
  prompt: "Write a short welcome message for a new user.",
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

In a real app, you connect that stream to the browser. In a framework route handler, streamText gives you a response helper, and the useChat hook consumes it on the front end with no manual plumbing.

// app/api/chat/route.ts (a framework route handler)
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

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

  const result = streamText({
    model: openai("gpt-5"),
    messages,
  });

  return result.toUIMessageStreamResponse();
}
// app/page.tsx (the client component)
"use client";
import { useChat } from "@ai-sdk/react";

export default function Chat() {
  const { messages, sendMessage } = useChat();

  return (
    <div>
      {messages.map((m) => (
        <p key={m.id}>{m.role}: {/* render m.parts */}</p>
      ))}
      <button onClick={() => sendMessage({ text: "Hello" })}>Send</button>
    </div>
  );
}

The hook tracks the message list, handles the streaming response, and re-renders as tokens land. The route runs comfortably in a serverless or edge function, a tradeoff we unpack in our guide to Vercel Functions and the edge-versus-serverless choice.

Step 4: Let the model call a tool

A model on its own cannot check today’s weather, query your database, or hit an API. Tool calling closes that gap. You define a function with a description and an input schema, and the model decides when to call it. This is the mechanism underneath most AI agents.

Define a tool with the tool helper. The inputSchema tells the model what arguments to pass, and execute is your code that runs when it calls.

import { generateText, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const { text } = await generateText({
  model: openai("gpt-5"),
  prompt: "What is the weather in Lagos right now?",
  tools: {
    getWeather: tool({
      description: "Get the current weather for a city.",
      inputSchema: z.object({
        city: z.string().describe("The city name"),
      }),
      execute: async ({ city }) => {
        // Call a real weather API here.
        return { city, temperatureC: 29, condition: "sunny" };
      },
    }),
  },
  stopWhen: stepCountIs(5),
});

Two details matter. First, the model receives your tool result and writes a final natural-language answer, so text reads like a normal reply rather than raw JSON. Second, stopWhen: stepCountIs(5) caps how many tool-call rounds one request can trigger. Without a stop condition, a misbehaving model can loop and run up unbounded provider cost on a single message. Set it on every call that has tools.

When you need reliable JSON instead of a tool, generateObject fills that role. Pass a schema and read the typed object back.

import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const { object } = await generateObject({
  model: openai("gpt-5"),
  schema: z.object({
    title: z.string(),
    tags: z.array(z.string()),
  }),
  prompt: "Suggest a title and three tags for a post about DNS.",
});

Where the SDK fits, and where it does not

The Vercel AI SDK earns its place when you want provider flexibility, first-class streaming, and typed tool calls without gluing three libraries together. It stays out of your way otherwise. It is a client library, not a model host, so you still pay each provider directly and manage your own keys and rate limits.

It also does not solve storage. A chat app that remembers conversations needs a database, and something like a managed Postgres service pairs naturally with it. Our overview of what Supabase is covers one common option. The SDK handles the model conversation; persistence, auth, and infrastructure remain your call.

Two honest caveats. The pace of major versions is fast, so pin your version and read the migration notes before upgrading. And the abstraction hides provider-specific features; when you need a niche capability from one model, you may reach past the shared interface into provider options. For most LLM-powered apps, that trade is worth it.

Frequently Asked Questions

Is the Vercel AI SDK free to use?

Yes. The SDK is free and open source, published as the ai package on npm and developed in the public vercel/ai repository. You still pay whichever model provider you call, but the library itself carries no license fee.

Do I have to deploy on Vercel to use it?

No. Despite the name, the SDK is a standard TypeScript library that runs anywhere JavaScript runs: Node.js, other clouds, or your own servers. Deploying on Vercel is convenient but not required.

Which model providers does it support?

Major providers including OpenAI, Anthropic, Google, and xAI have official provider packages, and more are available from the community. Each exposes the same interface, and Vercel’s AI Gateway can route across several providers behind one endpoint.

What is the difference between generateText and streamText?

generateText waits and returns the complete response at once, which suits background jobs and batch work. streamText emits tokens as they are generated, which is what makes a chat interface feel responsive.

Do I need React to use the SDK?

No. The core functions run server-side with no UI framework. The useChat and useCompletion hooks are optional conveniences in the @ai-sdk/react package, and separate packages exist for Vue and Svelte.

What is a tool call?

A tool call is the model asking your code to run a function, such as looking up data or hitting an API. You describe the function and its inputs, the model decides when to invoke it, the SDK runs your code, and the result goes back to the model so it can finish its answer.

How do I keep tool-calling costs bounded?

Set a stop condition on every call that has tools. stopWhen: stepCountIs(N) limits how many tool-call rounds a single request can run, which prevents a looping model from generating unbounded provider charges.

Can the SDK return structured JSON instead of plain text?

Yes. generateObject and streamObject take a schema you define, usually with Zod, and return typed data that matches it. That is more reliable than parsing JSON out of a text response by hand.

Adams V.

IT Infrastructure Desk