Mastering the Vercel AI SDK: All 8 Ways to Access LLMs in TypeScript

Mastering the Vercel AI SDK: All 8 Ways to Access LLMs in TypeScript
Photo by Kevin Canlas / Unsplash

The Node.js TypeScript ai package (developed by Vercel) has become the industry standard for building AI-powered applications. Rather than tying your codebase to vendor-specific SDKs with conflicting APIs, the ai package provides a unified, provider-agnostic interface. With a simple configuration change, you can switch between OpenAI, Anthropic, Google Gemini, Grok, or self-hosted local models like Ollama without rewriting your business logic.

In this comprehensive guide, we will explore all 8 primary ways to access AI using the ai package in TypeScript. For each approach, you'll see concrete code examples along with an analysis of its pros and cons.

1. Direct Non-Streaming Text Generation (generateText)

The simplest entry point into the AI SDK is generateText. It sends a prompt or message array to the model, waits for the entire response to complete on the server, and returns a single unified result object containing text, token usage, and response metadata.

Code Example

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

async function main() {
  const { text, usage, finishReason } = await generateText({
    model: openai('gpt-4o'),
    system: 'You are a technical writer specializing in cloud architecture.',
    prompt: 'Summarize the differences between event-driven and monolith architectures in 3 bullet points.',
  });

  console.log('--- Generated Response ---');
  console.log(text);
  console.log('\n--- Usage Stats ---');
  console.log(`Prompt Tokens: ${usage.promptTokens}, Completion Tokens: ${usage.completionTokens}`);
}

main();

Benefits

  • Simple Promise Flow: Returns standard JavaScript Promises, making error handling and async control flow straight-foward.
  • Complete Metadata: Offers full visibility into token usage, finish reasons, and raw response headers before proceeding.
  • Ideal for Offline/Background Jobs: Perfect for CLI scripts, cron jobs, automated summarization, and data pipelines where real-time streaming isn't required.

Drawbacks

  • High Perceived Latency: Users must wait until the model generates the last token before receiving any output.
  • Unsuitable for Interactive Interfaces: Blocking responses can lead to timeouts on edge functions or serverless runtimes when generating lengthy outputs.

2. Real-Time Token Streaming (streamText)

When building user-facing conversational interfaces or real-time text tools, streaming is essential. streamText begins delivering tokens to your server or client the millisecond they are emitted by the LLM provider.

Code Example

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

async function main() {
  const result = streamText({
    model: anthropic('claude-3-5-sonnet-20241022'),
    prompt: 'Write an essay exploring the philosophical implications of artificial general intelligence.',
  });

  // Consume text tokens incrementally as an AsyncIterable
  for await (const textChunk of result.textStream) {
    process.stdout.write(textChunk);
  }
}

main();

Benefits

  • Low Time-To-First-Token (TTFT): Delivers immediate feedback to users, drastically reducing perceived latency.
  • Backpressure Control: Utilizes native Web Streams / AsyncIterables under the hood, consuming memory efficiently even during long outputs.
  • Built-in HTTP Helpers: Includes server helper utilities (toDataStreamResponse, createTextStreamResponse) to stream data directly out of Next.js Route Handlers or Express endpoints.

Drawbacks

  • Complex Error Recovery: Mid-stream network interruptions or provider errors happen after HTTP headers are sent, requiring specialized client-side reconnect handling.
  • Aggregate Metrics Deferred: Token usage and final finish reasons are only accessible after the entire stream has been consumed.

3. Type-Safe Structured Data Generation (generateObject & streamObject)

LLMs are often used as processing engines that transform unstructured text into validated JSON data. Instead of parsing raw markdown text blocks with fragile regular expressions, generateObject (or its streaming counterpart, streamObject) enforces strict compliance with a Zod schema.

Code Example

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

const InvoiceSchema = z.object({
  vendor: z.string(),
  invoiceNumber: z.string(),
  date: z.string(),
  items: z.array(
    z.object({
      description: z.string(),
      amount: z.number(),
    })
  ),
  total: z.number(),
});

async function extractInvoiceData(rawText: string) {
  const { object } = await generateObject({
    model: openai('gpt-4o'),
    schema: InvoiceSchema,
    prompt: `Extract structured invoice details from this text:\n\n${rawText}`,
  });

  // TypeScript knows object is fully typed according to InvoiceSchema!
  console.log(`Vendor: ${object.vendor}`);
  console.log(`Total Amount: $${object.total}`);
}

