n4nAI

Vercel AI SDK streamText tutorial with GPT-4o and Claude 3.5

Step-by-step tutorial for Vercel AI SDK streamText with GPT-4o and Claude 3.5. Set up providers, stream responses, and handle errors in Node.

n4n Team3 min read606 words

Audio narration

Coming soon — every post will get a voice note here.

The vercel ai sdk streamtext gpt-4o claude combination gives you one streaming API across two leading model families. This tutorial builds a runnable Node.js script that calls GPT-4o and Claude 3.5 Sonnet through the AI SDK, prints streamed tokens, and cleans up on abort.

Prerequisites

  • Node.js 18.18+ or 20+ (native fetch and async iterators).
  • A package manager (npm or pnpm).
  • Valid OPENAI_API_KEY and ANTHROPIC_API_KEY environment variables.
  • TypeScript is optional but recommended; the snippets below are plain ESM JavaScript with a TypeScript section at the end.

If you prefer a single endpoint that fronts both providers with automatic fallback, keep a gateway key handy—we show that variant later.

Project setup

Create a directory and install the SDK plus the two provider packages.

mkdir ai-sdk-stream && cd ai-sdk-stream
npm init -y
npm install ai @ai-sdk/openai @ai-sdk/anthropic

The ai package ships streamText. The provider packages adapt OpenAI and Anthropic to the AI SDK interface. No telemetry or network calls occur on install.

Configure providers

Import the provider factories and construct model references. Model IDs follow each provider’s naming.

// models.mjs
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';

export const gpt4o = openai('gpt-4o');
export const claude35 = anthropic('claude-3-5-sonnet-20240620');

These objects are thin descriptors. You can reuse them across many streamText calls.

Stream from GPT-4o

streamText returns a result object immediately. Token chunks arrive via textStream, an async iterable.

// stream-gpt.mjs
import { streamText } from 'ai';
import { gpt4o } from './models.mjs';

