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/openaithat 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 asmodel. - 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/reactthat 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,isRetryableand 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.
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.
// server
const result = streamText({ model, instructions, messages: await convertToModelMessages(messages) });
return result.toUIMessageStreamResponse();
// browser
const { messages, sendMessage, status, stop, error } = useChat();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
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
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 awaitsresult.finishReasonthrowsAI_NoOutputGeneratedError, so the example ends with an uncaught error.streamTextitself never throws; it reports errors through its stream and callbacks (Topic 5.2). The next example shows the same error caught fromgenerateText. - 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 awaitover an iterable. Keep the difference in mind: the loop is still being run, only by someone else.
Code & diagrams
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
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: trueStatus 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
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-filterThe 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.
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 (
instructionsforsystem,onEndbesideonFinish) and tutorials written for older versions mix the two. When something does not match, checknode_modules/ai/dist/index.d.ts: the types are the truth for the version you installed. - ▸
isRetryableis 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 readretry-afterand when to stop. - ▸
Bundle size:
airuns on the server and does not ship to the browser except through@ai-sdk/reactand the chat types. Keep provider packages and API keys in server files, and import onlyuseChatand types in client components.
Remember this
- 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
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
Three packages.
aiis the core: server-side functions such asstreamTextandgenerateText, plus the message types and the stream format.@ai-sdk/reactis the browser side for React: theuseChathook. A provider package such as@ai-sdk/openai,@ai-sdk/anthropicor@ai-sdk/googleknows how to talk to one provider and hands you a model object. You also usezod(4.x) to describe shapes in later topics. - 4
One call over many providers. You write
streamText({ model, instructions, messages }). Themodelis the only part that names a provider. Swapopenai("...")foranthropic("...")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 (fakeModelfrom@hectal/fake-llm) so examples run offline. In a real app that one line becomes a real model, and nothing else changes. - 5
Errors become one shape. Whatever the provider answered, a failed call throws an
APICallError(itsnameisAI_APICallError) withstatusCode,isRetryable,responseBodyandresponseHeaders. You write onecatch, not one per provider. The SDK also decides what is worth retrying (429 and 5xx yes, 401 no) and retries up tomaxRetriestimes, 2 by default (Topic 5.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 (instructionsinstead ofsystem,onEndnext toonFinish), so always check the version before copying an old tutorial.
Explain it without notes
What problems does an AI SDK solve, and what does it deliberately leave to you?
What do the three kinds of packages (ai, @ai-sdk/react, a provider package) do?
A call returns successfully with an empty text and finish reason length. What does that tell you, and what should the UI do?
What is isRetryable, and why does the SDK not retry a 401?
Practice
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.
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
fetchcan 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.