Benefits

  • End-to-End Type Safety: TypeScript automatically infers response types directly from your Zod schemas.
  • Guaranteed Output Formats: Utilizes native provider JSON Mode / Structured Outputs features (e.g., OpenAI's strict JSON schema) to guarantee syntactic validity.
  • Partial Object Streaming: streamObject lets frontend UIs render partial JSON structures (e.g., auto-filling form fields live) as keys complete.

Drawbacks

  • Schema Overhead: Request prompts incur slightly higher token costs due to schema injection and constraints.
  • Model Failures on Complex Schemas: Smaller or lower-tier models may occasionally fail validation or hallucinate non-existent fields if schemas are overly nested.

4. Tool Calling & Autonomous Agent Loops (tool + maxSteps)

The AI SDK allows models to execute TypeScript functions as external "tools" (e.g., database lookups, API requests, vector searches). By enabling maxSteps, the SDK automatically enters an agentic execution loop: model calls tool ➡️ SDK runs function ➡️ result returned to model ➡️ model decides whether to execute another tool or return final text.

Code Example

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

const weatherTool = tool({
  description: 'Get current weather conditions for a specific location',
  parameters: z.object({
    location: z.string().describe('City name, e.g., San Francisco, CA'),
  }),
  execute: async ({ location }) => {
    // Mocked API integration
    return { location, temperature: '22°C', condition: 'Sunny' };
  },
});

async function runAgent() {
  const { text, steps } = await generateText({
    model: openai('gpt-4o'),
    prompt: 'What should I wear today in Seattle?',
    tools: { getWeather: weatherTool },
    maxSteps: 5, // Enables auto-execution agent loop up to 5 steps
  });

  console.log(`Total steps taken: ${steps.length}`);
  console.log('Final Agent Response:', text);
}

Benefits

  • Zero Boilerplate Agent Loops: Handles message formatting, tool invocation, payload parsing, and error reflection automatically.
  • Full Step Visibility: The steps array contains granular audit trails of every tool invocation, parameters supplied, and raw outputs.
  • Flexible Multi-Tool Capabilities: Can combine arbitrary tools (web scraping, SQL execution, external APIs) in a single request.

Drawbacks

  • Unbounded Token Costs: If an agent gets stuck in a loop or makes excessive tool calls, token usage can escalate quickly.
  • Requires Robust Tool Error Handling: Bugs inside custom execute functions can break the agent loop if uncaught.

5. Embeddings & Vector Operations (embed & embedMany)

For Retrieval-Augmented Generation (RAG), recommendation systems, or semantic search, text must be converted into numerical vector representations. The ai package provides embed and embedMany alongside vector distance calculation utilities like cosineSimilarity.

Code Example

import { embed, embedMany, cosineSimilarity } from 'ai';
import { openai } from '@ai-sdk/openai';

async function calculateSimilarity() {
  // Generate single embedding vector
  const { embedding: queryVector } = await embed({
    model: openai.embedding('text-embedding-3-small'),
    value: 'How do I reset my account password?',
  });

  // Batch embed multiple documents
  const { embeddings: docVectors } = await embedMany({
    model: openai.embedding('text-embedding-3-small'),
    values: [
      'To change your password, navigate to Account Settings > Security.',
      'Our refund policy guarantees returns within 30 days of purchase.',
    ],
  });

  // Calculate similarity scores
  const score1 = cosineSimilarity(queryVector, docVectors[0]);
  const score2 = cosineSimilarity(queryVector, docVectors[1]);

  console.log(`Relevance Doc 1 (Password): ${score1.toFixed(3)}`); // High score
  console.log(`Relevance Doc 2 (Refund): ${score2.toFixed(3)}`);   // Low score
}

Benefits

  • Standardized Embedding API: Consistent interface regardless of whether you use OpenAI, Cohere, or Google embedding models.
  • Built-in Batching: embedMany automatically handles batching payloads efficiently for bulk database ingestion.
  • Math Utilities Included: Ships with standard vector comparison algorithms like cosineSimilarity out of the box.

Drawbacks

  • No Built-in Vector DB: The SDK focuses solely on vector generation—storing, indexing, and querying requires an external vector database (e.g., Pinecone, Pgvector, Qdrant).

6. Framework Hooks for Web UIs (useChat & useCompletion)

Beyond backend Node.js code, the ecosystem includes UI packages (@ai-sdk/react, @ai-sdk/vue, @ai-sdk/svelte). These client-side hooks pair directly with backend server endpoints using streamText to manage complex conversational chat state out-of-the-box.

Backend API Route (Next.js App Router)

// app/api/chat/route.ts
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-4o'),
    messages,
  });

  // Stream structured message data to client
  return result.toDataStreamResponse();
}

Frontend React Component

// app/chat/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';

