cd /news/developer-tools/vercel-ai-sdk-vs-calling-model-apis-… · home topics developer-tools article
[ARTICLE · art-64044] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Vercel AI SDK vs calling model APIs directly: what you actually gain

A developer compared using the Vercel AI SDK versus calling model APIs directly in production Next.js apps. The SDK provides built-in streaming, provider agnosticism, and unified tool call handling, while raw fetch is simpler for one-shot completions. The tradeoff depends on whether the app needs streaming, multi-provider support, or middleware for caching and logging.

read7 min views1 publishedJul 17, 2026

You can ship a working LLM feature with nothing but fetch

and a model's REST endpoint. People do it every day, and for a one shot completion it is honestly fine. So the real question is not "can I call the API directly" but "what am I giving up by not adding the Vercel AI SDK, and is that tradeoff worth a dependency?"

I have wired both approaches into production Next.js apps, and the answer is not the blanket "always use the SDK" you see on Twitter. Here is what the abstraction actually buys you, where it earns its footprint, and when reaching for raw fetch

is the smarter call. If you are already deep into building AI products with Next.js, some of this will be familiar, but the middleware section is where most people leave value on the table.

Here is a bare completion against a chat endpoint. No SDK, no wrapper, just the request.

async function ask(prompt: string) {
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: "gpt-4o",
      messages: [{ role: "user", content: prompt }],
    }),
  });

  const data = await res.json();
  return data.choices[0].message.content;
}

That's the whole thing. If your feature is a single server side completion (a summarizer, a classifier, a one shot rewrite) this is clean and you should not feel bad about it. No bundle cost, no new API to learn, and you can read every byte on the wire.

The pain starts the moment you want any of these: streaming tokens to the browser, swapping to a different provider, handling tool calls, or attaching caching and logging without touching your feature code. Each of those is solvable by hand. The question is whether you want to solve them by hand every time.

The same completion, streamed, through the SDK:

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

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

  const result = streamText({
    model: openai("gpt-4o"),
    prompt,
  });

  return result.toDataStreamResponse();
}

Three things happened here that are annoying to do with raw fetch

.

First, streaming. streamText

gives you a properly framed data stream that the client hook consumes with no manual parsing of server sent event chunks. If you have ever hand parsed data:

lines out of a readable stream, you know why this matters.

Second, provider agnosticism. Swapping OpenAI for Anthropic is a single line:

import { anthropic } from "@ai-sdk/anthropic";
// model: openai("gpt-4o")  ->
model: anthropic("claude-sonnet-4"),

The unified API spans OpenAI, Anthropic, Google, Mistral, and dozens of other providers, so the rest of your code does not change. With raw calls, each provider has its own request shape, its own streaming format, and its own tool call schema, and you end up writing an adapter layer yourself. That adapter layer is basically the SDK, except untested and maintained by you.

Third, tool calls. Streaming partial tool calls as part of the data stream is now stable, which means the model can call your functions and you can render the arguments as they arrive instead of waiting for the full response. Doing this by hand against multiple providers is where the footgun count really climbs.

Here is the honest comparison:

Concern Raw fetch Vercel AI SDK
One shot completion Trivial Trivial (slight overhead)
Token streaming to browser Manual SSE parsing Built in
Switch provider Rewrite request + parsing One line
Tool calls across providers Per provider schema Unified
Caching / logging / guardrails Wrap every call site Middleware, one place
Bundle footprint Zero Real, but modest

This is the part people underrate. Middleware lets you intercept and modify model calls without changing a single line of application code. Guardrails, caching, and logging all live in one wrapper instead of being smeared across every call site.

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

const cachingMiddleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    const key = hash(params);
    const hit = await cache.get(key);
    if (hit) return hit;

    const result = await doGenerate();
    await cache.set(key, result);
    return result;
  },
};

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: cachingMiddleware,
});

Now every call through model

is cached, and your route handlers do not know or care. Swap the middleware for a logging wrapper and you get full request tracing with zero call site edits. Chain several and you have guardrails plus caching plus logging composed cleanly.

