Command Palette

Search for a command to run...

Hectal
PHASE 5Intermediate ~23 min· topic 1 of 9

Topic 5.1

Why Use an SDK

In one line

An SDK is a ready-made library for the plumbing you wrote by hand in Phases 1 to 4. The Vercel AI SDK gives one API over many model providers and a matching hook for the browser, but it does not render, secure or pay for anything: those stay your job.

Think of it like this

A travel adapter. Your phone charger always has the same plug, and every country has a different wall socket. You do not buy a new charger in each country: you put an adapter between the two. An AI SDK is the adapter: your code keeps one plug, and the SDK fits it to each provider's socket.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

SDK
Software development kit: a library that wraps a service (here, model providers) so you do not write the raw requests yourself.
Provider
The company that runs the model and its API, such as OpenAI, Anthropic or Google.
Provider package
A small npm package such as @ai-sdk/openai that turns the SDK's calls into that provider's API.
Model object
What a provider package gives you for one model, for example openai("model-name"). You pass it as model.
streamText
The core server function: sends the prompt to the model and gives back the answer as a stream.
generateText
The same call without streaming: it waits and returns the whole answer.
useChat
The React hook in @ai-sdk/react that holds the messages and the status of a chat in the browser.
APICallError
The SDK's error for a failed provider call. It carries statusCode, isRetryable and the response body.
maxRetries
How many times the SDK retries a failed call that is worth retrying. The default is 2.

Step by step

01The hand-built way

This is Phase 0's chat call, shortened. Everything the screen needs (the loop, the decoding, the state names) is code you own.

by hand (Phases 0 to 4)whole filejs
const res = await fetch("/api/chat", { method: "POST", body });
if (!res.ok) throw new Error("status " + res.status);
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
  const { done, value } = await reader.read();
  if (done) break;
  append(value); // plus: SSE parsing, partial lines, abort, retry, state names...
}

02The same thing with the SDK

On the server one call replaces the provider request, and on the browser one hook replaces the loop and the state. The route returns toUIMessageStreamResponse(), which sends a stream the hook understands.

with the SDKwhole filejsx
// server
const result = streamText({ model, instructions, messages: await convertToModelMessages(messages) });
return result.toUIMessageStreamResponse();

// browser
const { messages, sendMessage, status, stop, error } = useChat();
The same thing with the SDKdiagram
Rendering diagram…

03Run one call and read its results

The first live example calls streamText with the stand-in model and reads the answer piece by piece with result.textStream. Then it asks for the finish reason and the token usage. Each of those is something you parsed by hand in Phase 2.

In a real app only the model line differs. The comment shows it.

04One call, four providers, four failures

The second example runs the same function against four stand-in models: one that answers, one that is rate limited (429), one that rejects the key (401) and one that is down (503). The errors arrive in the same shape, with a flag that says whether a retry could help.

Then three edge cases: an empty answer, an answer cut off by the length limit, and an answer stopped by the provider's safety filter. All three finish without an error. Your screen has to look at the finish reason, not only at whether an error happened.

05Where the SDK stops

The SDK gives you messages and statuses; the screen, the limits and the safety are yours. A route that anyone on the internet can call will happily spend your money through the SDK, just as it would through raw fetch. Topic 5.5 shows where authentication goes, and Phase 9 covers the rest.

Try it yourself

  1. 1

    Change the provider by changing one line

    In the first example, change the model to fakeModel("A different provider, same code."). Run it.

    Everything below the model line still works: the pieces, the finish reason, the usage. That is what "one API over many providers" means in practice.

  2. 2

    Turn the stand-in into a failure

    In the first example, replace the model with fakeModel({ fail: { status: 429 } }) and run it.

    The loop produces no pieces. The SDK prints the error to the console (AI_APICallError: Request failed with status 429), and the line that awaits result.finishReason throws AI_NoOutputGeneratedError, so the example ends with an uncaught error. streamText itself never throws; it reports errors through its stream and callbacks (Topic 5.2). The next example shows the same error caught from generateText.

  3. 3

    Count the lines you did not write

    Open Topic 0.4 and look at the reading loop, then compare it with the first example here.

    Both show the answer piece by piece. One is 12 lines of stream handling, the other is a for await over an iterable. Keep the difference in mind: the loop is still being run, only by someone else.

Code & diagrams

streamText: the answer as a stream, plus its finish reason and usage

No fetch, no reader, no parser: the SDK did that. The usage numbers come from the stand-in model, which counts words as tokens; a real provider reports its own token counts.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

8 pieces
first: "Masala" last: " milk."
joined: Masala chai is black tea boiled with milk.
finish: stop
output tokens: 8 | input tokens counted: true
One function, four providers, and the edge cases that are not errors

Status 429 and 503 are worth retrying and 401 is not: a wrong key will be wrong again. The last three calls did not throw. The empty answer, the cut-off answer and the filtered answer are all successful calls, so your screen must check the finish reason (Topics 1.1 and 3.2).

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

--- failures
A works: "Hello from A." | finish: stop
B rate limited: AI_APICallError | status 429 | retry could help: true
C bad key: AI_APICallError | status 401 | retry could help: false
D server down: AI_APICallError | status 503 | retry could help: true
--- not errors, but not a normal answer either
empty answer: "" | finish: stop
cut off by length: "The return window is 7" | finish: length
stopped by filter: "I can" | finish: content-filter
app/api/chat/route.ts (real app)ts

The real route file in a Next.js app. Install with: npm install ai @ai-sdk/react @ai-sdk/openai zod. The model name is a placeholder: use the current name from your provider's documentation. (With the Vercel AI Gateway you can also pass a plain "provider/model-name" string as `model`, with no provider package.)

