n4nAI

Vercel AI SDK setup guide for n4n.ai's 240+ models

A practical guide to configuring Vercel AI SDK with n4n.ai's model catalog, covering provider setup, routing, streaming, and common integration pitfalls.

n4n Team5 min read997 words

Audio narration

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

The vercel ai sdk setup guide n4n.ai models engineers need starts with understanding that n4n.ai exposes a single OpenAI-compatible endpoint. You point the SDK at that endpoint, pass your API key, and the gateway handles model selection, fallback, and usage metering across 240+ models. This guide walks through the complete integration: installation, client configuration, streaming patterns, routing directives, and the failure modes you’ll hit in production.

Install the dependencies

Start with a fresh Next.js project or add to an existing one. You need the core AI SDK package and the OpenAI provider — n4n.ai speaks the OpenAI wire format, so no custom provider code required.

npm install ai @ai-sdk/openai
# or
pnpm add ai @ai-sdk/openai

The ai package contains the streaming primitives (streamText, streamObject, generateText) and the React hooks (useChat, useCompletion). The @ai-sdk/openai package provides the createOpenAI factory that lets you override the base URL — that’s the hook for n4n.ai.

Configure the client

Create a dedicated module for the gateway client. This keeps your API key out of component code and gives you a single place to adjust timeouts, headers, and retry logic.

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

export const gateway = createOpenAI({
  baseURL: 'https://api.n4n.ai/v1',
  apiKey: process.env.N4N_API_KEY,
  // Optional: customize fetch behavior
  headers: {
    'HTTP-Referer': process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
    'X-Title': 'My App',
  },
});

Set N4N_API_KEY in your environment. The HTTP-Referer and X-Title headers are optional but recommended — they appear in the n4n.ai dashboard for usage attribution and help with debugging routing decisions.

Pitfall: Don’t hardcode the API key. Use environment variables and ensure .env.local is in .gitignore. Vercel’s deployment environment injects these at build time; local development reads from .env.local.

Basic text generation

With the client configured, generating text is a one-liner. Import the gateway and call generateText with a model identifier from the n4n.ai catalog.

// app/api/chat/route.ts
import { generateText } from 'ai';
import { gateway } from '@/lib/gateway';

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

  const result = await generateText({
    model: gateway('anthropic/claude-3.5-sonnet'),
    messages,
    temperature: 0.3,
    maxTokens: 2048,
  });

  return Response.json({ text: result.text });
}

The model string anthropic/claude-3.5-sonnet follows the n4n.ai catalog format: provider/model-id. You can browse the full catalog at the n4n.ai dashboard or via their models API endpoint. The gateway normalizes provider-specific parameter names, so temperature, maxTokens, and topP work uniformly.

Streaming responses

Streaming is where the AI SDK shines. Use streamText and return a ReadableStream — the SDK handles chunk encoding, backpressure, and the data: protocol expected by the React hooks.

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

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

  const result = await streamText({
    model: gateway('openai/gpt-4o'),
    messages,
    temperature: 0.7,
    maxTokens: 4096,
  });

  return result.toDataStreamResponse();
}

The toDataStreamResponse() method returns a Response with the correct Content-Type: text/plain; charset=utf-8 and Transfer-Encoding: chunked headers. The frontend useChat hook consumes this natively.

Tradeoff: streamText buffers the entire response in memory before sending if you don’t configure onChunk. For very long generations (code generation, long-form writing), consider implementing a custom onChunk handler that flushes to the response stream incrementally to reduce time-to-first-byte.

Frontend integration with useChat

The useChat hook manages message state, input handling, and streaming UI updates. Pair it with the streaming route above.

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

import { useChat } from 'ai/react';

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

  return (
    <div className="flex flex-col h-screen p-4 gap-4">
      <div className="flex-1 overflow-y-auto space-y-4">
        {messages.map((message) => (
          <div key={message.id} className={`whitespace-pre-wrap ${message.role}`}>
            <strong>{message.role === 'user' ? 'You' : 'Assistant'}:</strong>
            {' '}{message.content}
          </div>
        ))}
        {isLoading && <div className="animate-pulse">Generating...</div>}
      </div>
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Type a message..."
          className="flex-1 p-2 border rounded"
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading || !input.trim()}>
          Send
        </button>
      </form>
    </div>
  );
}

The hook posts to /api/chat by default. Override with the api option if your route lives elsewhere. The messages array contains { id, role, content } objects — render them directly or map to your own components.

Model routing directives

n4n.ai honors client-side routing directives via the x-n4n-routing header or the model parameter format. You can steer traffic without changing code by using model aliases or explicit routing hints.

// Force a specific provider
const result = await streamText({
  model: gateway('anthropic/claude-3.5-sonnet'),
  // ...
});

// Use an alias that resolves to the best available model in a category
const result = await streamText({
  model: gateway('n4n/best-coding-model'),
  // ...
});

// Pass routing directives via providerOptions
const result = await streamText({
  model: gateway('openai/gpt-4o'),
  providerOptions: {
    n4n: {
      routing: {
        prefer: ['anthropic', 'openai'],
        avoid: ['provider-with-known-issues'],
        requireCapabilities: ['tool-use', 'vision'],
      },
    },
  },
});

The providerOptions.n4n.routing object is n4n.ai-specific. It lets you express preferences without hardcoding model IDs. The gateway evaluates available capacity, latency, and capability tags at request time. This is useful for fallback chains: if your primary provider is rate-limited or degraded, the gateway routes to the next preferred option automatically.

Pitfall: Routing directives are hints, not guarantees. If no provider matches your criteria, the gateway returns a 400 with a descriptive error. Always handle that case in your error boundary.

Structured output with generateObject

