AI providers
Use Anthropic or OpenAI (or both) through the Vercel AI SDK — streaming, tools, and structured output.
GlassKit ships with the Vercel AI SDK wired to both Anthropic and OpenAI providers. The AI SDK is provider-agnostic, so swapping models is a one-line change.
Why the AI SDK over a provider's native SDK
The AI SDK gives you:
- One API across providers — switch from Claude to GPT-4o by changing an import
- First-class streaming —
streamText()returns aReadableStreamthat plays nicely with React Server Components and edge runtime - Structured output —
generateObject()with a Zod schema, no JSON parsing - Tool use — same interface for Anthropic's
toolsand OpenAI'sfunction_calling - React hooks —
useChat,useCompletion,useObjectfor client UI
The trade is a thin abstraction layer. If you need a provider-specific
feature that the AI SDK hasn't surfaced yet (vision details, extended
thinking budget for Claude 4.7, etc.), you can drop down to the native
SDK — both @anthropic-ai/sdk and openai are dependencies.
Setup
Pick one provider to start. Add the API key to .env.local:
ANTHROPIC_API_KEY=sk-ant-...
# or
OPENAI_API_KEY=sk-...
You don't need both. The AI SDK constructs provider clients lazily — if you only import the Anthropic provider, the OpenAI key is irrelevant.
Basic completion (server-side)
// app/some-route/page.tsx
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export default async function Page() {
const { text } = await generateText({
model: anthropic("claude-opus-4-7"),
prompt: "Write a haiku about Next.js.",
});
return <p>{text}</p>;
}
Streaming completion (Route Handler)
// app/api/chat/route.ts
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-opus-4-7"),
messages,
});
return result.toDataStreamResponse();
}
// app/chat/page.tsx — client component
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<form onSubmit={handleSubmit}>
{messages.map((m) => (
<div key={m.id}>
<b>{m.role}:</b> {m.content}
</div>
))}
<input value={input} onChange={handleInputChange} />
</form>
);
}
Switching providers
The single-line swap:
import { openai } from "@ai-sdk/openai";
const result = await generateText({
model: openai("gpt-4o"), // ← was anthropic("claude-opus-4-7")
prompt: "...",
});
For an env-controlled swap:
// lib/ai.ts
import { anthropic } from "@ai-sdk/anthropic";
import { openai } from "@ai-sdk/openai";
export const defaultModel =
process.env.AI_PROVIDER === "openai"
? openai("gpt-4o")
: anthropic("claude-opus-4-7");
Structured output
import { generateObject } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const { object } = await generateObject({
model: anthropic("claude-opus-4-7"),
schema: z.object({
title: z.string(),
tags: z.array(z.string()).max(5),
summary: z.string(),
}),
prompt: "Summarize this article: ...",
});
// object is fully typed as { title: string; tags: string[]; summary: string }
This is significantly more reliable than asking for JSON and parsing it manually. The AI SDK does retries on schema failures.
Tool use
import { streamText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const result = streamText({
model: anthropic("claude-opus-4-7"),
prompt: "What's the weather in Tokyo?",
tools: {
getWeather: tool({
description: "Get the current weather for a city",
parameters: z.object({
city: z.string(),
}),
execute: async ({ city }) => {
// your real API call here
return { city, tempC: 22, conditions: "clear" };
},
}),
},
maxSteps: 5,
});
The model decides when to call the tool. The AI SDK handles the back-and-forth.
Cost considerations
Token costs add up fast. A few rules of thumb:
- Use the smallest model that works. Haiku is 60× cheaper than Opus
for most tasks. Try
claude-haiku-4-7first. - Cache prompts that don't change. Anthropic supports prompt caching
via
providerOptions— see the Claude API docs. - Stream wherever the user is waiting. Streaming is the same cost but feels 10× faster.
- Limit max tokens. Set
maxTokensto a hard ceiling so a runaway generation doesn't bankrupt you.
Production checklist
- API keys in Vercel env vars (never in code, never in client bundles)
- Rate limit per IP / per user — the AI SDK is happy to forward unbounded requests
- Token budget tracking — log input/output tokens per call
- Error handling — provider 429s and 500s happen; show a friendly message
- PII filtering on prompts if you're handling user data