const result = streamText({
  model: gpt4o,
  prompt: 'Explain backpressure in streaming pipelines in one paragraph.',
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

Run it:

node stream-gpt.mjs

Expected output (truncated):

Backpressure is a flow-control mechanism where a consumer signals a producer to slow down when it cannot keep up, preventing buffer bloat or memory exhaustion in streaming pipelines...

The process exits when the stream closes. If the API errors, the loop throws—we handle that below.

Stream from Claude 3.5

Swap the model reference. The call shape is identical.

// stream-claude.mjs
import { streamText } from 'ai';
import { claude35 } from './models.mjs';

const result = streamText({
  model: claude35,
  prompt: 'Explain backpressure in streaming pipelines in one paragraph.',
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

Claude 3.5 Sonnet returns different phrasing but the same streaming contract:

In streaming systems, backpressure occurs when a downstream stage processes data slower than the upstream producer emits it, causing the system to propagate a signal that throttles the source...

Runtime model selection

A real app often picks the model per request. Wrap the logic in a function that accepts a provider name.

// chat.mjs
import { streamText } from 'ai';
import { gpt4o, claude35 } from './models.mjs';

export async function streamFrom(provider, prompt) {
  const model = provider === 'claude' ? claude35 : gpt4o;
  const result = streamText({ model, prompt });
  for await (const chunk of result.textStream) {
    process.stdout.write(chunk);
  }
  process.stdout.write('\n');
}

// CLI entry
const [, , provider = 'gpt', prompt = 'Say hello.'] = process.argv;
streamFrom(provider, prompt).catch((err) => {
  console.error('Stream failed:', err.message);
  process.exit(1);
});

Usage:

node chat.mjs gpt "List three HTTP status codes for rate limiting."
node chat.mjs claude "List three HTTP status codes for rate limiting."

System prompts and messages

streamText accepts either prompt (string) or messages (array). Use system for instructions that should not appear in the conversation history.

import { streamText } from 'ai';
import { gpt4o } from './models.mjs';

const result = streamText({
  model: gpt4o,
  system: 'You are a terse senior engineer. No markdown.',
  messages: [
    { role: 'user', content: 'How do I cache LLM responses across requests?' },
  ],
});

for await (const chunk of result.textStream) process.stdout.write(chunk);

The Anthropic provider maps system to Claude’s top-level system parameter automatically. You do not need to prepend it to the first message.

Capture full text and usage

streamText also exposes text (a promise resolving to full text) and usage (token counts). In a server you await those alongside the stream.

const result = streamText({ model: gpt4o, prompt: 'Hi' });

for await (const chunk of result.textStream) process.stdout.write(chunk);

const finalText = await result.text;
const usage = await result.usage;
console.error(`\nTokens: ${usage.promptTokens} prompt / ${usage.completionTokens} completion`);

The usage object matches the OpenAI token accounting shape, which makes it easy to forward to billing.

Abort and cleanup

Pass an AbortSignal to cancel mid-stream. This matters in HTTP servers when the client disconnects.

const controller = new AbortController();
setTimeout(() => controller.abort(), 2000); // force stop after 2s

const result = streamText({
  model: claude35,
  prompt: 'Write a long essay about distributed consensus.',
  abortSignal: controller.signal,
});

try {
  for await (const chunk of result.textStream) process.stdout.write(chunk);
} catch (err) {
  if (err.name === 'AbortError') console.error('\nAborted by signal');
  else throw err;
}

The SDK propagates the abort to the provider request, so you are not billed for generations that outlive the signal.

Single-endpoint alternative

If you maintain both API keys and worry about rate limits, you can route both models through one OpenAI-compatible gateway. n4n.ai exposes a single endpoint that fronts 240+ models and applies automatic fallback when a provider is degraded. Point the OpenAI provider at the gateway and use prefixed model strings:

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

const gateway = openai({
  baseURL: 'https://api.n4n.ai/v1',
  apiKey: process.env.N4N_API_KEY,
});

const gpt = gateway('openai/gpt-4o');
const claude = gateway('anthropic/claude-3-5-sonnet-20240620');

// streamText calls unchanged

The AI SDK sends the same request shape; the gateway forwards cache-control hints and honors routing directives. You drop the @ai-sdk/anthropic dependency entirely and get per-token metering from one place.

Error handling patterns

Providers return 4xx/5xx with structured errors. Wrap streams in try/catch and inspect the error.

const result = streamText({ model: gpt4o, prompt: '...' });
try {
  for await (const chunk of result.textStream) process.stdout.write(chunk);
} catch (err) {
  console.error('Provider error:', err.statusCode, err.message);
}

For production, add retry with exponential backoff at the transport layer or use the gateway’s built-in fallback.

TypeScript notes

The SDK is fully typed. Import ModelMessage for message arrays and LanguageModel for model refs.

import { streamText, type ModelMessage, type LanguageModel } from 'ai';
import { openai } from '@ai-sdk/openai';

const model: LanguageModel = openai('gpt-4o');
const messages: ModelMessage[] = [{ role: 'user', content: 'Ping' }];

const result = streamText({ model, messages });

Type checking catches wrong model IDs only at runtime, but message role mistakes are caught at compile time.

Expected full-run output

A complete session using chat.mjs with GPT-4o:

$ node chat.mjs gpt "What is an idempotency key?"
An idempotency key is a unique value sent with an API request that lets the server detect duplicate submissions and return the original response instead of executing the action twice.
Tokens: 12 prompt / 34 completion

And with Claude:

$ node chat.mjs claude "What is an idempotency key?"
An idempotency key is a client-generated token that uniquely identifies a request, allowing APIs to safely retry calls without causing duplicate side effects.
Tokens: 12 prompt / 31 completion

Where to go next

Read the AI SDK streamText reference for options like temperature, maxTokens, and tools. If you build a web UI, use result.toDataStreamResponse() with the useChat hook. For multi-provider resilience, the gateway pattern above removes provider-specific failure modes from your code path.

That is the full loop: install, configure, stream, abort, and consolidate. The vercel ai sdk streamtext gpt-4o claude workflow stays uniform regardless of which backend answers.

Tagsvercel-ai-sdkstreamtextgpt-4oclaude

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All vercel ai sdk deep dive posts →