ai-orchestration-vercel-ai-sdk
Vercel AI SDK Patterns
Quick Guide: Use Vercel AI SDK (v6) to build AI-powered applications with a unified provider API. Use
generateText/streamTextfor text generation and streaming,Output.object()/Output.array()for structured data with Zod,tool()for function calling, anduseChat/useCompletionhooks for React chat UIs. Supports OpenAI, Anthropic, Google, and 20+ providers through a single API.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use the ai package (v6) with Output.object() / Output.array() for structured output -- NOT the deprecated generateObject / streamObject functions)
(You MUST define tool input schemas with z.object() and use .describe() on each property to help the model understand expected inputs)
(You MUST use streamText for user-facing responses to enable progressive rendering -- use generateText only for background/non-interactive tasks)
(You MUST handle streaming errors via onError callback -- streamText errors become part of the stream and are NOT thrown)
(You MUST use inputSchema (not parameters) when defining tools -- parameters was renamed in SDK v5+)
</critical_requirements>
Auto-detection: AI SDK, Vercel AI, generateText, streamText, generateObject, streamObject, Output.object, Output.array, useChat, useCompletion, @ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google, tool(), toolChoice, embedMany, embed, cosineSimilarity, ToolLoopAgent, smoothStream
When to use:
- Building AI chat interfaces with streaming responses
- Generating structured data (JSON objects, arrays) from LLMs with Zod schema validation
- Implementing tool calling / function calling with LLMs
- Creating multi-provider AI applications (OpenAI, Anthropic, Google, etc.)
- Building RAG pipelines with embeddings and vector similarity
- Adding AI text completion or generation to any TypeScript app
Key patterns covered:
- Provider setup and model configuration (OpenAI, Anthropic, Google, custom)
- Text generation (
generateText) and streaming (streamText) - Structured output with Zod schemas (
Output.object,Output.array,Output.choice) - Tool calling with
tool(), multi-step execution, and approval flows - React hooks:
useChatfor chat UIs,useCompletionfor text completion - Embeddings (
embed,embedMany) and RAG patterns withcosineSimilarity
When NOT to use:
- Simple static content that doesn't need AI generation
- Server-side-only batch jobs where a direct provider SDK (e.g.,
openainpm package) is simpler - Image generation only (AI SDK supports it, but dedicated image SDKs may be more feature-rich)
Detailed Resources:
- For provider setup, text generation, and error handling, see examples/core.md
- For chat UI patterns with useChat, see examples/chat.md
- For tool definitions and multi-step calling, see examples/tools.md
- For Zod-based structured output, see examples/structured-output.md
- For embeddings and RAG, see examples/rag.md
- For quick reference tables, see reference.md
Philosophy
The Vercel AI SDK provides a unified TypeScript API for building AI-powered applications across providers. Instead of learning each provider's unique SDK, you write one set of code that works with OpenAI, Anthropic, Google, and 20+ other providers.
Core principles:
- Provider agnostic -- Switch models by changing a string, not rewriting code. The provider abstraction means
generateText({ model: 'openai/gpt-4o' })andgenerateText({ model: 'anthropic/claude-sonnet-4.5' })use the same API. - Streaming first --
streamTextstarts delivering tokens immediately. Use it for all user-facing responses.generateTextblocks until completion and is better for background tasks and agent loops. - Type-safe structured output -- Define Zod schemas and get validated, typed objects back from the model. Use
.describe()on schema properties to guide the model. - Tools as first-class citizens -- Define tools with Zod input schemas and execute functions. The SDK handles the tool call loop, including multi-step execution and human approval.
- Framework-agnostic UI hooks --
useChatanduseCompletionwork with React, Svelte, Vue, and Angular. They manage streaming state, message history, and input handling.
When to use Vercel AI SDK:
- Multi-provider applications where you want to switch models easily
- Streaming chat interfaces with React (or Svelte, Vue, Angular)
- Structured data extraction from natural language
- Agent-style applications with tool calling loops
- RAG systems with embedding and retrieval
When NOT to use:
- Single-provider scripts where the native SDK is simpler and has fewer dependencies
- Extremely high-throughput batch processing (direct API calls avoid SDK overhead)
- Non-TypeScript environments (the SDK is TypeScript-first)
Core Patterns
Pattern 1: Provider Setup
Configure providers via direct imports (auto-reads env vars), custom instances, or AI Gateway. See examples/core.md for full examples.
import { gateway } from "ai";
import { openai } from "@ai-sdk/openai";
// Gateway: provider/model string routing
const model = gateway("anthropic/claude-sonnet-4.5");
// Direct: auto-reads OPENAI_API_KEY from env
const openaiModel = openai("gpt-4o");
Use customProvider for semantic model aliases (models('fast'), models('smart')). Never hardcode API keys.
Pattern 2: Text Generation with generateText
Use generateText for non-interactive tasks. Returns a promise that resolves when complete. See examples/core.md.
import { generateText } from "ai";
const { text, usage } = await generateText({
model: "openai/gpt-4o",
system: "You are a professional technical writer.",
prompt: `Summarize: ${article}`,
});
Use ModelMessage[] for multi-turn conversations. Append response.messages for continued dialogue. Do NOT use generateText for user-facing responses -- use streamText instead.
Pattern 3: Streaming with streamText
Use streamText for all user-facing responses. Errors are part of the stream (not thrown) -- use onError. See examples/core.md.
import { streamText, smoothStream } from "ai";
const result = streamText({
model: "anthropic/claude-sonnet-4.5",
prompt: "Explain TypeScript.",
experimental_transform: smoothStream(),
onError({ error }) {
console.error("Stream error:", error);
},
});
for await (const part of result.textStream) {
process.stdout.write(part);
}
Use result.toUIMessageStreamResponse() in route handlers serving useChat, or result.toTextStreamResponse() for plain text streaming. Use result.fullStream for granular event types (text-delta, tool-call, error, finish).
Pattern 4: Structured Output with Zod
Use Output.object() with generateText/streamText for type-safe structured data. See examples/structured-output.md.
import { generateText, Output } from "ai";
import { z } from "zod";
const schema = z.object({
name: z.string().describe("Recipe name"),
steps: z.array(z.string()).describe("Cooking instructions"),
});
const { output } = await generateText({
model: "openai/gpt-4o",
output: Output.object({ schema }),
prompt: "Generate a vegetarian lasagna recipe.",
});
Key variants: Output.array({ element }) with elementStream for streaming arrays, Output.choice() for classification, partialOutputStream for streaming partial objects. Do NOT use deprecated generateObject/streamObject.
Pattern 5: Tool Calling
Define tools with tool(), Zod inputSchema, and execute. The SDK handles multi-step loops. See examples/tools.md.
import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
const weatherTool = tool({
description: "Get weather in a location",
inputSchema: z.object({
location: z.string().describe("City name"),
}),
execute: async ({ location }) => fetchWeather(location),
});
const MAX_STEPS = 5;
const { text } = await generateText({
model: "openai/gpt-4o",
tools: { weather: weatherTool },
stopWhen: stepCountIs(MAX_STEPS),
prompt: "Weather in SF and Tokyo?",
});
Key features: needsApproval for human-in-the-loop, ToolLoopAgent for reusable agents (use instructions not system), toolChoice to force/prevent tool usage, activeTools/prepareStep for per-step control. Always use stepCountIs() to prevent infinite loops.
Pattern 6: useChat Hook (React)
useChat manages streaming chat state. v6 uses transport-based architecture and external input state. See examples/chat.md.
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, status, stop, error } = useChat();
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input }); // NOT { role, content }
setInput("");
}
// ... render messages.parts, status-based UI
}
v6 breaking changes: sendMessage({ text }) replaces handleSubmit/append({ role, content }). External useState for input (hook no longer manages it). status replaces isLoading. Import from @ai-sdk/react not ai/react.
Pattern 7: useCompletion Hook (React)
useCompletion handles single-turn text completions. Unlike useChat, it still manages input state internally. See examples/core.md.
import { useCompletion } from "@ai-sdk/react";
const { completion, input, handleInputChange, handleSubmit, isLoading } =
useCompletion({
api: "/api/completion",
});
Good for autocomplete, summarization, and one-shot generation where multi-turn chat is not needed.
<decision_framework>
Decision Framework
Which Function to Use
Do you need AI-generated content?
├─ YES -> Is it user-facing (needs progressive display)?
│ ├─ YES -> Is it a multi-turn conversation?
│ │ ├─ YES -> useChat hook (React) or streamText (server)
│ │ └─ NO -> Is it a single completion/generation?
│ │ ├─ YES -> useCompletion hook (React) or streamText (server)
│ │ └─ NO -> streamText with custom UI
│ └─ NO -> Is it a background task (agent, batch)?
│ ├─ YES -> generateText (blocks until complete)
│ └─ NO -> generateText for simple one-shots
├─ Do you need structured data (JSON/objects)?
│ ├─ YES -> Output.object() with Zod schema
│ │ ├─ Need streaming partial object? -> streamText + partialOutputStream
│ │ ├─ Need array of items? -> Output.array() + elementStream
│ │ └─ Need one of N options? -> Output.choice()
│ └─ NO -> Plain text generation
├─ Do you need the model to call functions?
│ ├─ YES -> Define tools with tool() + Zod inputSchema
│ │ ├─ Multi-step reasoning? -> stopWhen: stepCountIs(N)
│ │ ├─ Need human approval? -> needsApproval on tool
│ │ └─ Single tool call? -> Default (stops after first response)
│ └─ NO -> No tools needed
└─ Do you need vector embeddings?
├─ Single text -> embed()
├─ Batch of texts -> embedMany()
└─ Similarity search -> cosineSimilarity()
Which Provider to Choose
What is your primary concern?
├─ Best reasoning / complex tasks -> anthropic/claude-sonnet-4.5 or openai/o3
├─ Fast + cheap for simple tasks -> openai/gpt-4o-mini or anthropic/claude-haiku-4.5
├─ Structured output reliability -> openai/gpt-4o (best schema adherence)
├─ Multi-modal (images + text) -> openai/gpt-4o or anthropic/claude-sonnet-4.5
├─ Google ecosystem / grounding -> google/gemini-2.5-flash
└─ Provider agnostic -> Use AI Gateway with model aliases
</decision_framework>
Integration Guide
Framework support:
- Server-side route handlers for
streamText(any framework with standardRequest/Response) - Frontend hooks (
useChat,useCompletion) from@ai-sdk/reactwith framework-specific variants for Svelte, Vue, and Angular - Edge runtime compatible (Cloudflare Workers, Vercel Edge)
Provider architecture:
- Core
aipackage providesgenerateText,streamText,embed,Output,tool,gateway - Provider packages (
@ai-sdk/openai,@ai-sdk/anthropic,@ai-sdk/google) auto-read environment variables @ai-sdk/openai-compatiblesupports any OpenAI-compatible API (Ollama, Together AI, etc.)- AI Gateway (
gateway) routes to any provider with aprovider/modelstring
Schema integration:
- Structured output (
Output.object()) and tool input schemas use Zod for validation and type inference - MCP (Model Context Protocol) integration for standardized tool access
<red_flags>
RED FLAGS
High Priority Issues:
- Using deprecated
generateObject/streamObjectinstead ofgenerateText+Output.object()(removed in v6) - Using
parametersinstead ofinputSchemain tool definitions (renamed in v5+) - Using
generateTextfor user-facing chat responses (blocks until complete, no streaming) - Hardcoding API keys in source code instead of using environment variables
- Using
import { useChat } from 'ai/react'instead ofimport { useChat } from '@ai-sdk/react' - Using
CoreMessagetype instead ofModelMessage(renamed in v6) - Calling
sendMessage({ role: 'user', content: text })instead ofsendMessage({ text })(v6 API change)
Medium Priority Issues:
- Missing
.describe()on Zod schema properties for structured output (model gets less guidance) - Not setting
stopWhenwithstepCountIs()for multi-step tool calling (risks infinite loops) - Not handling stream errors with
onErrorcallback (errors silently disappear) - Using
systeminstead ofinstructionsinToolLoopAgent(renamed in v6)
Common Mistakes:
- Forgetting that
streamTextdoes NOT throw errors -- they appear in the stream as error events - Not consuming the stream from
streamText-- the function returns immediately, you must iterate the stream - Using
objectdestructure from deprecatedgenerateObjectinstead ofoutputfromgenerateTextwithOutput.object() - Passing raw strings to
modelparameter without a provider prefix (e.g.,'gpt-4o'instead of'openai/gpt-4o')
Gotchas & Edge Cases:
smoothStream()transform adds slight delay but makes output feel more natural -- always use for chat UIsOutput.array()withelementStreamyields each element only when fully validated -- partial elements are not emittedembed()andembedMany()require embedding model strings (e.g.,'openai/text-embedding-3-small'), not chat model strings- Zod schema support varies by provider -- complex unions and transforms may not work with all models
useChatv6 no longer manages input state -- you must use externaluseStatefor the input field and callsendMessage({ text })(not{ role, content })convertToModelMessages()is async in v6 (was sync asconvertToCoreMessages()in v5) -- convertsUIMessage[]toModelMessage[]fullStreamgives you all event types includingtool-call,tool-result,source, anderror--textStreamonly gives text deltas- Token usage is available via
usageproperty on results, including cache hit details inusage.inputTokenDetails
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use the ai package (v6) with Output.object() / Output.array() for structured output -- NOT the deprecated generateObject / streamObject functions)
(You MUST define tool input schemas with z.object() and use .describe() on each property to help the model understand expected inputs)
(You MUST use streamText for user-facing responses to enable progressive rendering -- use generateText only for background/non-interactive tasks)
(You MUST handle streaming errors via onError callback -- streamText errors become part of the stream and are NOT thrown)
(You MUST use inputSchema (not parameters) when defining tools -- parameters was renamed in SDK v5+)
Failure to follow these rules will produce broken AI integrations, deprecated API usage, or poor user experiences with blocked responses.
</critical_reminders>
More from agents-inc/skills
web-animation-css-animations
CSS Animation patterns - transitions, keyframes, scroll-driven animations, @property, GPU-accelerated properties, accessibility with prefers-reduced-motion
20web-testing-playwright-e2e
Playwright E2E testing patterns - test structure, Page Object Model, locator strategies, assertions, network mocking, visual regression, parallel execution, fixtures, and configuration
18web-animation-view-transitions
View Transitions API patterns - same-document transitions, cross-document MPA transitions, shared element animations, pseudo-element styling, accessibility
17web-animation-framer-motion
Motion (formerly Framer Motion) animation patterns - motion components, variants, gestures, layout animations, scroll-linked animations, accessibility
17web-styling-cva
Class Variance Authority - type-safe component variant styling with cva(), compound variants, and VariantProps
16web-i18n-next-intl
Type-safe i18n for Next.js App Router
16