The caching case connects directly to your bill. Repeated identical prompts are pure waste, and a cache in front of the model is one of the highest leverage moves for LLM inference cost. With raw fetch

you would bolt this onto each call site and hope nobody adds a new one that forgets the cache. Middleware makes the wrapper the only path in.

On the frontend, useChat

handles the loop you would otherwise build by hand: appending messages, streaming the assistant reply token by token, tracking and error state.

"use client";
import { useChat } from "ai/react";

export function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <form onSubmit={handleSubmit}>
      {messages.map((m) => (
        <div key={m.id}>{m.role}: {m.content}</div>
      ))}
      <input value={input} onChange={handleInputChange} />
    </form>
  );
}

That is a streaming chat UI with optimistic input and error handling in about ten lines. Building the same thing on raw fetch

means owning the readable stream reader, the incremental state updates, and the reconnection edge cases. It is doable. It is also exactly the kind of undifferentiated plumbing that eats a sprint.

The abstraction is not free, so here are the cases where I reach for raw fetch

instead.

You are doing a single server side completion with no streaming and no provider switching. A classifier, a batch summarizer, a cron job that rewrites text. The SDK adds a dependency for capabilities you will never call.

You are not in a JavaScript or TypeScript frontend context at all. If your LLM calls live in a Python service or an edge function in another runtime, the SDK's frontend ergonomics (useChat

, the data stream protocol) are irrelevant, and you are paying for surface area you cannot use.

You need byte level control over the raw request for a provider feature the SDK has not surfaced yet. Abstractions lag the underlying APIs by design. When you hit that edge, drop down to fetch

for that one call and keep the SDK for the rest. A mixed codebase is completely valid.

Bundle footprint matters for you and the feature is trivial. On a marketing site with one tiny AI widget, shipping the SDK to every visitor for a single completion is a poor trade.

Walk it top to bottom and stop at the first yes.

Do you need token streaming to the browser?      -> yes: use the SDK
Will you switch providers or A/B two models?     -> yes: use the SDK
Do you need tool calls, especially streamed?     -> yes: use the SDK
Do you want caching/logging/guardrails in one    -> yes: use the SDK (middleware)
  place instead of per call site?
Is it a single server side completion, one       -> yes: raw fetch is fine
  provider, no streaming?
Not a JS/TS frontend runtime at all?             -> raw fetch (or that runtime's client)

Most real product features hit one of the first four within a month. That is why the SDK is the default recommendation. It is not that raw calls are wrong. It is that the features you will inevitably want are the ones the SDK already handles, tested and maintained, so you are not the one paying to build and debug them.

What does the Vercel AI SDK do?

It gives you a unified API for calling LLMs across many providers, with built in token streaming, tool call handling, language model middleware for caching and logging and guardrails, and React hooks like useChat

for the frontend loop.

Is the Vercel AI SDK free?

Yes, the SDK itself is open source and free. You still pay each model provider for the tokens you use, and if you deploy on Vercel you pay for that hosting, but the library carries no license fee.

Does the Vercel AI SDK work with Claude and Gemini?

YY\ˈ]�\ܝ�[���X�

�]YJK����H

�[Z[�JK�[�RKZ\��[[�ޙ[��و�\��ݚY\����Y�H�[YH[�\��X�K���]�[���]�Y[�[H\�H�[��H[�H�[��H[�[����K���KKB���Y�[�H�[�HY\����]�][��[�[�[�H�ݙ\�][�[ܙH]Z[ۈ�^H�]WJ��]Y\��\��[��YK؛��K�����Y�[�H�[�\��\�Y\ۈ[�\��ۈ�]H[��[��]\�^X�HH�[�و�ܚ�HZ�HۗJ��]Y\��\��[��YK��\��X�\�K����KKB�����H��[Y[�Y�[�\��]\����Y��\�[�8�%�\�[�\��]�\�X][ۜ�[�H\�H�[��[��[���X�[ۋ��

── more in #developer-tools 4 stories · sorted by recency
── more on @vercel 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/vercel-ai-sdk-vs-cal…] indexed:0 read:7min 2026-07-17 ·