For typed responses, use generateObject with a Zod schema. The SDK handles schema injection, validation, and retry on parse failure.

// lib/schemas.ts
import { z } from 'zod';

export const codeReviewSchema = z.object({
  summary: z.string(),
  issues: z.array(
    z.object({
      file: z.string(),
      line: z.number(),
      severity: z.enum(['error', 'warning', 'suggestion']),
      message: z.string(),
      fix: z.string().optional(),
    })
  ),
  score: z.number().min(0).max(100),
});
// app/api/review/route.ts
import { generateObject } from 'ai';
import { gateway } from '@/lib/gateway';
import { codeReviewSchema } from '@/lib/schemas';

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

  const result = await generateObject({
    model: gateway('anthropic/claude-3.5-sonnet'),
    schema: codeReviewSchema,
    system: 'You are a senior code reviewer. Be thorough but concise.',
    prompt: `Review this ${language} code:\n\n${code}`,
    temperature: 0.1,
  });

  return Response.json(result.object);
}

The generateObject call automatically injects the JSON schema into the system prompt and validates the response. If the model returns invalid JSON, the SDK retries up to maxRetries times (default: 2) with a correction prompt.

Tradeoff: Structured output adds latency — typically 500ms–2s depending on model and schema complexity. For latency-sensitive paths, consider a two-stage approach: stream a quick summary, then generate the structured object in the background.

Error handling and retries

The AI SDK wraps fetch errors in APIError with provider-specific details. n4n.ai adds gateway-specific fields: gatewayCode, provider, retryAfter.

import { streamText, APIError } from 'ai';
import { gateway } from '@/lib/gateway';

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

    const result = await streamText({
      model: gateway('openai/gpt-4o'),
      messages,
      maxRetries: 2,
      // Custom retry condition
      retryWhen: (error) => {
        if (error instanceof APIError) {
          // Retry on gateway-level rate limits, not provider errors
          return error.gatewayCode === 'rate_limited' || error.statusCode === 429;
        }
        return false;
      },
    });

    return result.toDataStreamResponse();
  } catch (error) {
    if (error instanceof APIError) {
      const status = error.statusCode ?? 500;
      return Response.json(
        { error: error.message, code: error.gatewayCode, provider: error.provider },
        { status }
      );
    }
    return Response.json({ error: 'Internal server error' }, { status: 500 });
  }
}

The retryWhen predicate gives you fine-grained control. The example above retries only on gateway-level rate limits (which indicate capacity across providers) rather than provider-specific 429s (which the gateway already handles via fallback). Adjust based on your SLA requirements.

Usage metering and observability

n4n.ai returns usage metadata in the response headers and the usage field of the result object. Capture this for cost tracking and alerting.

const result = await streamText({
  model: gateway('anthropic/claude-3.5-sonnet'),
  messages,
  onFinish: async ({ usage, finishReason, response }) => {
    // usage: { promptTokens, completionTokens, totalTokens }
    // response.headers.get('x-n4n-usage') contains provider breakdown
    await logUsage({
      model: 'anthropic/claude-3.5-sonnet',
      promptTokens: usage.promptTokens,
      completionTokens: usage.completionTokens,
      finishReason,
      requestId: response.headers.get('x-request-id'),
    });
  },
});

The x-n4n-usage header provides a JSON object with per-provider token counts — useful when routing directives cause the request to hit multiple providers. Store x-request-id for correlation with the n4n.ai dashboard.

Common pitfalls

1. Model ID mismatch. The n4n.ai catalog uses provider/model-id format. Passing gpt-4o without the provider prefix returns a 404. Always use the full catalog identifier.

2. Streaming timeout. Vercel’s default function timeout is 60s (Hobby) or 300s (Pro). Long generations can exceed this. Configure maxDuration in vercel.json or split work into background jobs.

// vercel.json
{
  "functions": {
    "app/api/chat/route.ts": { "maxDuration": 300 }
  }
}

3. Missing Content-Type on client requests. The useChat hook sends application/json by default. If you call the API directly with fetch, set the header explicitly or the gateway returns 415.

4. Tool calling with non-supporting models. Not all 240+ models support function calling. Check the capability tags in the catalog or catch the APIError with gatewayCode: 'unsupported_capability' and fall back to a compatible model.

5. Double-encoding streaming responses. If you wrap toDataStreamResponse() in another Response or NextResponse, you’ll break the chunked encoding. Return the result directly.

Local development vs production

In development, you might want to hit a local mock or a different gateway instance. Use environment-specific configuration:

// lib/gateway.ts
const isDev = process.env.NODE_ENV === 'development';

export const gateway = createOpenAI({
  baseURL: isDev
    ? 'http://localhost:4000/v1'  // local mock gateway
    : 'https://api.n4n.ai/v1',
  apiKey: isDev ? 'dev-key' : process.env.N4N_API_KEY,
});

Run a local OpenAI-compatible mock (like msw or a lightweight FastAPI server) for offline development. This keeps your iteration loop fast and avoids burning gateway quota on typo fixes.

Next steps

You now have a working Vercel AI SDK integration with n4n.ai. From here:

  • Add tool calling with streamText({ tools: { ... } }) for agentic workflows
  • Implement conversation persistence with the onFinish callback and a database
  • Set up evals using the AI SDK’s experimental_generateImage and experimental_transcribe for multimodal paths
  • Configure custom routing policies in the n4n.ai dashboard for cost optimization

The gateway’s single-endpoint design means you can swap models, add fallbacks, and adjust routing without touching application code. That’s the leverage: your integration stays stable while the model landscape shifts underneath.

Tagsvercel-ai-sdkn4n-aisetup-guidemodel-catalog

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 getting started with n4n.ai posts →