← Back to dashboard
vercel-ai-sdkaifresh

Vercel AI SDK + AI Gateway Integration Guide

What is the Vercel AI SDK?

The real model

Typed wrapper + streaming primitives + AI Gateway routing + React hooks.

Built around the `LanguageModelV2` interface so providers are interchangeable. `streamText({ model, messages, tools })` returns a typed stream you can pipe into a server response or pass to `useChat`. Multi-step tool calls auto-loop until the model stops calling tools. `generateObject({ schema })` uses provider-native structured outputs where available, falls back to retry-on-Zod-validate elsewhere. AI Gateway sits in front of providers for per-call routing, fallback, and aggregated billing — great for cost optimisation. For codeAmani, the SDK is the abstraction layer; AI Gateway is the cost lever.

Five AI SDK primitives

Same verbs across every provider. Swap models with one string.

Text
██╗   ██╗███████╗██████╗  ██████╗███████╗██╗          █████╗ ██╗    ███████╗██████╗ ██╗  ██╗
██║   ██║██╔════╝██╔══██╗██╔════╝██╔════╝██║         ██╔══██╗██║    ██╔════╝██╔══██╗██║ ██╔╝
██║   ██║█████╗  ██████╔╝██║     █████╗  ██║         ███████║██║    ███████╗██║  ██║█████╔╝
╚██╗ ██╔╝██╔══╝  ██╔══██╗██║     ██╔══╝  ██║         ██╔══██║██║    ╚════██║██║  ██║██╔═██╗
 ╚████╔╝ ███████╗██║  ██║╚██████╗███████╗███████╗    ██║  ██║██║    ███████║██████╔╝██║  ██╗
  ╚═══╝  ╚══════╝╚═╝  ╚═╝ ╚═════╝╚══════╝╚══════╝    ╚═╝  ╚═╝╚═╝    ╚══════╝╚═════╝ ╚═╝  ╚═╝

Vercel AI SDK + AI Gateway Integration Guide

Focus: One TypeScript interface (generateText / streamText / generateObject) over every provider codeAmani uses — Anthropic, OpenAI, Gemini, DeepSeek, HuggingFace, ElevenLabs — with AI Gateway adding a single API key, automatic fallback chains, and built-in cost/latency observability. This replaces the hand-rolled routing in AI_WORKFLOWS.md.

Overview

Here is the big picture — one interface, many providers, all routed through a single Gateway:

Two complementary pieces:

  • AI SDK (ai + @ai-sdk/* provider packages) — a unified, framework-agnostic API. Swap providers by changing one model value; the rest of the code is identical.
  • AI Gateway — a Vercel-hosted unified endpoint. When you pass model as a string in provider/model form (e.g. "anthropic/claude-sonnet-4-20250514"), the SDK routes through the Gateway using one AI_GATEWAY_API_KEY — no per-provider key wrangling. The Gateway adds cost/latency/usage dashboards, auth, and model fallbacks.

Net effect for codeAmani: the AI routing policy (Claude primary, OpenAI for structured output, HF/others as fallbacks) becomes config, not app code.

Official Documentation


1. Install

Bash
npm install ai                       # core
# Optional explicit providers (only if NOT using the string/Gateway form):
npm install @ai-sdk/anthropic @ai-sdk/openai @ai-sdk/google @ai-sdk/deepseek

2. Credentials

Bash
# Gateway path (recommended) — one key for all providers:
AI_GATEWAY_API_KEY=...
# OR direct-provider path — one key each:
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...
GOOGLE_GENERATIVE_AI_API_KEY=...

On Vercel, the Gateway can also authenticate via the deployment's OIDC token (VERCEL_OIDC_TOKEN) with no key at all. Keep every key server-side.

3. Generate text (Gateway via model string)

TypeScript
import { generateText } from "ai";

const { text } = await generateText({
  model: "anthropic/claude-sonnet-4-20250514", // string -> routed through AI Gateway
  prompt: "Explain M-Pesa STK Push in one sentence.",
});

Switching providers is a one-line change — "openai/gpt-5", "google/gemini-2.5-flash", "deepseek/deepseek-chat". The surrounding code never changes.

4. Stream + structured output

TypeScript
import { streamText, generateObject } from "ai";
import { z } from "zod";

const result = streamText({ model: "google/gemini-2.5-flash", prompt });
for await (const chunk of result.textStream) process.stdout.write(chunk);

const { object } = await generateObject({
  model: "openai/gpt-5",
  schema: z.object({ amount: z.number(), phone: z.string() }),
  prompt: "Extract the payment amount and phone from: 'Send 500 to 0712345678'",
});

4a. useChat — streaming chat UI (AI SDK v5)

For a React chat UI, the useChat hook (from @ai-sdk/react) handles message state, streaming, and input wiring; the matching route handler runs streamText on the server and returns result.toUIMessageStreamResponse(). This targets AI SDK v5 — the message shape is UIMessage (rendered via message.parts, not a flat content string), and the client posts through a DefaultChatTransport. Keep the API route server-side so your AI_GATEWAY_API_KEY never reaches the browser.

TSX
// app/(public)/chat/page.tsx
"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useState } from "react";

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({ api: "/api/chat" }),
  });
  const [input, setInput] = useState("");

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          <strong>{m.role}: </strong>
          {m.parts.map((part, i) =>
            part.type === "text" ? <span key={i}>{part.text}</span> : null,
          )}
        </div>
      ))}

      <form
        onSubmit={(e) => {
          e.preventDefault();
          if (!input.trim()) return;
          sendMessage({ text: input });
          setInput("");
        }}
      >
        <input
          value={input}
          placeholder="Uliza chochote..."
          onChange={(e) => setInput(e.target.value)}
        />
      </form>
    </div>
  );
}
TypeScript
// app/api/chat/route.ts
import { streamText, convertToModelMessages, type UIMessage } from "ai";

export const maxDuration = 30; // allow streaming responses up to 30s

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

  const result = streamText({
    model: "anthropic/claude-sonnet-4-20250514", // string -> AI Gateway
    system: "You are a helpful assistant for Kenyan SMEs.",
    messages: await convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

Gotcha: in v5 the helper is toUIMessageStreamResponse(), not the v4 toDataStreamResponse(), and you must pass await convertToModelMessages(messages) to streamText — handing the raw UIMessage[] (with parts) straight to the model throws. The client reads message.parts, so there is no message.content string to render.

5. Fallback chains (the replacement for hand-rolled routing)

You can let the Gateway handle failover automatically — here is the order it walks:

TypeScript
const { text } = await generateText({
  model: "anthropic/claude-sonnet-4-20250514", // primary
  prompt,
  providerOptions: {
    gateway: {
      models: ["openai/gpt-5", "google/gemini-2.5-flash"], // tried in order if primary fails
      // order: ["anthropic", "vertex"], // or pin provider routing order
    },
  },
});

Cost, latency, and tokens-per-model are visible in the AI Gateway dashboard — no custom metrics code needed.

codeAmani notes

  • Replaces AI_WORKFLOWS.md plumbing: the provider routing + fallback chains currently hand-rolled there map directly to providerOptions.gateway.models / order. Keep the policy (Claude primary, OpenAI for structured/function calling, HF for open models) but let the Gateway execute it. ElevenLabs (voice) and DeepSeek are reachable through the same interface — see the eleven-labs and deepseek guides.
  • Security: AI_GATEWAY_API_KEY (or per-provider keys) stay in .env.local / Vercel env; call only from server actions / API routes, never client components.
  • African market: prefer streamText so 2G/3G users see tokens immediately; pick smaller fallback models (e.g. gemini-2.5-flash) to cut cost + latency on spiky SME traffic, and use the Gateway dashboard to watch per-model spend.
  • Edge-safe: the SDK runs on Vercel Edge — pairs well with the Upstash cache/queue for memoizing expensive completions.