export default function ChatComponent() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/chat',
  });

  return (
    <div className="flex flex-col h-screen max-w-xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-4">
        {messages.map((m) => (
          <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
            <span className="inline-block p-2 rounded bg-gray-200 dark:bg-gray-800">
              <strong>{m.role}: </strong>{m.content}
            </span>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2 mt-4">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask a question..."
          className="flex-1 border p-2 rounded"
        />
        <button type="submit" disabled={isLoading} className="bg-blue-600 text-white px-4 py-2 rounded">
          Send
        </button>
      </form>
    </div>
  );
}

Benefits

  • Reduces Frontend Boilerplate: Eliminates manual websocket/HTTP stream management, input bindings, optimistic updating, and message state arrays.
  • Support for Multi-modal Inputs: useChat natively handles file uploads, image attachments, and tool call rendering state.
  • Cross-Framework: Available for React, Next.js, Vue, Svelte, and Angular.

Drawbacks

  • Tightly Coupled Stream Protocol: Client hooks expect responses formatted via the SDK's proprietary toDataStreamResponse() format.
  • Custom State Overheads: Highly bespoke UI states (e.g. branchable timelines) may require overriding internal hook behaviors.

7. Generative React Server Components (streamUI)

For Next.js applications using React Server Components (RSC), @ai-sdk/rsc offers streamUI. Instead of returning raw text or JSON, the model streams actual React UI components directly from the server to the client on demand.

Code Example

// actions.tsx (Server Action)
'use server';

import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

// Client React Components
import { StockCard } from '@/components/StockCard';
import { LoadingSkeleton } from '@/components/LoadingSkeleton';

export async function submitUserMessage(prompt: string) {
  const result = await streamUI({
    model: openai('gpt-4o'),
    prompt,
    initial: <LoadingSkeleton />,
    tools: {
      showStockPrice: {
        description: 'Get current stock ticker data and render stock card',
        parameters: z.object({ symbol: z.string() }),
        generate: async ({ symbol }) => {
          const data = await fetchStockData(symbol);
          return <StockCard symbol={symbol} price={data.price} change={data.change} />;
        },
      },
    },
  });

  return result.value;
}

Benefits

  • True Generative UI: The AI determines when to render actual rich interactive widgets (charts, maps, booking widgets) rather than plain text.
  • Server-Side Security: Data fetching and component logic stay entirely on the server.

Drawbacks

  • Framework Lock-in: Requires Next.js App Router and React Server Components environment.
  • Experimental Status: Vercel currently recommends AI SDK UI for production while RSC features mature.

8. Multi-Provider Swappability & Local Model Access (Ollama / Vercel AI Gateway)

The core architectural strength of the ai package lies in model abstraction. You can call models hosted by cloud providers, pass requests through proxies like the Vercel AI Gateway, or route calls to local offline models via Ollama using the OpenAI-compatible interface.

Code Example

import { generateText } from 'ai';
import { openai, createOpenAI } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';

// 1. Standard Cloud Provider (OpenAI)
const res1 = await generateText({
  model: openai('gpt-4o'),
  prompt: 'Hello OpenAI!',
});

// 2. Swapping to Anthropic (Just change 1 line!)
const res2 = await generateText({
  model: anthropic('claude-3-5-sonnet-20241022'),
  prompt: 'Hello Anthropic!',
});

// 3. Local Model via Ollama (OpenAI-compatible local endpoint)
const localOllama = createOpenAI({
  baseURL: 'http://localhost:11434/v1',
  apiKey: 'ollama', // dummy key required by client
});

const res3 = await generateText({
  model: localOllama('llama3.2'),
  prompt: 'Hello local offline LLM!',
});

Benefits

  • Zero Vendor Lock-in: Switch primary providers or implement model fallbacks with zero business logic changes.
  • Local Offline Development: Test and develop locally using Ollama or LM Studio without incurring API billing charges.
  • Unified Telemetry: Identical logging and error handling regardless of which backend model processes the request.

Drawbacks

  • Feature Parity Differences: Not all models support all features (e.g. local 3B models may struggle with strict structured outputs or tool execution compared to GPT-4o).

Summary: Choosing the Right Access Method

Access MethodPrimary Use CaseComplexityStreaming?
generateTextBackground tasks, scripts, cron jobs, CLI utilitiesLowNo
streamTextCustom text streams, REST endpoints, web server responsesLow-MediumYes
generateObjectData extraction, form parsing, semantic classificationLowOptional
Tools & Agent LoopsMulti-step workflows, web browsing, automated problem solvingMedium-HighYes
embed / embedManyRAG pipelines, semantic search, vector indexingLowNo
useChat / useCompletionFull-stack Web Chat apps (React, Vue, Svelte)LowYes
streamUIReact Server Components streaming generative UI widgetsHighYes
Local Models / OllamaLocal offline testing, privacy-focused computeLowYes

Conclusion

The Vercel AI SDK (ai) simplifies AI development in Node.js and TypeScript. Whether you need simple text generation, structured JSON extraction, multi-step tool-calling agents, or rich generative React components, the SDK provides a unified set of primitives.
By understanding these access patterns, you can select the right architecture for your project's performance requirements, budget, and desired user experience.