n4nAI

Switch between GPT-4o and Claude 3.5 Sonnet in Vercel AI SDK

Practical walkthrough to vercel ai sdk switch gpt-4o claude 3.5 sonnet dynamically, with install steps, Next.js route code, and verification.

n4n Team4 min read840 words

Audio narration

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

The Vercel AI SDK abstracts provider differences behind a common interface, but the exact wiring needed to vercel ai sdk switch gpt-4o claude 3.5 sonnet at runtime still trips up engineers who expect a single model string. You need separate provider packages, a selector function, and a route that respects the client’s choice. This guide walks through a production-shaped implementation with Next.js, including streaming and a verification step you can run from curl.

Step 1: Install the required packages

Use your package manager of choice. The core ai package plus the OpenAI and Anthropic adapters are required, and the React hooks live in a separate package.

pnpm add ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/react
# npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/react

Pin to recent majors. As of writing, ai v3.3+ supports the languageModel factory pattern used below. If you are on an older major, the import paths differ and useChat may come from ai/react instead of @ai-sdk/react.

Step 2: Configure provider instances

Read keys from environment variables. Never hardcode credentials. Both providers expose a factory that returns a model function bound to your account.

// lib/providers.ts
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

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

Create a .env.local at the project root:

OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...

The model identifiers must match what the provider expects. GPT-4o is gpt-4o; Claude 3.5 Sonnet is claude-3-5-sonnet-20240620 (the dated alias is stable and avoids surprise migrations).

Step 3: Write a model selector

A plain function keeps routing logic in one place. This is where the vercel ai sdk switch gpt-4o claude 3.5 sonnet decision happens.

// lib/modelSelector.ts
import { gpt4o, claude35Sonnet } from './providers';
import type { LanguageModel } from 'ai';

export type SupportedModel = 'gpt-4o' | 'claude-3-5-sonnet';

export function selectModel(model: string): LanguageModel {
  switch (model) {
    case 'gpt-4o':
      return gpt4o;
    case 'claude-3-5-sonnet':
      return claude35Sonnet;
    default:
      throw new Error(`Unsupported model: ${model}`);
  }
}

If you later add more models, extend the union and the switch. The SDK types enforce that the returned object satisfies LanguageModel, so generateText and streamText accept it uniformly. Keep the selector pure—no I/O—so it is trivial to unit test.

Step 4: Create a Next.js route handler

Use a route handler to keep keys server-side. The client posts a model field; the server selects and streams.

// app/api/chat/route.ts
import { streamText } from 'ai';
import { selectModel } from '@/lib/modelSelector';

export async function POST(req: Request) {
  const { messages, model } = await req.json();
  if (!model || typeof model !== 'string') {
    return new Response('model required', { status: 400 });
  }

  let languageModel;
  try {
    languageModel = selectModel(model);
  } catch (e) {
    return new Response((e as Error).message, { status: 400 });
  }

  const result = await streamText({
    model: languageModel,
    messages,
    temperature: 0.7,
  });

  return result.toDataStreamResponse();
}

streamText returns a data stream compatible with the Vercel AI SDK client hooks. You can swap streamText for generateText if you don’t need streaming, but most chat UIs benefit from incremental tokens. Add export const runtime = 'edge' if you want the handler to run on Edge runtime—both providers work over fetch.

Step 5: Call the endpoint from the client

Use useChat from @ai-sdk/react and pass the chosen model in the request body.