import { openai } from "@ai-sdk/openai"; // @ai-sdk/openai 4.x at the time of writing
import { convertToModelMessages, streamText, type UIMessage } from "ai";

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

  const result = streamText({
    model: openai("provider/model-name"), // the ONE line that names a provider
    instructions: "You are Mitra, the help assistant of Chai & Co.",
    messages: await convertToModelMessages(messages),
    abortSignal: req.signal,
  });

  return result.toUIMessageStreamResponse();
}
// The API key is read from the OPENAI_API_KEY environment variable on the server.
// To change provider, import anthropic or google instead and change that one model line.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget that a successful call can be empty

In the second example, show only the answer: replace the console.log line in the try block with console.log(label + ": " + r.text); and read the last three lines.

terminal
$ The console, for the three edge cases
── what you'll see ──
empty answer:
cut off by length: The return window is 7
stopped by filter: I can

Myth vs fact

Myth

With an SDK, switching providers is only a config change.

Fact

The call shape is the same, but models differ in quality, limits, tool support and price. You still re-test the prompts and the UI against the new model.

Myth

The SDK keeps my API key safe.

Fact

It reads the key you give it on the server. Whether it stays secret depends on your code never sending it to the browser (Topic 0.2).

Myth

An SDK is only for beginners.

Fact

Teams ship large products on it. The plumbing is the least interesting part of an AI feature, and a tested library handles its rare cases (a split packet, an abort) better than most hand-written code.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Pin the SDK version and read the changelog on upgrades. AI SDK 7 moved names (instructions for system, onEnd beside onFinish) and tutorials written for older versions mix the two. When something does not match, check node_modules/ai/dist/index.d.ts: the types are the truth for the version you installed.

  • ▸

    isRetryable is the provider's hint, not a promise. A 429 that says the daily quota is gone will fail again however many times you retry. Topic 5.6 shows how to read retry-after and when to stop.

  • ▸

    Bundle size: ai runs on the server and does not ship to the browser except through @ai-sdk/react and the chat types. Keep provider packages and API keys in server files, and import only useChat and types in client components.

Remember this

  1. 1

    The problem this solves: Every model provider speaks a slightly different language: different URLs, different JSON, different stream formats, different error bodies. If you code against one, switching costs a rewrite. And in the browser you keep rewriting the same chat plumbing (states, stop, retry, partial answers). An SDK takes the repeated part off you so you can spend your time on the screen.

  2. 2

    What you built by hand. In Phases 1 to 4 you wrote: the request and the response shapes (1.1), the error mapping (1.3), the read loop (0.4, 2.2), the Server-Sent Events parser (2.4, 2.5), the stop button (3.1), the retry button (3.4), the reducer (4.2), and the saved chats (4.4). The SDK ships a tested version of each of these. Reading its output will feel familiar, because you know the problem it is solving.

  3. 3

    Three packages. ai is the core: server-side functions such as streamText and generateText, plus the message types and the stream format. @ai-sdk/react is the browser side for React: the useChat hook. A provider package such as @ai-sdk/openai, @ai-sdk/anthropic or @ai-sdk/google knows how to talk to one provider and hands you a model object. You also use zod (4.x) to describe shapes in later topics.

  4. 4

    One call over many providers. You write streamText({ model, instructions, messages }). The model is the only part that names a provider. Swap openai("...") for anthropic("...") and the rest of the route is unchanged, including the stream your browser receives and the errors you catch. In this course the model is a stand-in (fakeModel from @hectal/fake-llm) so examples run offline. In a real app that one line becomes a real model, and nothing else changes.

  5. 5

    Errors become one shape. Whatever the provider answered, a failed call throws an APICallError (its name is AI_APICallError) with statusCode, isRetryable, responseBody and responseHeaders. You write one catch, not one per provider. The SDK also decides what is worth retrying (429 and 5xx yes, 401 no) and retries up to maxRetries times, 2 by default (Topic 5.6).

  6. 6

    What the SDK does not do. It does not draw your screen. It does not decide who may call your route or how often, so authentication, rate limits and input size limits are still yours (Topics 1.5, 3.6). It does not pay your provider bill. It does not make the model correct: a confident wrong answer is still a 200. And it does not freeze in time: AI SDK 7 renamed things from older versions (instructions instead of system, onEnd next to onFinish), so always check the version before copying an old tutorial.

Explain it without notes

01

What problems does an AI SDK solve, and what does it deliberately leave to you?

02

What do the three kinds of packages (ai, @ai-sdk/react, a provider package) do?

03

A call returns successfully with an empty text and finish reason length. What does that tell you, and what should the UI do?

04

What is isRetryable, and why does the SDK not retry a 401?

Practice

01

Write a function summarise(model, text) using generateText that returns the text, or the string "Mitra is busy, please try again." for a retryable error and "Mitra is not set up correctly." for a non-retryable one.

02

Call your function with the four stand-in models from the second example and print what each returns.

Trade-offs

  • ↔

    An SDK saves weeks of plumbing and gives you tested edge cases, in exchange for a dependency whose names and defaults change between major versions. For a quick feature it is clearly worth it. For a very small feature (one non-streamed call) plain fetch can be fine.

  • ↔

    One API over many providers makes switching cheap, but you can only rely on features every provider supports. Provider-specific options go through providerOptions, and using them ties that part of your code to one provider again.

Done when you can

  • I can name the three kinds of AI SDK package and what each does.

  • I can say which parts of Phases 1 to 4 the SDK replaces.

  • I can read an APICallError (statusCode, isRetryable) and decide what to show.

  • I can tell a successful but unusable answer (empty, length, content-filter) from an error.

  • I can list what the SDK does not do for me: screen, limits, authentication, cost, correctness.