// app/Chat.tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export function Chat() {
  const [model, setModel] = useState<'gpt-4o' | 'claude-3-5-sonnet'>('gpt-4o');
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    body: { model },
  });

  return (
    <div>
      <select value={model} onChange={(e) => setModel(e.target.value as any)}>
        <option value="gpt-4o">GPT-4o</option>
        <option value="claude-3-5-sonnet">Claude 3.5 Sonnet</option>
      </select>
      {messages.map((m) => (
        <div key={m.id}>{m.role}: {m.content}</div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

Changing the dropdown updates body.model on the next request. The server picks the corresponding provider. That is the core of the vercel ai sdk switch gpt-4o claude 3.5 sonnet flow. If you are not using React, a plain fetch with body: JSON.stringify({ model, messages }) works identically.

Step 6: Handle provider-specific quirks

The SDK normalizes most differences, but a few remain visible in production:

  • Context windows: GPT-4o supports 128k tokens; Claude 3.5 Sonnet supports 200k. Set maxTokens explicitly when you pipeline long histories to avoid silent truncation.
  • Tool calling: Both support JSON tools via the SDK, but Anthropic requires tool definitions passed as tools while OpenAI accepts the same shape. No extra code needed beyond the SDK’s tools param.
  • System prompts: Pass system in the streamText call; the adapter maps it to Claude’s leading human turn or OpenAI’s system role.
  • Temperature behavior: The same numeric temperature produces different entropy across providers. 0.7 is a reasonable default for both, but tune per model if output feels off.

Example with system prompt and max tokens:

const result = await streamText({
  model: languageModel,
  system: 'You are a concise senior engineer.',
  messages,
  maxTokens: 1024,
  temperature: 0.7,
});

Step 7: Verify the switch works

Run the dev server with pnpm dev. Send two curls, one per model, and inspect the responses.

curl -X POST http://localhost:3000/api/chat \
  -H 'content-type: application/json' \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Say hi in 3 words"}]}'

curl -X POST http://localhost:3000/api/chat \
  -H 'content-type: application/json' \
  -d '{"model":"claude-3-5-sonnet","messages":[{"role":"user","content":"Say hi in 3 words"}]}'

Success criteria:

  1. Both requests return 200 with a streamed text response.
  2. Server logs (add console.log('model:', model) before streamText) show the distinct identifiers.
  3. The client dropdown toggles behavior without a page reload.

If you get Unsupported model, the selector threw—check the string matches exactly, including the dated Claude alias.

For an automated check, write a small Node script using generateText:

import { generateText } from 'ai';
import { selectModel } from './lib/modelSelector';

for (const model of ['gpt-4o', 'claude-3-5-sonnet']) {
  const { text } = await generateText({
    model: selectModel(model),
    prompt: 'Reply with the model name you are.',
  });
  console.log(model, '->', text.slice(0, 50));
}

Optional: Unify providers behind one endpoint

Managing two API keys and separate rate limits is overhead. An OpenRouter-class gateway such as n4n.ai exposes a single OpenAI-compatible endpoint addressing 240+ models, including both GPT-4o and Claude 3.5 Sonnet, with automatic fallback when a provider is rate-limited or degraded. You can point createOpenAI at its base URL and route by model name only:

const gateway = createOpenAI({
  apiKey: process.env.N4N_API_KEY,
  baseURL: 'https://api.n4n.ai/v1',
});
export const gpt4o = gateway('gpt-4o');
export const claude35Sonnet = gateway('claude-3-5-sonnet-20240620');

The gateway honors client routing directives and forwards provider cache-control hints, so the selector and route code stay identical. Per-token usage metering aggregates both providers in one bill.

Production caveats

  • Latency: Cross-provider calls have different cold starts. Measure p50/p95 in your environment before promising SLAs.
  • Fallback: If you roll your own, wrap streamText in try/catch and retry with the other model on 429. The gateway approach handles this transparently.
  • Cost tracking: The SDK returns usage on generateText; for streams, await result.usage after consumption to log tokens per model.
  • Versioning: Model IDs change. Pin dated aliases for Claude; GPT-4o may become gpt-4o-2024-05-13. Centralize the strings in lib/providers.ts.
  • Observability: Emit the selected model name in your tracing span. When the vercel ai sdk switch gpt-4o claude 3.5 sonnet logic is invisible in logs, debugging production incidents takes longer.

Switching models at runtime is not a hack; it is a first-class pattern in the Vercel AI SDK. Get the selector and route right, and you can A/B test providers or route around outages without touching client code.

Tagsvercel-ai-sdkgpt-4oclaude-3-5-sonnetmulti-model

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 multi-model switching posts →